diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e5e44d8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + ignore: + - dependency-name: "*" + update-types: [ "version-update:semver-major" ] diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..f985483 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,44 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Maven Central Deployment + +on: + workflow_dispatch + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + + - name: Set up Maven Central + uses: actions/setup-java@v3 + with: # running setup-java again overwrites the settings.xml + distribution: 'temurin' + java-version: '17' + server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml + server-username: OSSRH_USERNAME # env variable for username in deploy + server-password: OSSRH_TOKEN # env variable for token in deploy + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import + gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase + + - name: Build and Publish to Maven Central + run: mvn -B clean deploy -Prelease --file pom.xml + env: + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..113af43 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,32 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Java CI with Maven + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + + - name: Build with Maven + run: mvn -B clean install --file pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db3d2a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +*target* +*.jar +*.war +*.ear +*.class +.DS_Store + +# eclipse specific git ignore +.project +.metadata +.factorypath +bin/** +tmp/** +tmp/**/* +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# IntelliJ specific git ignore +.idea/ \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..e1fd273 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d264116 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Mapstruct SPI implementation for protocol buffers mapping + +This project provides a SPI implementation for [Mapstruct](http://mapstruct.org/) to generate mapping code from protocol +buffers to the following targets: + +- Plain Old Java Objects (POJOs) +- [Immutables](https://immutables.github.io/) value objects +- Java records + +Unit tests exist to validate all of these mappings. The SPI implementation generally requires Mapstruct 1.5.5 and Java +1.8+ (of course if you want to map to records, Java 14+ is required). + +The enum mapping strategy assumes that Google's enum value naming scheme is used, as described +here: https://developers.google.com/protocol-buffers/docs/style#enum + +This SPI implementation also includes a [pull request](https://github.com/mapstruct/mapstruct/pull/2219) from the +Mapstruct repository that was not merged yet, but fixes a +deficiency with Mapstructs' own org.immutables support when using inner classes and @Value.Enclosing. + +## Usage + +Your protobuf mapping interfaces must be annotated with `@Mapper` +and `collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED` +because the protobuf classes use a builder pattern. + +```java + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface XXX { + +``` + +Include the mapstruct dependency and the annotation processor in your Maven project: + +```xml + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + + + + + maven-compiler-plugin + + + + de.firehead + mapstruct-spi-protobuf + 1.0.0 + + + + + + + +``` + +Or for Gradle: + +```java + +implementation"org.mapstruct:mapstruct:${mapstructVersion}" +annotationProcessor"org.mapstruct:mapstruct-processor:${mapstructVersion}" +annotationProcessor"de.firehead:mapstruct-spi-protobuf:1.0.0" + +``` + diff --git a/mapstruct-spi-protobuf-test-immutables/pom.xml b/mapstruct-spi-protobuf-test-immutables/pom.xml new file mode 100644 index 0000000..442a57e --- /dev/null +++ b/mapstruct-spi-protobuf-test-immutables/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + + + de.firehead + mapstruct-spi-protobuf-parent + 1.0.0-SNAPSHOT + + + mapstruct-spi-protobuf-test-immutables + + + 1.8 + 1.8 + true + true + + + + + de.firehead + mapstruct-spi-protobuf-test-protos + + + + org.mapstruct + mapstruct + + + com.google.protobuf + protobuf-java + + + org.immutables + value + provided + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + + + dev.cookiecode + another-protobuf-maven-plugin + 2.1.0 + + + + compile + compile-custom + + generate-sources + + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + org.immutables + value-processor + ${immutables.version} + + + de.firehead + mapstruct-spi-protobuf + ${project.version} + + + + + + + + diff --git a/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/DeepTestImmutableObject.java b/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/DeepTestImmutableObject.java new file mode 100644 index 0000000..6aade6e --- /dev/null +++ b/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/DeepTestImmutableObject.java @@ -0,0 +1,31 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.immutables; + +import org.immutables.value.Value; + +/** + * Test immutable object for deep testing purposes. + * This corresponds to the following test proto message: + *

+ * message DeepTestProtoMessage { + * TestProtoMessage testProtoMessage = 1; + * repeated TestProtoMessage testProtoMessageList = 2; + * map testProtoMessageMap = 3; + * } + */ +@Value.Immutable +public interface DeepTestImmutableObject { + + TestImmutableObject getTestProtoMessagePlain(); + + java.util.List getTestProtoMessageList(); + + java.util.Map getTestProtoMessageMap(); + +} diff --git a/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/TestEnum.java b/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/TestEnum.java new file mode 100644 index 0000000..f760693 --- /dev/null +++ b/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/TestEnum.java @@ -0,0 +1,13 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.immutables; + +public enum TestEnum { + + VALUE; +} diff --git a/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/TestImmutableObject.java b/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/TestImmutableObject.java new file mode 100644 index 0000000..d938eef --- /dev/null +++ b/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/TestImmutableObject.java @@ -0,0 +1,70 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.immutables; + +import org.immutables.value.Value; + +/** + * Test immutable object for testing purposes. + * This corresponds to the following test proto message: + *

+ * message TestProtoMessage { + * string stringField = 1; + * int32 intField = 2; + * int64 longField = 3; + * float floatField = 4; + * double doubleField = 5; + * bool booleanField = 6; + * bytes bytesField = 7; + * TestEnum enumField = 8; + * map stringMapField = 9; + * map intMapField = 10; + * map longMapField = 11; + * map floatMapField = 12; + * map doubleMapField = 13; + * map boolMapField = 14; + * map bytesMapField = 15; + * map enumMapField = 16; + * } + */ +@Value.Immutable +public interface TestImmutableObject { + + String getStringField(); + + int getIntField(); + + long getLongField(); + + float getFloatField(); + + double getDoubleField(); + + boolean getBooleanField(); + + byte[] getBytesField(); + + TestEnum getEnumField(); + + java.util.Map getStringMapField(); + + java.util.Map getIntMapField(); + + java.util.Map getLongMapField(); + + java.util.Map getFloatMapField(); + + java.util.Map getDoubleMapField(); + + java.util.Map getBoolMapField(); + + java.util.Map getBytesMapField(); + + java.util.Map getEnumMapField(); + +} diff --git a/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/mapper/TestMapper.java b/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/mapper/TestMapper.java new file mode 100644 index 0000000..498cd18 --- /dev/null +++ b/mapstruct-spi-protobuf-test-immutables/src/main/java/de/firehead/mapstruct/spi/protobuf/test/immutables/mapper/TestMapper.java @@ -0,0 +1,40 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.immutables.mapper; + +import com.google.protobuf.ByteString; +import de.firehead.mapstruct.spi.protobuf.test.immutables.DeepTestImmutableObject; +import de.firehead.mapstruct.spi.protobuf.test.immutables.ImmutableDeepTestImmutableObject; +import de.firehead.mapstruct.spi.protobuf.test.immutables.ImmutableTestImmutableObject; +import de.firehead.mapstruct.spi.protobuf.test.immutables.TestImmutableObject; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public abstract class TestMapper { + + public static final TestMapper INSTANCE = Mappers.getMapper(TestMapper.class); + + public abstract ImmutableTestImmutableObject mapTestProtoToImmutable(TestProtos.TestProtoMessage testProtoMessage); + + public abstract TestProtos.TestProtoMessage mapTestImmutableToProto(TestImmutableObject testImmutableObject); + + public abstract ImmutableDeepTestImmutableObject mapDeepTestProtoToImmutable(TestProtos.DeepTestProtoMessage deepTestProtoMessage); + + public abstract TestProtos.DeepTestProtoMessage mapDeepTestImmutableToProto(DeepTestImmutableObject testImmutableObject); + + protected byte[] mapByteStringToByteArray(com.google.protobuf.ByteString byteString) { + return byteString.toByteArray(); + } + + protected ByteString mapByteArrayToByteString(byte[] byteArray) { + return ByteString.copyFrom(byteArray); + } +} diff --git a/mapstruct-spi-protobuf-test-immutables/src/test/java/de/firehead/mapstruct/spi/protobuf/test/immutables/AbstractMappingTest.java b/mapstruct-spi-protobuf-test-immutables/src/test/java/de/firehead/mapstruct/spi/protobuf/test/immutables/AbstractMappingTest.java new file mode 100644 index 0000000..82f8cc9 --- /dev/null +++ b/mapstruct-spi-protobuf-test-immutables/src/test/java/de/firehead/mapstruct/spi/protobuf/test/immutables/AbstractMappingTest.java @@ -0,0 +1,69 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.immutables; + +import com.google.protobuf.ByteString; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; + +public abstract class AbstractMappingTest { + + protected static final TestImmutableObject TEST_IMMUTABLE_OBJECT = ImmutableTestImmutableObject.builder() + .stringField("test") + .intField(42) + .longField(42L) + .floatField(42.0f) + .doubleField(42.0) + .booleanField(true) + .bytesField(new byte[]{0x01, 0x02, 0x03}) + .enumField(TestEnum.VALUE) + .putStringMapField("key1", "value1") + .putStringMapField("key2", "value2") + .putIntMapField("key1", 42) + .putIntMapField("key2", 43) + .putLongMapField("key1", 42L) + .putLongMapField("key2", 43L) + .putFloatMapField("key1", 42.0f) + .putFloatMapField("key2", 42.1f) + .putDoubleMapField("key1", 42.0) + .putDoubleMapField("key2", 42.1) + .putBoolMapField("key1", true) + .putBoolMapField("key2", false) + .putBytesMapField("key1", new byte[]{0x01, 0x02, 0x03}) + .putBytesMapField("key2", new byte[]{0x04, 0x05, 0x06}) + .putEnumMapField("key1", TestEnum.VALUE) + .putEnumMapField("key2", TestEnum.VALUE) + .build(); + + protected static final TestProtos.TestProtoMessage TEST_PROTO_MESSAGE = TestProtos.TestProtoMessage.newBuilder() + .setStringField("test") + .setIntField(42) + .setLongField(42L) + .setFloatField(42.0f) + .setDoubleField(42.0) + .setBooleanField(true) + .setBytesField(ByteString.copyFrom(new byte[]{0x01, 0x02, 0x03})) + .setEnumField(TestProtos.TestEnum.TEST_ENUM_VALUE) + .putStringMapField("key1", "value1") + .putStringMapField("key2", "value2") + .putIntMapField("key1", 42) + .putIntMapField("key2", 43) + .putLongMapField("key1", 42L) + .putLongMapField("key2", 43L) + .putFloatMapField("key1", 42.0f) + .putFloatMapField("key2", 42.1f) + .putDoubleMapField("key1", 42.0) + .putDoubleMapField("key2", 42.1) + .putBoolMapField("key1", true) + .putBoolMapField("key2", false) + .putBytesMapField("key1", ByteString.copyFrom(new byte[]{0x01, 0x02, 0x03})) + .putBytesMapField("key2", ByteString.copyFrom(new byte[]{0x04, 0x05, 0x06})) + .putEnumMapField("key1", TestProtos.TestEnum.TEST_ENUM_VALUE) + .putEnumMapField("key2", TestProtos.TestEnum.TEST_ENUM_VALUE) + .build(); + +} diff --git a/mapstruct-spi-protobuf-test-immutables/src/test/java/de/firehead/mapstruct/spi/protobuf/test/immutables/DeepMappingTest.java b/mapstruct-spi-protobuf-test-immutables/src/test/java/de/firehead/mapstruct/spi/protobuf/test/immutables/DeepMappingTest.java new file mode 100644 index 0000000..afb84c4 --- /dev/null +++ b/mapstruct-spi-protobuf-test-immutables/src/test/java/de/firehead/mapstruct/spi/protobuf/test/immutables/DeepMappingTest.java @@ -0,0 +1,148 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.immutables; + +import de.firehead.mapstruct.spi.protobuf.test.immutables.mapper.TestMapper; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class DeepMappingTest extends AbstractMappingTest { + + private static final DeepTestImmutableObject DEEP_TEST_IMMUTABLE_OBJECT = ImmutableDeepTestImmutableObject.builder() + .testProtoMessagePlain(TEST_IMMUTABLE_OBJECT) + .addTestProtoMessageList(TEST_IMMUTABLE_OBJECT) + .putTestProtoMessageMap("key1", TEST_IMMUTABLE_OBJECT) + .build(); + + private static final TestProtos.DeepTestProtoMessage DEEP_TEST_PROTO_MESSAGE = TestProtos.DeepTestProtoMessage.newBuilder() + .setTestProtoMessagePlain(TEST_PROTO_MESSAGE) + .addTestProtoMessageList(TEST_PROTO_MESSAGE) + .putTestProtoMessageMap("key1", TEST_PROTO_MESSAGE) + .build(); + + + @Test + public void testDeepMappingProtoToImmutable() { + final DeepTestImmutableObject mappedImmutable = TestMapper.INSTANCE.mapDeepTestProtoToImmutable(DEEP_TEST_PROTO_MESSAGE); + + // Test testProtoMessage + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getStringField(), mappedImmutable.getTestProtoMessagePlain().getStringField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getIntField(), mappedImmutable.getTestProtoMessagePlain().getIntField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getLongField(), mappedImmutable.getTestProtoMessagePlain().getLongField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getFloatField(), mappedImmutable.getTestProtoMessagePlain().getFloatField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getDoubleField(), mappedImmutable.getTestProtoMessagePlain().getDoubleField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBooleanField(), mappedImmutable.getTestProtoMessagePlain().getBooleanField()); + Assertions.assertArrayEquals(TEST_IMMUTABLE_OBJECT.getBytesField(), mappedImmutable.getTestProtoMessagePlain().getBytesField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getEnumField(), mappedImmutable.getTestProtoMessagePlain().getEnumField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getStringMapField(), mappedImmutable.getTestProtoMessagePlain().getStringMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getIntMapField(), mappedImmutable.getTestProtoMessagePlain().getIntMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getLongMapField(), mappedImmutable.getTestProtoMessagePlain().getLongMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getFloatMapField(), mappedImmutable.getTestProtoMessagePlain().getFloatMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getDoubleMapField(), mappedImmutable.getTestProtoMessagePlain().getDoubleMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBoolMapField(), mappedImmutable.getTestProtoMessagePlain().getBoolMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBytesMapField().size(), mappedImmutable.getTestProtoMessagePlain().getBytesMapField().size()); + for (final String key : TEST_IMMUTABLE_OBJECT.getBytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_IMMUTABLE_OBJECT.getBytesMapField().get(key), mappedImmutable.getTestProtoMessagePlain().getBytesMapField().get(key)); + } + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getEnumMapField(), mappedImmutable.getTestProtoMessagePlain().getEnumMapField()); + + // Test testProtoMessageList + Assertions.assertEquals(1, mappedImmutable.getTestProtoMessageList().size()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getStringField(), mappedImmutable.getTestProtoMessageList().get(0).getStringField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getIntField(), mappedImmutable.getTestProtoMessageList().get(0).getIntField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getLongField(), mappedImmutable.getTestProtoMessageList().get(0).getLongField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getFloatField(), mappedImmutable.getTestProtoMessageList().get(0).getFloatField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getDoubleField(), mappedImmutable.getTestProtoMessageList().get(0).getDoubleField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBooleanField(), mappedImmutable.getTestProtoMessageList().get(0).getBooleanField()); + Assertions.assertArrayEquals(TEST_IMMUTABLE_OBJECT.getBytesField(), mappedImmutable.getTestProtoMessageList().get(0).getBytesField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getEnumField(), mappedImmutable.getTestProtoMessageList().get(0).getEnumField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getStringMapField(), mappedImmutable.getTestProtoMessageList().get(0).getStringMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getIntMapField(), mappedImmutable.getTestProtoMessageList().get(0).getIntMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getLongMapField(), mappedImmutable.getTestProtoMessageList().get(0).getLongMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getFloatMapField(), mappedImmutable.getTestProtoMessageList().get(0).getFloatMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getDoubleMapField(), mappedImmutable.getTestProtoMessageList().get(0).getDoubleMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBoolMapField(), mappedImmutable.getTestProtoMessageList().get(0).getBoolMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBytesMapField().size(), mappedImmutable.getTestProtoMessageList().get(0).getBytesMapField().size()); + for (final String key : TEST_IMMUTABLE_OBJECT.getBytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_IMMUTABLE_OBJECT.getBytesMapField().get(key), mappedImmutable.getTestProtoMessageList().get(0).getBytesMapField().get(key)); + } + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getEnumMapField(), mappedImmutable.getTestProtoMessageList().get(0).getEnumMapField()); + + // Test testProtoMessageMap + Assertions.assertEquals(1, mappedImmutable.getTestProtoMessageMap().size()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getStringField(), mappedImmutable.getTestProtoMessageMap().get("key1").getStringField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getIntField(), mappedImmutable.getTestProtoMessageMap().get("key1").getIntField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getLongField(), mappedImmutable.getTestProtoMessageMap().get("key1").getLongField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getFloatField(), mappedImmutable.getTestProtoMessageMap().get("key1").getFloatField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getDoubleField(), mappedImmutable.getTestProtoMessageMap().get("key1").getDoubleField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBooleanField(), mappedImmutable.getTestProtoMessageMap().get("key1").getBooleanField()); + Assertions.assertArrayEquals(TEST_IMMUTABLE_OBJECT.getBytesField(), mappedImmutable.getTestProtoMessageMap().get("key1").getBytesField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getEnumField(), mappedImmutable.getTestProtoMessageMap().get("key1").getEnumField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getStringMapField(), mappedImmutable.getTestProtoMessageMap().get("key1").getStringMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getIntMapField(), mappedImmutable.getTestProtoMessageMap().get("key1").getIntMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getLongMapField(), mappedImmutable.getTestProtoMessageMap().get("key1").getLongMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getFloatMapField(), mappedImmutable.getTestProtoMessageMap().get("key1").getFloatMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getDoubleMapField(), mappedImmutable.getTestProtoMessageMap().get("key1").getDoubleMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBoolMapField(), mappedImmutable.getTestProtoMessageMap().get("key1").getBoolMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBytesMapField().size(), mappedImmutable.getTestProtoMessageMap().get("key1").getBytesMapField().size()); + for (final String key : TEST_IMMUTABLE_OBJECT.getBytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_IMMUTABLE_OBJECT.getBytesMapField().get(key), mappedImmutable.getTestProtoMessageMap().get("key1").getBytesMapField().get(key)); + } + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getEnumMapField(), mappedImmutable.getTestProtoMessageMap().get("key1").getEnumMapField()); + } + + @Test + public void testDeepMappingImmutableToProto() { + final TestProtos.DeepTestProtoMessage mappedProto = TestMapper.INSTANCE.mapDeepTestImmutableToProto(DEEP_TEST_IMMUTABLE_OBJECT); + + // Test testProtoMessage + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringField(), mappedProto.getTestProtoMessagePlain().getStringField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntField(), mappedProto.getTestProtoMessagePlain().getIntField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongField(), mappedProto.getTestProtoMessagePlain().getLongField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatField(), mappedProto.getTestProtoMessagePlain().getFloatField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleField(), mappedProto.getTestProtoMessagePlain().getDoubleField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBooleanField(), mappedProto.getTestProtoMessagePlain().getBooleanField()); + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesField().toByteArray(), mappedProto.getTestProtoMessagePlain().getBytesField().toByteArray()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumField(), mappedProto.getTestProtoMessagePlain().getEnumField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringMapFieldMap(), mappedProto.getTestProtoMessagePlain().getStringMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntMapFieldMap(), mappedProto.getTestProtoMessagePlain().getIntMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongMapFieldMap(), mappedProto.getTestProtoMessagePlain().getLongMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatMapFieldMap(), mappedProto.getTestProtoMessagePlain().getFloatMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleMapFieldMap(), mappedProto.getTestProtoMessagePlain().getDoubleMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBoolMapFieldMap(), mappedProto.getTestProtoMessagePlain().getBoolMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().size(), mappedProto.getTestProtoMessagePlain().getBytesMapFieldMap().size()); + for (final String key : TEST_PROTO_MESSAGE.getBytesMapFieldMap().keySet()) { + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().get(key).toByteArray(), mappedProto.getTestProtoMessagePlain().getBytesMapFieldMap().get(key).toByteArray()); + } + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumMapFieldMap(), mappedProto.getTestProtoMessagePlain().getEnumMapFieldMap()); + + // Test testProtoMessageList + Assertions.assertEquals(1, mappedProto.getTestProtoMessageListList().size()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringField(), mappedProto.getTestProtoMessageListList().get(0).getStringField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntField(), mappedProto.getTestProtoMessageListList().get(0).getIntField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongField(), mappedProto.getTestProtoMessageListList().get(0).getLongField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatField(), mappedProto.getTestProtoMessageListList().get(0).getFloatField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleField(), mappedProto.getTestProtoMessageListList().get(0).getDoubleField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBooleanField(), mappedProto.getTestProtoMessageListList().get(0).getBooleanField()); + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesField().toByteArray(), mappedProto.getTestProtoMessageListList().get(0).getBytesField().toByteArray()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumField(), mappedProto.getTestProtoMessageListList().get(0).getEnumField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getStringMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getIntMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getLongMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getFloatMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getDoubleMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBoolMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getBoolMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().size(), mappedProto.getTestProtoMessageListList().get(0).getBytesMapFieldMap().size()); + for (final String key : TEST_PROTO_MESSAGE.getBytesMapFieldMap().keySet()) { + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().get(key).toByteArray(), mappedProto.getTestProtoMessageListList().get(0).getBytesMapFieldMap().get(key).toByteArray()); + } + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getEnumMapFieldMap()); + } + +} diff --git a/mapstruct-spi-protobuf-test-immutables/src/test/java/de/firehead/mapstruct/spi/protobuf/test/immutables/SimpleMappingTest.java b/mapstruct-spi-protobuf-test-immutables/src/test/java/de/firehead/mapstruct/spi/protobuf/test/immutables/SimpleMappingTest.java new file mode 100644 index 0000000..e664868 --- /dev/null +++ b/mapstruct-spi-protobuf-test-immutables/src/test/java/de/firehead/mapstruct/spi/protobuf/test/immutables/SimpleMappingTest.java @@ -0,0 +1,67 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.immutables; + +import de.firehead.mapstruct.spi.protobuf.test.immutables.mapper.TestMapper; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class SimpleMappingTest extends AbstractMappingTest { + + @Test + public void testSimpleMappingProtoToImmutable() { + final TestImmutableObject mappedImmutable = TestMapper.INSTANCE.mapTestProtoToImmutable(TEST_PROTO_MESSAGE); + + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getStringField(), mappedImmutable.getStringField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getIntField(), mappedImmutable.getIntField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getLongField(), mappedImmutable.getLongField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getFloatField(), mappedImmutable.getFloatField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getDoubleField(), mappedImmutable.getDoubleField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBooleanField(), mappedImmutable.getBooleanField()); + Assertions.assertArrayEquals(TEST_IMMUTABLE_OBJECT.getBytesField(), mappedImmutable.getBytesField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getEnumField(), mappedImmutable.getEnumField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getStringMapField(), mappedImmutable.getStringMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getIntMapField(), mappedImmutable.getIntMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getLongMapField(), mappedImmutable.getLongMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getFloatMapField(), mappedImmutable.getFloatMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getDoubleMapField(), mappedImmutable.getDoubleMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBoolMapField(), mappedImmutable.getBoolMapField()); + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getBytesMapField().size(), mappedImmutable.getBytesMapField().size()); + for (final String key : TEST_IMMUTABLE_OBJECT.getBytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_IMMUTABLE_OBJECT.getBytesMapField().get(key), mappedImmutable.getBytesMapField().get(key)); + } + Assertions.assertEquals(TEST_IMMUTABLE_OBJECT.getEnumMapField(), mappedImmutable.getEnumMapField()); + } + + @Test + public void testSimpleMappingImmutableToProto() { + final TestProtos.TestProtoMessage mappedProto = TestMapper.INSTANCE.mapTestImmutableToProto(TEST_IMMUTABLE_OBJECT); + + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringField(), mappedProto.getStringField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntField(), mappedProto.getIntField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongField(), mappedProto.getLongField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatField(), mappedProto.getFloatField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleField(), mappedProto.getDoubleField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBooleanField(), mappedProto.getBooleanField()); + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesField().toByteArray(), mappedProto.getBytesField().toByteArray()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumField(), mappedProto.getEnumField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringMapFieldMap(), mappedProto.getStringMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntMapFieldMap(), mappedProto.getIntMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongMapFieldMap(), mappedProto.getLongMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatMapFieldMap(), mappedProto.getFloatMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleMapFieldMap(), mappedProto.getDoubleMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBoolMapFieldMap(), mappedProto.getBoolMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().size(), mappedProto.getBytesMapFieldMap().size()); + for (final String key : TEST_PROTO_MESSAGE.getBytesMapFieldMap().keySet()) { + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().get(key).toByteArray(), mappedProto.getBytesMapFieldMap().get(key).toByteArray()); + } + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumMapFieldMap(), mappedProto.getEnumMapFieldMap()); + } + +} diff --git a/mapstruct-spi-protobuf-test-pojo/pom.xml b/mapstruct-spi-protobuf-test-pojo/pom.xml new file mode 100644 index 0000000..08761a3 --- /dev/null +++ b/mapstruct-spi-protobuf-test-pojo/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + + de.firehead + mapstruct-spi-protobuf-parent + 1.0.0-SNAPSHOT + + + mapstruct-spi-protobuf-test-pojo + + + 1.8 + 1.8 + true + true + + + + + de.firehead + mapstruct-spi-protobuf-test-protos + + + + org.mapstruct + mapstruct + + + com.google.protobuf + protobuf-java + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + + + dev.cookiecode + another-protobuf-maven-plugin + 2.1.0 + + + + compile + compile-custom + + generate-sources + + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + org.immutables + value-processor + ${immutables.version} + + + de.firehead + mapstruct-spi-protobuf + ${project.version} + + + + + + + + diff --git a/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/DeepTestObject.java b/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/DeepTestObject.java new file mode 100644 index 0000000..8a69b2e --- /dev/null +++ b/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/DeepTestObject.java @@ -0,0 +1,54 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.pojo; + +import java.util.List; +import java.util.Map; + +/** + * Test immutable object for deep testing purposes. + * This corresponds to the following test proto message: + *

+ * message DeepTestProtoMessage { + * TestProtoMessage testProtoMessage = 1; + * repeated TestProtoMessage testProtoMessageList = 2; + * map testProtoMessageMap = 3; + * } + */ +public class DeepTestObject { + + TestObject testProtoMessagePlain; + + java.util.List testProtoMessageList; + + java.util.Map testProtoMessageMap; + + public TestObject getTestProtoMessagePlain() { + return testProtoMessagePlain; + } + + public void setTestProtoMessagePlain(TestObject testProtoMessagePlain) { + this.testProtoMessagePlain = testProtoMessagePlain; + } + + public List getTestProtoMessageList() { + return testProtoMessageList; + } + + public void setTestProtoMessageList(List testProtoMessageList) { + this.testProtoMessageList = testProtoMessageList; + } + + public Map getTestProtoMessageMap() { + return testProtoMessageMap; + } + + public void setTestProtoMessageMap(Map testProtoMessageMap) { + this.testProtoMessageMap = testProtoMessageMap; + } +} diff --git a/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/TestEnum.java b/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/TestEnum.java new file mode 100644 index 0000000..488497e --- /dev/null +++ b/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/TestEnum.java @@ -0,0 +1,13 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.pojo; + +public enum TestEnum { + + VALUE; +} diff --git a/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/TestObject.java b/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/TestObject.java new file mode 100644 index 0000000..e363f68 --- /dev/null +++ b/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/TestObject.java @@ -0,0 +1,196 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.pojo; + +import java.util.Map; + +/** + * Test immutable object for testing purposes. + * This corresponds to the following test proto message: + *

+ * message TestProtoMessage { + * string stringField = 1; + * int32 intField = 2; + * int64 longField = 3; + * float floatField = 4; + * double doubleField = 5; + * bool booleanField = 6; + * bytes bytesField = 7; + * TestEnum enumField = 8; + * map stringMapField = 9; + * map intMapField = 10; + * map longMapField = 11; + * map floatMapField = 12; + * map doubleMapField = 13; + * map boolMapField = 14; + * map bytesMapField = 15; + * map enumMapField = 16; + * } + */ +public class TestObject { + + private String stringField; + + private int intField; + + private long longField; + + private float floatField; + + private double doubleField; + + private boolean booleanField; + + private byte[] bytesField; + + private TestEnum enumField; + + private java.util.Map stringMapField; + + private java.util.Map intMapField; + + private java.util.Map longMapField; + + private java.util.Map floatMapField; + + private java.util.Map doubleMapField; + + private java.util.Map boolMapField; + + private java.util.Map bytesMapField; + + private java.util.Map enumMapField; + + public String getStringField() { + return stringField; + } + + public void setStringField(String stringField) { + this.stringField = stringField; + } + + public int getIntField() { + return intField; + } + + public void setIntField(int intField) { + this.intField = intField; + } + + public long getLongField() { + return longField; + } + + public void setLongField(long longField) { + this.longField = longField; + } + + public float getFloatField() { + return floatField; + } + + public void setFloatField(float floatField) { + this.floatField = floatField; + } + + public double getDoubleField() { + return doubleField; + } + + public void setDoubleField(double doubleField) { + this.doubleField = doubleField; + } + + public boolean getBooleanField() { + return booleanField; + } + + public void setBooleanField(boolean booleanField) { + this.booleanField = booleanField; + } + + public byte[] getBytesField() { + return bytesField; + } + + public void setBytesField(byte[] bytesField) { + this.bytesField = bytesField; + } + + public TestEnum getEnumField() { + return enumField; + } + + public void setEnumField(TestEnum enumField) { + this.enumField = enumField; + } + + public Map getStringMapField() { + return stringMapField; + } + + public void setStringMapField(Map stringMapField) { + this.stringMapField = stringMapField; + } + + public Map getIntMapField() { + return intMapField; + } + + public void setIntMapField(Map intMapField) { + this.intMapField = intMapField; + } + + public Map getLongMapField() { + return longMapField; + } + + public void setLongMapField(Map longMapField) { + this.longMapField = longMapField; + } + + public Map getFloatMapField() { + return floatMapField; + } + + public void setFloatMapField(Map floatMapField) { + this.floatMapField = floatMapField; + } + + public Map getDoubleMapField() { + return doubleMapField; + } + + public void setDoubleMapField(Map doubleMapField) { + this.doubleMapField = doubleMapField; + } + + public Map getBoolMapField() { + return boolMapField; + } + + public void setBoolMapField(Map boolMapField) { + this.boolMapField = boolMapField; + } + + public Map getBytesMapField() { + return bytesMapField; + } + + public void setBytesMapField(Map bytesMapField) { + this.bytesMapField = bytesMapField; + } + + public Map getEnumMapField() { + return enumMapField; + } + + public void setEnumMapField(Map enumMapField) { + this.enumMapField = enumMapField; + } +} diff --git a/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/mapper/TestMapper.java b/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/mapper/TestMapper.java new file mode 100644 index 0000000..77cc6d0 --- /dev/null +++ b/mapstruct-spi-protobuf-test-pojo/src/main/java/de/firehead/mapstruct/spi/protobuf/test/pojo/mapper/TestMapper.java @@ -0,0 +1,38 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.pojo.mapper; + +import com.google.protobuf.ByteString; +import de.firehead.mapstruct.spi.protobuf.test.pojo.DeepTestObject; +import de.firehead.mapstruct.spi.protobuf.test.pojo.TestObject; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public abstract class TestMapper { + + public static final TestMapper INSTANCE = Mappers.getMapper(TestMapper.class); + + public abstract TestObject mapTestProtoToPojo(TestProtos.TestProtoMessage testProtoMessage); + + public abstract TestProtos.TestProtoMessage mapTestPojoToProto(TestObject testObject); + + public abstract DeepTestObject mapDeepTestProtoToPojo(TestProtos.DeepTestProtoMessage deepTestProtoMessage); + + public abstract TestProtos.DeepTestProtoMessage mapDeepTestPojoToProto(DeepTestObject testImmutableObject); + + protected byte[] mapByteStringToByteArray(com.google.protobuf.ByteString byteString) { + return byteString.toByteArray(); + } + + protected ByteString mapByteArrayToByteString(byte[] byteArray) { + return ByteString.copyFrom(byteArray); + } +} diff --git a/mapstruct-spi-protobuf-test-pojo/src/test/java/de/firehead/mapstruct/spi/protobuf/test/pojo/AbstractMappingTest.java b/mapstruct-spi-protobuf-test-pojo/src/test/java/de/firehead/mapstruct/spi/protobuf/test/pojo/AbstractMappingTest.java new file mode 100644 index 0000000..5d386bc --- /dev/null +++ b/mapstruct-spi-protobuf-test-pojo/src/test/java/de/firehead/mapstruct/spi/protobuf/test/pojo/AbstractMappingTest.java @@ -0,0 +1,81 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.pojo; + +import com.google.protobuf.ByteString; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; + +import java.util.HashMap; + +public abstract class AbstractMappingTest { + + protected static final TestObject TEST_OBJECT = new TestObject(); + + static { + TEST_OBJECT.setStringField("test"); + TEST_OBJECT.setIntField(42); + TEST_OBJECT.setLongField(42L); + TEST_OBJECT.setFloatField(42.0f); + TEST_OBJECT.setDoubleField(42.0); + TEST_OBJECT.setBooleanField(true); + TEST_OBJECT.setBytesField(new byte[]{0x01, 0x02, 0x03}); + TEST_OBJECT.setEnumField(TestEnum.VALUE); + TEST_OBJECT.setStringMapField(new HashMap<>()); + TEST_OBJECT.getStringMapField().put("key1", "value1"); + TEST_OBJECT.getStringMapField().put("key2", "value2"); + TEST_OBJECT.setIntMapField(new HashMap<>()); + TEST_OBJECT.getIntMapField().put("key1", 42); + TEST_OBJECT.getIntMapField().put("key2", 43); + TEST_OBJECT.setLongMapField(new HashMap<>()); + TEST_OBJECT.getLongMapField().put("key1", 42L); + TEST_OBJECT.getLongMapField().put("key2", 43L); + TEST_OBJECT.setFloatMapField(new HashMap<>()); + TEST_OBJECT.getFloatMapField().put("key1", 42.0f); + TEST_OBJECT.getFloatMapField().put("key2", 42.1f); + TEST_OBJECT.setDoubleMapField(new HashMap<>()); + TEST_OBJECT.getDoubleMapField().put("key1", 42.0); + TEST_OBJECT.getDoubleMapField().put("key2", 42.1); + TEST_OBJECT.setBoolMapField(new HashMap<>()); + TEST_OBJECT.getBoolMapField().put("key1", true); + TEST_OBJECT.getBoolMapField().put("key2", false); + TEST_OBJECT.setBytesMapField(new HashMap<>()); + TEST_OBJECT.getBytesMapField().put("key1", new byte[]{0x01, 0x02, 0x03}); + TEST_OBJECT.getBytesMapField().put("key2", new byte[]{0x04, 0x05, 0x06}); + TEST_OBJECT.setEnumMapField(new HashMap<>()); + TEST_OBJECT.getEnumMapField().put("key1", TestEnum.VALUE); + TEST_OBJECT.getEnumMapField().put("key2", TestEnum.VALUE); + } + + protected static final TestProtos.TestProtoMessage TEST_PROTO_MESSAGE = TestProtos.TestProtoMessage.newBuilder() + .setStringField("test") + .setIntField(42) + .setLongField(42L) + .setFloatField(42.0f) + .setDoubleField(42.0) + .setBooleanField(true) + .setBytesField(ByteString.copyFrom(new byte[]{0x01, 0x02, 0x03})) + .setEnumField(TestProtos.TestEnum.TEST_ENUM_VALUE) + .putStringMapField("key1", "value1") + .putStringMapField("key2", "value2") + .putIntMapField("key1", 42) + .putIntMapField("key2", 43) + .putLongMapField("key1", 42L) + .putLongMapField("key2", 43L) + .putFloatMapField("key1", 42.0f) + .putFloatMapField("key2", 42.1f) + .putDoubleMapField("key1", 42.0) + .putDoubleMapField("key2", 42.1) + .putBoolMapField("key1", true) + .putBoolMapField("key2", false) + .putBytesMapField("key1", ByteString.copyFrom(new byte[]{0x01, 0x02, 0x03})) + .putBytesMapField("key2", ByteString.copyFrom(new byte[]{0x04, 0x05, 0x06})) + .putEnumMapField("key1", TestProtos.TestEnum.TEST_ENUM_VALUE) + .putEnumMapField("key2", TestProtos.TestEnum.TEST_ENUM_VALUE) + .build(); + +} diff --git a/mapstruct-spi-protobuf-test-pojo/src/test/java/de/firehead/mapstruct/spi/protobuf/test/pojo/DeepMappingTest.java b/mapstruct-spi-protobuf-test-pojo/src/test/java/de/firehead/mapstruct/spi/protobuf/test/pojo/DeepMappingTest.java new file mode 100644 index 0000000..8a044c3 --- /dev/null +++ b/mapstruct-spi-protobuf-test-pojo/src/test/java/de/firehead/mapstruct/spi/protobuf/test/pojo/DeepMappingTest.java @@ -0,0 +1,152 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.pojo; + +import de.firehead.mapstruct.spi.protobuf.test.pojo.mapper.TestMapper; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +public class DeepMappingTest extends AbstractMappingTest { + + private static final DeepTestObject DEEP_TEST_OBJECT = new DeepTestObject(); + + static { + DEEP_TEST_OBJECT.setTestProtoMessagePlain(TEST_OBJECT); + DEEP_TEST_OBJECT.setTestProtoMessageList(Collections.singletonList(TEST_OBJECT)); + DEEP_TEST_OBJECT.setTestProtoMessageMap(Collections.singletonMap("key1", TEST_OBJECT)); + } + + private static final TestProtos.DeepTestProtoMessage DEEP_TEST_PROTO_MESSAGE = TestProtos.DeepTestProtoMessage.newBuilder() + .setTestProtoMessagePlain(TEST_PROTO_MESSAGE) + .addTestProtoMessageList(TEST_PROTO_MESSAGE) + .putTestProtoMessageMap("key1", TEST_PROTO_MESSAGE) + .build(); + + + @Test + public void testDeepMappingProtoToPojo() { + final DeepTestObject mappedPojo = TestMapper.INSTANCE.mapDeepTestProtoToPojo(DEEP_TEST_PROTO_MESSAGE); + + // Test testProtoMessage + Assertions.assertEquals(TEST_OBJECT.getStringField(), mappedPojo.getTestProtoMessagePlain().getStringField()); + Assertions.assertEquals(TEST_OBJECT.getIntField(), mappedPojo.getTestProtoMessagePlain().getIntField()); + Assertions.assertEquals(TEST_OBJECT.getLongField(), mappedPojo.getTestProtoMessagePlain().getLongField()); + Assertions.assertEquals(TEST_OBJECT.getFloatField(), mappedPojo.getTestProtoMessagePlain().getFloatField()); + Assertions.assertEquals(TEST_OBJECT.getDoubleField(), mappedPojo.getTestProtoMessagePlain().getDoubleField()); + Assertions.assertEquals(TEST_OBJECT.getBooleanField(), mappedPojo.getTestProtoMessagePlain().getBooleanField()); + Assertions.assertArrayEquals(TEST_OBJECT.getBytesField(), mappedPojo.getTestProtoMessagePlain().getBytesField()); + Assertions.assertEquals(TEST_OBJECT.getEnumField(), mappedPojo.getTestProtoMessagePlain().getEnumField()); + Assertions.assertEquals(TEST_OBJECT.getStringMapField(), mappedPojo.getTestProtoMessagePlain().getStringMapField()); + Assertions.assertEquals(TEST_OBJECT.getIntMapField(), mappedPojo.getTestProtoMessagePlain().getIntMapField()); + Assertions.assertEquals(TEST_OBJECT.getLongMapField(), mappedPojo.getTestProtoMessagePlain().getLongMapField()); + Assertions.assertEquals(TEST_OBJECT.getFloatMapField(), mappedPojo.getTestProtoMessagePlain().getFloatMapField()); + Assertions.assertEquals(TEST_OBJECT.getDoubleMapField(), mappedPojo.getTestProtoMessagePlain().getDoubleMapField()); + Assertions.assertEquals(TEST_OBJECT.getBoolMapField(), mappedPojo.getTestProtoMessagePlain().getBoolMapField()); + Assertions.assertEquals(TEST_OBJECT.getBytesMapField().size(), mappedPojo.getTestProtoMessagePlain().getBytesMapField().size()); + for (final String key : TEST_OBJECT.getBytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_OBJECT.getBytesMapField().get(key), mappedPojo.getTestProtoMessagePlain().getBytesMapField().get(key)); + } + Assertions.assertEquals(TEST_OBJECT.getEnumMapField(), mappedPojo.getTestProtoMessagePlain().getEnumMapField()); + + // Test testProtoMessageList + Assertions.assertEquals(1, mappedPojo.getTestProtoMessageList().size()); + Assertions.assertEquals(TEST_OBJECT.getStringField(), mappedPojo.getTestProtoMessageList().get(0).getStringField()); + Assertions.assertEquals(TEST_OBJECT.getIntField(), mappedPojo.getTestProtoMessageList().get(0).getIntField()); + Assertions.assertEquals(TEST_OBJECT.getLongField(), mappedPojo.getTestProtoMessageList().get(0).getLongField()); + Assertions.assertEquals(TEST_OBJECT.getFloatField(), mappedPojo.getTestProtoMessageList().get(0).getFloatField()); + Assertions.assertEquals(TEST_OBJECT.getDoubleField(), mappedPojo.getTestProtoMessageList().get(0).getDoubleField()); + Assertions.assertEquals(TEST_OBJECT.getBooleanField(), mappedPojo.getTestProtoMessageList().get(0).getBooleanField()); + Assertions.assertArrayEquals(TEST_OBJECT.getBytesField(), mappedPojo.getTestProtoMessageList().get(0).getBytesField()); + Assertions.assertEquals(TEST_OBJECT.getEnumField(), mappedPojo.getTestProtoMessageList().get(0).getEnumField()); + Assertions.assertEquals(TEST_OBJECT.getStringMapField(), mappedPojo.getTestProtoMessageList().get(0).getStringMapField()); + Assertions.assertEquals(TEST_OBJECT.getIntMapField(), mappedPojo.getTestProtoMessageList().get(0).getIntMapField()); + Assertions.assertEquals(TEST_OBJECT.getLongMapField(), mappedPojo.getTestProtoMessageList().get(0).getLongMapField()); + Assertions.assertEquals(TEST_OBJECT.getFloatMapField(), mappedPojo.getTestProtoMessageList().get(0).getFloatMapField()); + Assertions.assertEquals(TEST_OBJECT.getDoubleMapField(), mappedPojo.getTestProtoMessageList().get(0).getDoubleMapField()); + Assertions.assertEquals(TEST_OBJECT.getBoolMapField(), mappedPojo.getTestProtoMessageList().get(0).getBoolMapField()); + Assertions.assertEquals(TEST_OBJECT.getBytesMapField().size(), mappedPojo.getTestProtoMessageList().get(0).getBytesMapField().size()); + for (final String key : TEST_OBJECT.getBytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_OBJECT.getBytesMapField().get(key), mappedPojo.getTestProtoMessageList().get(0).getBytesMapField().get(key)); + } + Assertions.assertEquals(TEST_OBJECT.getEnumMapField(), mappedPojo.getTestProtoMessageList().get(0).getEnumMapField()); + + // Test testProtoMessageMap + Assertions.assertEquals(1, mappedPojo.getTestProtoMessageMap().size()); + Assertions.assertEquals(TEST_OBJECT.getStringField(), mappedPojo.getTestProtoMessageMap().get("key1").getStringField()); + Assertions.assertEquals(TEST_OBJECT.getIntField(), mappedPojo.getTestProtoMessageMap().get("key1").getIntField()); + Assertions.assertEquals(TEST_OBJECT.getLongField(), mappedPojo.getTestProtoMessageMap().get("key1").getLongField()); + Assertions.assertEquals(TEST_OBJECT.getFloatField(), mappedPojo.getTestProtoMessageMap().get("key1").getFloatField()); + Assertions.assertEquals(TEST_OBJECT.getDoubleField(), mappedPojo.getTestProtoMessageMap().get("key1").getDoubleField()); + Assertions.assertEquals(TEST_OBJECT.getBooleanField(), mappedPojo.getTestProtoMessageMap().get("key1").getBooleanField()); + Assertions.assertArrayEquals(TEST_OBJECT.getBytesField(), mappedPojo.getTestProtoMessageMap().get("key1").getBytesField()); + Assertions.assertEquals(TEST_OBJECT.getEnumField(), mappedPojo.getTestProtoMessageMap().get("key1").getEnumField()); + Assertions.assertEquals(TEST_OBJECT.getStringMapField(), mappedPojo.getTestProtoMessageMap().get("key1").getStringMapField()); + Assertions.assertEquals(TEST_OBJECT.getIntMapField(), mappedPojo.getTestProtoMessageMap().get("key1").getIntMapField()); + Assertions.assertEquals(TEST_OBJECT.getLongMapField(), mappedPojo.getTestProtoMessageMap().get("key1").getLongMapField()); + Assertions.assertEquals(TEST_OBJECT.getFloatMapField(), mappedPojo.getTestProtoMessageMap().get("key1").getFloatMapField()); + Assertions.assertEquals(TEST_OBJECT.getDoubleMapField(), mappedPojo.getTestProtoMessageMap().get("key1").getDoubleMapField()); + Assertions.assertEquals(TEST_OBJECT.getBoolMapField(), mappedPojo.getTestProtoMessageMap().get("key1").getBoolMapField()); + Assertions.assertEquals(TEST_OBJECT.getBytesMapField().size(), mappedPojo.getTestProtoMessageMap().get("key1").getBytesMapField().size()); + for (final String key : TEST_OBJECT.getBytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_OBJECT.getBytesMapField().get(key), mappedPojo.getTestProtoMessageMap().get("key1").getBytesMapField().get(key)); + } + Assertions.assertEquals(TEST_OBJECT.getEnumMapField(), mappedPojo.getTestProtoMessageMap().get("key1").getEnumMapField()); + } + + @Test + public void testDeepMappingPojoToProto() { + final TestProtos.DeepTestProtoMessage mappedProto = TestMapper.INSTANCE.mapDeepTestPojoToProto(DEEP_TEST_OBJECT); + + // Test testProtoMessage + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringField(), mappedProto.getTestProtoMessagePlain().getStringField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntField(), mappedProto.getTestProtoMessagePlain().getIntField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongField(), mappedProto.getTestProtoMessagePlain().getLongField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatField(), mappedProto.getTestProtoMessagePlain().getFloatField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleField(), mappedProto.getTestProtoMessagePlain().getDoubleField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBooleanField(), mappedProto.getTestProtoMessagePlain().getBooleanField()); + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesField().toByteArray(), mappedProto.getTestProtoMessagePlain().getBytesField().toByteArray()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumField(), mappedProto.getTestProtoMessagePlain().getEnumField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringMapFieldMap(), mappedProto.getTestProtoMessagePlain().getStringMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntMapFieldMap(), mappedProto.getTestProtoMessagePlain().getIntMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongMapFieldMap(), mappedProto.getTestProtoMessagePlain().getLongMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatMapFieldMap(), mappedProto.getTestProtoMessagePlain().getFloatMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleMapFieldMap(), mappedProto.getTestProtoMessagePlain().getDoubleMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBoolMapFieldMap(), mappedProto.getTestProtoMessagePlain().getBoolMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().size(), mappedProto.getTestProtoMessagePlain().getBytesMapFieldMap().size()); + for (final String key : TEST_PROTO_MESSAGE.getBytesMapFieldMap().keySet()) { + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().get(key).toByteArray(), mappedProto.getTestProtoMessagePlain().getBytesMapFieldMap().get(key).toByteArray()); + } + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumMapFieldMap(), mappedProto.getTestProtoMessagePlain().getEnumMapFieldMap()); + + // Test testProtoMessageList + Assertions.assertEquals(1, mappedProto.getTestProtoMessageListList().size()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringField(), mappedProto.getTestProtoMessageListList().get(0).getStringField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntField(), mappedProto.getTestProtoMessageListList().get(0).getIntField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongField(), mappedProto.getTestProtoMessageListList().get(0).getLongField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatField(), mappedProto.getTestProtoMessageListList().get(0).getFloatField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleField(), mappedProto.getTestProtoMessageListList().get(0).getDoubleField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBooleanField(), mappedProto.getTestProtoMessageListList().get(0).getBooleanField()); + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesField().toByteArray(), mappedProto.getTestProtoMessageListList().get(0).getBytesField().toByteArray()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumField(), mappedProto.getTestProtoMessageListList().get(0).getEnumField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getStringMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getIntMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getLongMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getFloatMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getDoubleMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBoolMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getBoolMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().size(), mappedProto.getTestProtoMessageListList().get(0).getBytesMapFieldMap().size()); + for (final String key : TEST_PROTO_MESSAGE.getBytesMapFieldMap().keySet()) { + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().get(key).toByteArray(), mappedProto.getTestProtoMessageListList().get(0).getBytesMapFieldMap().get(key).toByteArray()); + } + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getEnumMapFieldMap()); + } + +} diff --git a/mapstruct-spi-protobuf-test-pojo/src/test/java/de/firehead/mapstruct/spi/protobuf/test/pojo/SimpleMappingTest.java b/mapstruct-spi-protobuf-test-pojo/src/test/java/de/firehead/mapstruct/spi/protobuf/test/pojo/SimpleMappingTest.java new file mode 100644 index 0000000..f432b10 --- /dev/null +++ b/mapstruct-spi-protobuf-test-pojo/src/test/java/de/firehead/mapstruct/spi/protobuf/test/pojo/SimpleMappingTest.java @@ -0,0 +1,67 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.pojo; + +import de.firehead.mapstruct.spi.protobuf.test.pojo.mapper.TestMapper; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class SimpleMappingTest extends AbstractMappingTest { + + @Test + public void testSimpleMappingProtoToPojo() { + final TestObject mappedPojo = TestMapper.INSTANCE.mapTestProtoToPojo(TEST_PROTO_MESSAGE); + + Assertions.assertEquals(TEST_OBJECT.getStringField(), mappedPojo.getStringField()); + Assertions.assertEquals(TEST_OBJECT.getIntField(), mappedPojo.getIntField()); + Assertions.assertEquals(TEST_OBJECT.getLongField(), mappedPojo.getLongField()); + Assertions.assertEquals(TEST_OBJECT.getFloatField(), mappedPojo.getFloatField()); + Assertions.assertEquals(TEST_OBJECT.getDoubleField(), mappedPojo.getDoubleField()); + Assertions.assertEquals(TEST_OBJECT.getBooleanField(), mappedPojo.getBooleanField()); + Assertions.assertArrayEquals(TEST_OBJECT.getBytesField(), mappedPojo.getBytesField()); + Assertions.assertEquals(TEST_OBJECT.getEnumField(), mappedPojo.getEnumField()); + Assertions.assertEquals(TEST_OBJECT.getStringMapField(), mappedPojo.getStringMapField()); + Assertions.assertEquals(TEST_OBJECT.getIntMapField(), mappedPojo.getIntMapField()); + Assertions.assertEquals(TEST_OBJECT.getLongMapField(), mappedPojo.getLongMapField()); + Assertions.assertEquals(TEST_OBJECT.getFloatMapField(), mappedPojo.getFloatMapField()); + Assertions.assertEquals(TEST_OBJECT.getDoubleMapField(), mappedPojo.getDoubleMapField()); + Assertions.assertEquals(TEST_OBJECT.getBoolMapField(), mappedPojo.getBoolMapField()); + Assertions.assertEquals(TEST_OBJECT.getBytesMapField().size(), mappedPojo.getBytesMapField().size()); + for (final String key : TEST_OBJECT.getBytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_OBJECT.getBytesMapField().get(key), mappedPojo.getBytesMapField().get(key)); + } + Assertions.assertEquals(TEST_OBJECT.getEnumMapField(), mappedPojo.getEnumMapField()); + } + + @Test + public void testSimpleMappingPojoToProto() { + final TestProtos.TestProtoMessage mappedProto = TestMapper.INSTANCE.mapTestPojoToProto(TEST_OBJECT); + + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringField(), mappedProto.getStringField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntField(), mappedProto.getIntField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongField(), mappedProto.getLongField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatField(), mappedProto.getFloatField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleField(), mappedProto.getDoubleField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBooleanField(), mappedProto.getBooleanField()); + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesField().toByteArray(), mappedProto.getBytesField().toByteArray()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumField(), mappedProto.getEnumField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringMapFieldMap(), mappedProto.getStringMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntMapFieldMap(), mappedProto.getIntMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongMapFieldMap(), mappedProto.getLongMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatMapFieldMap(), mappedProto.getFloatMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleMapFieldMap(), mappedProto.getDoubleMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBoolMapFieldMap(), mappedProto.getBoolMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().size(), mappedProto.getBytesMapFieldMap().size()); + for (final String key : TEST_PROTO_MESSAGE.getBytesMapFieldMap().keySet()) { + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().get(key).toByteArray(), mappedProto.getBytesMapFieldMap().get(key).toByteArray()); + } + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumMapFieldMap(), mappedProto.getEnumMapFieldMap()); + } + +} diff --git a/mapstruct-spi-protobuf-test-protos/pom.xml b/mapstruct-spi-protobuf-test-protos/pom.xml new file mode 100644 index 0000000..2146049 --- /dev/null +++ b/mapstruct-spi-protobuf-test-protos/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + + de.firehead + mapstruct-spi-protobuf-parent + 1.0.0-SNAPSHOT + + + mapstruct-spi-protobuf-test-protos + + + 1.8 + 1.8 + true + true + + + + + com.google.protobuf + protobuf-java + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + + + dev.cookiecode + another-protobuf-maven-plugin + 2.1.0 + + + + compile + compile-custom + + generate-sources + + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + org.immutables + value-processor + ${immutables.version} + + + de.firehead + mapstruct-spi-protobuf + ${project.version} + + + + + + + + diff --git a/mapstruct-spi-protobuf-test-protos/src/main/proto/MapstructSpiProtobufTestProtos.proto b/mapstruct-spi-protobuf-test-protos/src/main/proto/MapstructSpiProtobufTestProtos.proto new file mode 100644 index 0000000..0694d59 --- /dev/null +++ b/mapstruct-spi-protobuf-test-protos/src/main/proto/MapstructSpiProtobufTestProtos.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +option java_package = "de.firehead.mapstruct.spi.protobuf.test.protos"; +option java_outer_classname = "TestProtos"; + +message TestProtoMessage { + string stringField = 1; + int32 intField = 2; + int64 longField = 3; + float floatField = 4; + double doubleField = 5; + bool booleanField = 6; + bytes bytesField = 7; + TestEnum enumField = 8; + map stringMapField = 9; + map intMapField = 10; + map longMapField = 11; + map floatMapField = 12; + map doubleMapField = 13; + map boolMapField = 14; + map bytesMapField = 15; + map enumMapField = 16; +} + +message DeepTestProtoMessage { + TestProtoMessage testProtoMessagePlain = 1; + repeated TestProtoMessage testProtoMessageList = 2; + map testProtoMessageMap = 3; +} + +enum TestEnum { + TEST_ENUM_UNSPECIFIED = 0; + TEST_ENUM_VALUE = 1; +} diff --git a/mapstruct-spi-protobuf-test-records/pom.xml b/mapstruct-spi-protobuf-test-records/pom.xml new file mode 100644 index 0000000..904a8c8 --- /dev/null +++ b/mapstruct-spi-protobuf-test-records/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + + de.firehead + mapstruct-spi-protobuf-parent + 1.0.0-SNAPSHOT + + + mapstruct-spi-protobuf-test-records + + + 17 + 17 + true + true + + + + + de.firehead + mapstruct-spi-protobuf-test-protos + + + + org.mapstruct + mapstruct + + + com.google.protobuf + protobuf-java + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + + + dev.cookiecode + another-protobuf-maven-plugin + 2.1.0 + + + + compile + compile-custom + + generate-sources + + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + + org.immutables + value-processor + ${immutables.version} + + + de.firehead + mapstruct-spi-protobuf + ${project.version} + + + + + + + + diff --git a/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/DeepTestRecord.java b/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/DeepTestRecord.java new file mode 100644 index 0000000..8fa8b4e --- /dev/null +++ b/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/DeepTestRecord.java @@ -0,0 +1,26 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.records; + +import java.util.List; +import java.util.Map; + +/** + * Test immutable object for deep testing purposes. + * This corresponds to the following test proto message: + *

+ * message DeepTestProtoMessage { + * TestProtoMessage testProtoMessage = 1; + * repeated TestProtoMessage testProtoMessageList = 2; + * map testProtoMessageMap = 3; + * } + */ +public record DeepTestRecord(TestRecord testProtoMessagePlain, + List testProtoMessageList, + Map testProtoMessageMap) { +} diff --git a/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/TestEnum.java b/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/TestEnum.java new file mode 100644 index 0000000..c0a9ffe --- /dev/null +++ b/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/TestEnum.java @@ -0,0 +1,13 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.records; + +public enum TestEnum { + + VALUE; +} diff --git a/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/TestRecord.java b/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/TestRecord.java new file mode 100644 index 0000000..f71b0d4 --- /dev/null +++ b/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/TestRecord.java @@ -0,0 +1,43 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.records; + +import java.util.Map; + +/** + * Test immutable record for testing purposes. + * This corresponds to the following test proto message: + *

+ * message TestProtoMessage { + * string stringField = 1; + * int32 intField = 2; + * int64 longField = 3; + * float floatField = 4; + * double doubleField = 5; + * bool booleanField = 6; + * bytes bytesField = 7; + * TestEnum enumField = 8; + * map stringMapField = 9; + * map intMapField = 10; + * map longMapField = 11; + * map floatMapField = 12; + * map doubleMapField = 13; + * map boolMapField = 14; + * map bytesMapField = 15; + * map enumMapField = 16; + * } + */ +public record TestRecord(String stringField, int intField, long longField, float floatField, double doubleField, + boolean booleanField, byte[] bytesField, TestEnum enumField, + Map stringMapField, + Map intMapField, Map longMapField, + Map floatMapField, + Map doubleMapField, Map boolMapField, + Map bytesMapField, + Map enumMapField) { +} diff --git a/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/mapper/TestMapper.java b/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/mapper/TestMapper.java new file mode 100644 index 0000000..be2c587 --- /dev/null +++ b/mapstruct-spi-protobuf-test-records/src/main/java/de/firehead/mapstruct/spi/protobuf/test/records/mapper/TestMapper.java @@ -0,0 +1,38 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.records.mapper; + +import com.google.protobuf.ByteString; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; +import de.firehead.mapstruct.spi.protobuf.test.records.DeepTestRecord; +import de.firehead.mapstruct.spi.protobuf.test.records.TestRecord; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public abstract class TestMapper { + + public static final TestMapper INSTANCE = Mappers.getMapper(TestMapper.class); + + public abstract TestRecord mapTestProtoToRecord(TestProtos.TestProtoMessage testProtoMessage); + + public abstract TestProtos.TestProtoMessage mapTestRecordToProto(TestRecord testRecord); + + public abstract DeepTestRecord mapDeepTestProtoToRecord(TestProtos.DeepTestProtoMessage deepTestProtoMessage); + + public abstract TestProtos.DeepTestProtoMessage mapDeepTestRecordToProto(DeepTestRecord testImmutableObject); + + protected byte[] mapByteStringToByteArray(com.google.protobuf.ByteString byteString) { + return byteString.toByteArray(); + } + + protected ByteString mapByteArrayToByteString(byte[] byteArray) { + return ByteString.copyFrom(byteArray); + } +} diff --git a/mapstruct-spi-protobuf-test-records/src/test/java/de/firehead/mapstruct/spi/protobuf/test/records/AbstractMappingTest.java b/mapstruct-spi-protobuf-test-records/src/test/java/de/firehead/mapstruct/spi/protobuf/test/records/AbstractMappingTest.java new file mode 100644 index 0000000..24c62bc --- /dev/null +++ b/mapstruct-spi-protobuf-test-records/src/test/java/de/firehead/mapstruct/spi/protobuf/test/records/AbstractMappingTest.java @@ -0,0 +1,46 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.records; + +import com.google.protobuf.ByteString; +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; + +import java.util.Map; + +public abstract class AbstractMappingTest { + + protected static final TestRecord TEST_RECORD = new TestRecord("test", 42, 42L, 42.0f, 42.0, true, new byte[]{0x01, 0x02, 0x03}, TestEnum.VALUE, Map.of("key1", "value1", "key2", "value2"), Map.of("key1", 42, "key2", 43), Map.of("key1", 42L, "key2", 43L), Map.of("key1", 42.0f, "key2", 42.1f), Map.of("key1", 42.0, "key2", 42.1), Map.of("key1", true, "key2", false), Map.of("key1", new byte[]{0x01, 0x02, 0x03}, "key2", new byte[]{0x04, 0x05, 0x06}), Map.of("key1", TestEnum.VALUE, "key2", TestEnum.VALUE)); + + protected static final TestProtos.TestProtoMessage TEST_PROTO_MESSAGE = TestProtos.TestProtoMessage.newBuilder() + .setStringField("test") + .setIntField(42) + .setLongField(42L) + .setFloatField(42.0f) + .setDoubleField(42.0) + .setBooleanField(true) + .setBytesField(ByteString.copyFrom(new byte[]{0x01, 0x02, 0x03})) + .setEnumField(TestProtos.TestEnum.TEST_ENUM_VALUE) + .putStringMapField("key1", "value1") + .putStringMapField("key2", "value2") + .putIntMapField("key1", 42) + .putIntMapField("key2", 43) + .putLongMapField("key1", 42L) + .putLongMapField("key2", 43L) + .putFloatMapField("key1", 42.0f) + .putFloatMapField("key2", 42.1f) + .putDoubleMapField("key1", 42.0) + .putDoubleMapField("key2", 42.1) + .putBoolMapField("key1", true) + .putBoolMapField("key2", false) + .putBytesMapField("key1", ByteString.copyFrom(new byte[]{0x01, 0x02, 0x03})) + .putBytesMapField("key2", ByteString.copyFrom(new byte[]{0x04, 0x05, 0x06})) + .putEnumMapField("key1", TestProtos.TestEnum.TEST_ENUM_VALUE) + .putEnumMapField("key2", TestProtos.TestEnum.TEST_ENUM_VALUE) + .build(); + +} diff --git a/mapstruct-spi-protobuf-test-records/src/test/java/de/firehead/mapstruct/spi/protobuf/test/records/DeepMappingTest.java b/mapstruct-spi-protobuf-test-records/src/test/java/de/firehead/mapstruct/spi/protobuf/test/records/DeepMappingTest.java new file mode 100644 index 0000000..2f939c9 --- /dev/null +++ b/mapstruct-spi-protobuf-test-records/src/test/java/de/firehead/mapstruct/spi/protobuf/test/records/DeepMappingTest.java @@ -0,0 +1,146 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.records; + +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; +import de.firehead.mapstruct.spi.protobuf.test.records.mapper.TestMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +public class DeepMappingTest extends AbstractMappingTest { + + private static final DeepTestRecord DEEP_TEST_RECORD = new DeepTestRecord(TEST_RECORD, Collections.singletonList(TEST_RECORD), Collections.singletonMap("key1", TEST_RECORD)); + + private static final TestProtos.DeepTestProtoMessage DEEP_TEST_PROTO_MESSAGE = TestProtos.DeepTestProtoMessage.newBuilder() + .setTestProtoMessagePlain(TEST_PROTO_MESSAGE) + .addTestProtoMessageList(TEST_PROTO_MESSAGE) + .putTestProtoMessageMap("key1", TEST_PROTO_MESSAGE) + .build(); + + + @Test + public void testDeepMappingProtoToRecord() { + final DeepTestRecord mappedRecord = TestMapper.INSTANCE.mapDeepTestProtoToRecord(DEEP_TEST_PROTO_MESSAGE); + + // Test testProtoMessage + Assertions.assertEquals(TEST_RECORD.stringField(), mappedRecord.testProtoMessagePlain().stringField()); + Assertions.assertEquals(TEST_RECORD.intField(), mappedRecord.testProtoMessagePlain().intField()); + Assertions.assertEquals(TEST_RECORD.longField(), mappedRecord.testProtoMessagePlain().longField()); + Assertions.assertEquals(TEST_RECORD.floatField(), mappedRecord.testProtoMessagePlain().floatField()); + Assertions.assertEquals(TEST_RECORD.doubleField(), mappedRecord.testProtoMessagePlain().doubleField()); + Assertions.assertEquals(TEST_RECORD.booleanField(), mappedRecord.testProtoMessagePlain().booleanField()); + Assertions.assertArrayEquals(TEST_RECORD.bytesField(), mappedRecord.testProtoMessagePlain().bytesField()); + Assertions.assertEquals(TEST_RECORD.enumField(), mappedRecord.testProtoMessagePlain().enumField()); + Assertions.assertEquals(TEST_RECORD.stringMapField(), mappedRecord.testProtoMessagePlain().stringMapField()); + Assertions.assertEquals(TEST_RECORD.intMapField(), mappedRecord.testProtoMessagePlain().intMapField()); + Assertions.assertEquals(TEST_RECORD.longMapField(), mappedRecord.testProtoMessagePlain().longMapField()); + Assertions.assertEquals(TEST_RECORD.floatMapField(), mappedRecord.testProtoMessagePlain().floatMapField()); + Assertions.assertEquals(TEST_RECORD.doubleMapField(), mappedRecord.testProtoMessagePlain().doubleMapField()); + Assertions.assertEquals(TEST_RECORD.boolMapField(), mappedRecord.testProtoMessagePlain().boolMapField()); + Assertions.assertEquals(TEST_RECORD.bytesMapField().size(), mappedRecord.testProtoMessagePlain().bytesMapField().size()); + for (final String key : TEST_RECORD.bytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_RECORD.bytesMapField().get(key), mappedRecord.testProtoMessagePlain().bytesMapField().get(key)); + } + Assertions.assertEquals(TEST_RECORD.enumMapField(), mappedRecord.testProtoMessagePlain().enumMapField()); + + // Test testProtoMessageList + Assertions.assertEquals(1, mappedRecord.testProtoMessageList().size()); + Assertions.assertEquals(TEST_RECORD.stringField(), mappedRecord.testProtoMessageList().get(0).stringField()); + Assertions.assertEquals(TEST_RECORD.intField(), mappedRecord.testProtoMessageList().get(0).intField()); + Assertions.assertEquals(TEST_RECORD.longField(), mappedRecord.testProtoMessageList().get(0).longField()); + Assertions.assertEquals(TEST_RECORD.floatField(), mappedRecord.testProtoMessageList().get(0).floatField()); + Assertions.assertEquals(TEST_RECORD.doubleField(), mappedRecord.testProtoMessageList().get(0).doubleField()); + Assertions.assertEquals(TEST_RECORD.booleanField(), mappedRecord.testProtoMessageList().get(0).booleanField()); + Assertions.assertArrayEquals(TEST_RECORD.bytesField(), mappedRecord.testProtoMessageList().get(0).bytesField()); + Assertions.assertEquals(TEST_RECORD.enumField(), mappedRecord.testProtoMessageList().get(0).enumField()); + Assertions.assertEquals(TEST_RECORD.stringMapField(), mappedRecord.testProtoMessageList().get(0).stringMapField()); + Assertions.assertEquals(TEST_RECORD.intMapField(), mappedRecord.testProtoMessageList().get(0).intMapField()); + Assertions.assertEquals(TEST_RECORD.longMapField(), mappedRecord.testProtoMessageList().get(0).longMapField()); + Assertions.assertEquals(TEST_RECORD.floatMapField(), mappedRecord.testProtoMessageList().get(0).floatMapField()); + Assertions.assertEquals(TEST_RECORD.doubleMapField(), mappedRecord.testProtoMessageList().get(0).doubleMapField()); + Assertions.assertEquals(TEST_RECORD.boolMapField(), mappedRecord.testProtoMessageList().get(0).boolMapField()); + Assertions.assertEquals(TEST_RECORD.bytesMapField().size(), mappedRecord.testProtoMessageList().get(0).bytesMapField().size()); + for (final String key : TEST_RECORD.bytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_RECORD.bytesMapField().get(key), mappedRecord.testProtoMessageList().get(0).bytesMapField().get(key)); + } + Assertions.assertEquals(TEST_RECORD.enumMapField(), mappedRecord.testProtoMessageList().get(0).enumMapField()); + + // Test testProtoMessageMap + Assertions.assertEquals(1, mappedRecord.testProtoMessageMap().size()); + Assertions.assertEquals(TEST_RECORD.stringField(), mappedRecord.testProtoMessageMap().get("key1").stringField()); + Assertions.assertEquals(TEST_RECORD.intField(), mappedRecord.testProtoMessageMap().get("key1").intField()); + Assertions.assertEquals(TEST_RECORD.longField(), mappedRecord.testProtoMessageMap().get("key1").longField()); + Assertions.assertEquals(TEST_RECORD.floatField(), mappedRecord.testProtoMessageMap().get("key1").floatField()); + Assertions.assertEquals(TEST_RECORD.doubleField(), mappedRecord.testProtoMessageMap().get("key1").doubleField()); + Assertions.assertEquals(TEST_RECORD.booleanField(), mappedRecord.testProtoMessageMap().get("key1").booleanField()); + Assertions.assertArrayEquals(TEST_RECORD.bytesField(), mappedRecord.testProtoMessageMap().get("key1").bytesField()); + Assertions.assertEquals(TEST_RECORD.enumField(), mappedRecord.testProtoMessageMap().get("key1").enumField()); + Assertions.assertEquals(TEST_RECORD.stringMapField(), mappedRecord.testProtoMessageMap().get("key1").stringMapField()); + Assertions.assertEquals(TEST_RECORD.intMapField(), mappedRecord.testProtoMessageMap().get("key1").intMapField()); + Assertions.assertEquals(TEST_RECORD.longMapField(), mappedRecord.testProtoMessageMap().get("key1").longMapField()); + Assertions.assertEquals(TEST_RECORD.floatMapField(), mappedRecord.testProtoMessageMap().get("key1").floatMapField()); + Assertions.assertEquals(TEST_RECORD.doubleMapField(), mappedRecord.testProtoMessageMap().get("key1").doubleMapField()); + Assertions.assertEquals(TEST_RECORD.boolMapField(), mappedRecord.testProtoMessageMap().get("key1").boolMapField()); + Assertions.assertEquals(TEST_RECORD.bytesMapField().size(), mappedRecord.testProtoMessageMap().get("key1").bytesMapField().size()); + for (final String key : TEST_RECORD.bytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_RECORD.bytesMapField().get(key), mappedRecord.testProtoMessageMap().get("key1").bytesMapField().get(key)); + } + Assertions.assertEquals(TEST_RECORD.enumMapField(), mappedRecord.testProtoMessageMap().get("key1").enumMapField()); + } + + @Test + public void testDeepMappingRecordToProto() { + final TestProtos.DeepTestProtoMessage mappedProto = TestMapper.INSTANCE.mapDeepTestRecordToProto(DEEP_TEST_RECORD); + + // Test testProtoMessage + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringField(), mappedProto.getTestProtoMessagePlain().getStringField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntField(), mappedProto.getTestProtoMessagePlain().getIntField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongField(), mappedProto.getTestProtoMessagePlain().getLongField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatField(), mappedProto.getTestProtoMessagePlain().getFloatField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleField(), mappedProto.getTestProtoMessagePlain().getDoubleField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBooleanField(), mappedProto.getTestProtoMessagePlain().getBooleanField()); + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesField().toByteArray(), mappedProto.getTestProtoMessagePlain().getBytesField().toByteArray()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumField(), mappedProto.getTestProtoMessagePlain().getEnumField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringMapFieldMap(), mappedProto.getTestProtoMessagePlain().getStringMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntMapFieldMap(), mappedProto.getTestProtoMessagePlain().getIntMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongMapFieldMap(), mappedProto.getTestProtoMessagePlain().getLongMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatMapFieldMap(), mappedProto.getTestProtoMessagePlain().getFloatMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleMapFieldMap(), mappedProto.getTestProtoMessagePlain().getDoubleMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBoolMapFieldMap(), mappedProto.getTestProtoMessagePlain().getBoolMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().size(), mappedProto.getTestProtoMessagePlain().getBytesMapFieldMap().size()); + for (final String key : TEST_PROTO_MESSAGE.getBytesMapFieldMap().keySet()) { + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().get(key).toByteArray(), mappedProto.getTestProtoMessagePlain().getBytesMapFieldMap().get(key).toByteArray()); + } + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumMapFieldMap(), mappedProto.getTestProtoMessagePlain().getEnumMapFieldMap()); + + // Test testProtoMessageList + Assertions.assertEquals(1, mappedProto.getTestProtoMessageListList().size()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringField(), mappedProto.getTestProtoMessageListList().get(0).getStringField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntField(), mappedProto.getTestProtoMessageListList().get(0).getIntField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongField(), mappedProto.getTestProtoMessageListList().get(0).getLongField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatField(), mappedProto.getTestProtoMessageListList().get(0).getFloatField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleField(), mappedProto.getTestProtoMessageListList().get(0).getDoubleField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBooleanField(), mappedProto.getTestProtoMessageListList().get(0).getBooleanField()); + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesField().toByteArray(), mappedProto.getTestProtoMessageListList().get(0).getBytesField().toByteArray()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumField(), mappedProto.getTestProtoMessageListList().get(0).getEnumField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getStringMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getIntMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getLongMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getFloatMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getDoubleMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBoolMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getBoolMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().size(), mappedProto.getTestProtoMessageListList().get(0).getBytesMapFieldMap().size()); + for (final String key : TEST_PROTO_MESSAGE.getBytesMapFieldMap().keySet()) { + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().get(key).toByteArray(), mappedProto.getTestProtoMessageListList().get(0).getBytesMapFieldMap().get(key).toByteArray()); + } + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumMapFieldMap(), mappedProto.getTestProtoMessageListList().get(0).getEnumMapFieldMap()); + } + +} diff --git a/mapstruct-spi-protobuf-test-records/src/test/java/de/firehead/mapstruct/spi/protobuf/test/records/SimpleMappingTest.java b/mapstruct-spi-protobuf-test-records/src/test/java/de/firehead/mapstruct/spi/protobuf/test/records/SimpleMappingTest.java new file mode 100644 index 0000000..29ab977 --- /dev/null +++ b/mapstruct-spi-protobuf-test-records/src/test/java/de/firehead/mapstruct/spi/protobuf/test/records/SimpleMappingTest.java @@ -0,0 +1,67 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.test.records; + +import de.firehead.mapstruct.spi.protobuf.test.protos.TestProtos; +import de.firehead.mapstruct.spi.protobuf.test.records.mapper.TestMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class SimpleMappingTest extends AbstractMappingTest { + + @Test + public void testSimpleMappingProtoToPojo() { + final TestRecord mappedRecord = TestMapper.INSTANCE.mapTestProtoToRecord(TEST_PROTO_MESSAGE); + + Assertions.assertEquals(TEST_RECORD.stringField(), mappedRecord.stringField()); + Assertions.assertEquals(TEST_RECORD.intField(), mappedRecord.intField()); + Assertions.assertEquals(TEST_RECORD.longField(), mappedRecord.longField()); + Assertions.assertEquals(TEST_RECORD.floatField(), mappedRecord.floatField()); + Assertions.assertEquals(TEST_RECORD.doubleField(), mappedRecord.doubleField()); + Assertions.assertEquals(TEST_RECORD.booleanField(), mappedRecord.booleanField()); + Assertions.assertArrayEquals(TEST_RECORD.bytesField(), mappedRecord.bytesField()); + Assertions.assertEquals(TEST_RECORD.enumField(), mappedRecord.enumField()); + Assertions.assertEquals(TEST_RECORD.stringMapField(), mappedRecord.stringMapField()); + Assertions.assertEquals(TEST_RECORD.intMapField(), mappedRecord.intMapField()); + Assertions.assertEquals(TEST_RECORD.longMapField(), mappedRecord.longMapField()); + Assertions.assertEquals(TEST_RECORD.floatMapField(), mappedRecord.floatMapField()); + Assertions.assertEquals(TEST_RECORD.doubleMapField(), mappedRecord.doubleMapField()); + Assertions.assertEquals(TEST_RECORD.boolMapField(), mappedRecord.boolMapField()); + Assertions.assertEquals(TEST_RECORD.bytesMapField().size(), mappedRecord.bytesMapField().size()); + for (final String key : TEST_RECORD.bytesMapField().keySet()) { + Assertions.assertArrayEquals(TEST_RECORD.bytesMapField().get(key), mappedRecord.bytesMapField().get(key)); + } + Assertions.assertEquals(TEST_RECORD.enumMapField(), mappedRecord.enumMapField()); + } + + @Test + public void testSimpleMappingPojoToProto() { + final TestProtos.TestProtoMessage mappedProto = TestMapper.INSTANCE.mapTestRecordToProto(TEST_RECORD); + + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringField(), mappedProto.getStringField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntField(), mappedProto.getIntField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongField(), mappedProto.getLongField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatField(), mappedProto.getFloatField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleField(), mappedProto.getDoubleField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBooleanField(), mappedProto.getBooleanField()); + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesField().toByteArray(), mappedProto.getBytesField().toByteArray()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumField(), mappedProto.getEnumField()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getStringMapFieldMap(), mappedProto.getStringMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getIntMapFieldMap(), mappedProto.getIntMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getLongMapFieldMap(), mappedProto.getLongMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getFloatMapFieldMap(), mappedProto.getFloatMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getDoubleMapFieldMap(), mappedProto.getDoubleMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBoolMapFieldMap(), mappedProto.getBoolMapFieldMap()); + Assertions.assertEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().size(), mappedProto.getBytesMapFieldMap().size()); + for (final String key : TEST_PROTO_MESSAGE.getBytesMapFieldMap().keySet()) { + Assertions.assertArrayEquals(TEST_PROTO_MESSAGE.getBytesMapFieldMap().get(key).toByteArray(), mappedProto.getBytesMapFieldMap().get(key).toByteArray()); + } + Assertions.assertEquals(TEST_PROTO_MESSAGE.getEnumMapFieldMap(), mappedProto.getEnumMapFieldMap()); + } + +} diff --git a/mapstruct-spi-protobuf/pom.xml b/mapstruct-spi-protobuf/pom.xml new file mode 100644 index 0000000..0a89437 --- /dev/null +++ b/mapstruct-spi-protobuf/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + + de.firehead + mapstruct-spi-protobuf-parent + 1.0.0-SNAPSHOT + + + mapstruct-spi-protobuf + + + 1.8 + 1.8 + + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + release + + + + org.sonatype.plugins + nexus-staging-maven-plugin + + + + + + + diff --git a/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/accessors/ProtobufAccessorNamingStrategy.java b/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/accessors/ProtobufAccessorNamingStrategy.java new file mode 100644 index 0000000..5993fd2 --- /dev/null +++ b/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/accessors/ProtobufAccessorNamingStrategy.java @@ -0,0 +1,410 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.accessors; + +import org.mapstruct.ap.internal.util.ImmutablesConstants; +import org.mapstruct.ap.internal.util.Nouns; +import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy; +import org.mapstruct.ap.spi.MapStructProcessingEnvironment; +import org.mapstruct.ap.spi.util.IntrospectorUtils; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import java.util.*; + +/** + * Accessor naming strategy that is aware of the special naming conventions used in protobuf-generated classes. + *

+ * Optionally supports Immutables as counterpart, much like + * {@link org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy}, but doesn't require them. + */ +public class ProtobufAccessorNamingStrategy extends DefaultAccessorNamingStrategy { + + /** + * An expected interface used to find out if a type is a protobuf message type (or a builder of one). + */ + protected TypeMirror protobufMarkerInterface; + + /** + * Whether Immutables is found on the classpath. If true, we assume that the counterpart for protobuf mapping are + * Immutables-based data structures. + */ + protected boolean isImmutables; + + @Override + public void init(MapStructProcessingEnvironment aProcessingEnvironment) { + super.init(aProcessingEnvironment); + + final TypeElement typeElement = elementUtils.getTypeElement("com.google.protobuf.MessageLiteOrBuilder"); + if (typeElement != null) { + protobufMarkerInterface = typeElement.asType(); + } + + isImmutables = elementUtils.getTypeElement(ImmutablesConstants.IMMUTABLE_FQN) != null; + } + + /** + * Recursively checks whether the given {@link TypeElement} is a protobuf message type (or a builder of one). + * + * @param aType the type to check + * @return true if it's a protobuf message type, false otherwise + */ + private boolean isProtobufGeneratedMessage(TypeElement aType) { + // Check the interfaces first + for (final TypeMirror implementedInterface : aType.getInterfaces()) { + if (implementedInterface.toString().startsWith("com.google.protobuf.MessageLiteOrBuilder")) { + return true; + } else if (implementedInterface instanceof DeclaredType) { + if (isProtobufGeneratedMessage((TypeElement) ((DeclaredType) implementedInterface).asElement())) { + return true; + } + } + } + + // If no match was found, check the superclasses' interfaces recursively + final TypeMirror superType = aType.getSuperclass(); + if (superType instanceof DeclaredType) { + return isProtobufGeneratedMessage((TypeElement) ((DeclaredType) superType).asElement()); + } + + return false; + } + + /** + * These are purely internal methods generated into protobuf classes. They can be entirely ignored when mapping. + */ + private static final Set INTERNAL_PROTOBUF_METHODS = new HashSet<>(Arrays.asList( + "clear", + "clearField", + "clearOneof", + "getAllFields", + "getAllFieldsMutable", + "getAllFieldsRaw", + "getDefaultInstance", + "getDefaultInstanceForType", + "getDescriptor", + "getDescriptorForType", + "getField", + "getFieldRaw", + "getInitializationErrorString", + "getMemoizedSerializedSize", + "getOneofFieldDescriptor", + "getParserForType", + "getRepeatedField", + "getRepeatedFieldCount", + "getSerializedSize", + "getSerializingExceptionMessage", + "getUnknownFields", + "isInitialized", + "mergeFrom", + "mergeUnknownFields", + "newBuilder", + "newBuilderForType", + "parseDelimitedFrom", + "parseFrom", + "setRepeatedField", + "setUnknownFields")); + + /** + * Checks whether the given method is an internal protobuf method. + * + * @param aMethod the method to check + * @return true if it is an internal protobuf method, false otherwise + */ + private boolean isInternalProtobufMethod(ExecutableElement aMethod) { + return INTERNAL_PROTOBUF_METHODS.contains(aMethod.getSimpleName().toString()); + } + + /** + * Checks whether the given {@link ExecutableElement} is a method from a generated protobuf message class. + * + * @param aMethod the method to check + * @return true if it's from a protobuf class, false otherwise + */ + private boolean isProtobufMethod(ExecutableElement aMethod) { + return aMethod.getKind() == ElementKind.METHOD + && aMethod.getEnclosingElement() != null + && protobufMarkerInterface != null + && typeUtils.isAssignable(aMethod.getEnclosingElement().asType(), protobufMarkerInterface); + } + + /** + * Checks whether the given {@link TypeElement} has a method of the provided name. + * + * @param aType the type to check + * @param aMethodName the method to check for + * @return true if a matching method is found, false otherwise + */ + private boolean doesHaveMethod(TypeElement aType, String aMethodName) { + return aType.getEnclosedElements() + .stream() + .anyMatch(e -> e.getSimpleName().toString().equals(aMethodName)); + } + + /** + * Protobuf message fields often have several "auxiliary accessor methods" generated for them, which relate to + * a particular field (and thus have its name as part of their name). Which methods exist depends on the type + * of the field, but for the purpose of mapping we must ignore all of these as they all relate to the same field. + * + * @param aMethod the method to check + * @return true if it is an auxiliary accessor for a field of a protobuf message, false otherwise + */ + private boolean isAuxiliaryProtobufPropertyAccessor(ExecutableElement aMethod) { + if (!isProtobufMethod(aMethod)) { + return false; + } + final String methodName = aMethod.getSimpleName().toString(); + + if (methodName.startsWith("getOneOf") && methodName.endsWith("Case")) { + return true; + } + + for (String prefixCandidate : Arrays.asList("clear", "merge", "mutable", "putAll", "remove")) { + if (methodName.startsWith(prefixCandidate)) { + String expectedAccessor = "get" + methodName.substring(prefixCandidate.length()); + if (doesHaveMethod((TypeElement) aMethod.getEnclosingElement(), expectedAccessor)) { + return true; + } + } + } + + for (String suffixCandidate : Arrays.asList("Bytes", "Count", "Map", "Value", "ValueList")) { + if (methodName.endsWith(suffixCandidate)) { + final String expectedAccessor = methodName.substring(0, methodName.length() - suffixCandidate.length()); + if (doesHaveMethod((TypeElement) aMethod.getEnclosingElement(), expectedAccessor)) { + return true; + } + } + } + + return false; + } + + /** + * Checks if a given type is a list (Java or protobuf ProtocolStringList) type. + * + * @param aType the type to check + * @return true if it is a list, false otherwise + */ + private boolean isListType(TypeMirror aType) { + final String typeString = aType.toString(); + return typeString.startsWith(List.class.getCanonicalName()) + || typeString.startsWith("com.google.protobuf.ProtocolStringList"); + } + + /** + * Checks whether a given type is a {@link Map}. + * + * @param aType the type to check + * @return true if it is a map, false otherwise + */ + private boolean isMapType(TypeMirror aType) { + return aType.toString().startsWith(Map.class.getCanonicalName()); + } + + /** + * Checks if a given type is a {@link java.util.Map.Entry}. + * + * @param aType the type to check + * @return true if it is an entry, false otherwise + */ + private boolean isMapEntryType(TypeMirror aType) { + return aType.toString().startsWith(Map.Entry.class.getCanonicalName()); + } + + /** + * Checks whether a given method is an accessor to a mutable map. + * + * @param aMethod the method to check + * @return true if it is a mutable map accessor, false otherwise + */ + private boolean isMutableMapAccessor(ExecutableElement aMethod) { + return aMethod.getSimpleName().toString().startsWith("getMutable") && isMapType(aMethod.getReturnType()); + } + + /** + * Checks whether a given method is an accessor to an immutable map. + * + * @param aMethod the method to check + * @return true if it is an immutable map accessor, false otherwise + */ + private boolean isImmutableMapAccessor(ExecutableElement aMethod) { + final String methodName = aMethod.getSimpleName().toString(); + return methodName.startsWith("get") + && !methodName.startsWith("getMutable") + && isMapType(aMethod.getReturnType()) + && (!methodName.endsWith("Map") || doesHaveMethod((TypeElement) aMethod.getEnclosingElement(), methodName + "Map")); + } + + /** + * Checks whether a given method is a getter for a list. + * + * @param aMethod the method to check + * @return true if it is a list getter, false otherwise + */ + private boolean isListGetter(ExecutableElement aMethod) { + return aMethod.getSimpleName().toString().startsWith("get") && isListType(aMethod.getReturnType()); + } + + /** + * Checks whether a given method is a setter for a list. + * + * @param aMethod the method to check + * @return true if it is a list setter, false otherwise + */ + private boolean isListSetter(ExecutableElement aMethod) { + return aMethod.getSimpleName().toString().startsWith("set") + && aMethod.getParameters().size() == 1 + && isListType(aMethod.getParameters().get(0).asType()); + } + + /** + * Checks whether a given method is a putter for a map value. + * + * @param aMethod the method to check + * @return true if it is a map value putter, false otherwise + */ + private boolean isMapValuePutter(ExecutableElement aMethod) { + return aMethod.getSimpleName().toString().startsWith("put") && aMethod.getParameters().size() == 2; + } + + /** + * Checks whether a given method is a putter for a map entry. + * + * @param aMethod the method to check + * @return true if it is a map entry putter, false otherwise + */ + private boolean isMapEntryPutter(ExecutableElement aMethod) { + return aMethod.getSimpleName().toString().startsWith("put") + && aMethod.getParameters().size() == 1 + && isMapEntryType(aMethod.getParameters().get(0).asType()); + } + + /** + * Checks whether a given method is a putAll for a map entry. + * + * @param aMethod the method to check + * @return true if it is a map entry putAll, false otherwise + */ + private boolean isMapEntryPutAll(ExecutableElement aMethod) { + return aMethod.getSimpleName().toString().startsWith("putAll") + && aMethod.getParameters().size() == 1 + && isMapType(aMethod.getParameters().get(0).asType()); + } + + @Override + public boolean isGetterMethod(ExecutableElement aMethod) { + if (isInternalProtobufMethod(aMethod) || isAuxiliaryProtobufPropertyAccessor(aMethod)) { + return false; + } + + final String methodName = aMethod.getSimpleName().toString(); + if (methodName.endsWith("OrBuilder") || methodName.endsWith("BuilderList")) { + return false; + } + + if (isMutableMapAccessor(aMethod)) { + return true; + } else if (isImmutableMapAccessor(aMethod)) { + // For protobuf builder types we want to use the mutable map accessor + final TypeElement type = (TypeElement) aMethod.getEnclosingElement(); + return !type.getSuperclass().toString().startsWith("com.google.protobuf.GeneratedMessageV3.Builder"); + } + + return super.isGetterMethod(aMethod); + } + + @Override + public boolean isSetterMethod(ExecutableElement aMethod) { + if (isInternalProtobufMethod(aMethod) || isAuxiliaryProtobufPropertyAccessor(aMethod)) { + return false; + } + if (isMapEntryPutAll(aMethod) || isMapEntryPutter(aMethod) || isMapValuePutter(aMethod)) { + return false; + } + + return super.isSetterMethod(aMethod); + } + + @Override + protected boolean isFluentSetter(ExecutableElement aMethod) { + // Equivalent from ImmutablesAccessorNamingStrategy + if (isImmutables && aMethod.getSimpleName().toString().equals("from")) { + return false; + } + + if (isInternalProtobufMethod(aMethod) || isAuxiliaryProtobufPropertyAccessor(aMethod)) { + return false; + } + + final String methodName = aMethod.getSimpleName().toString(); + if (methodName.startsWith("get")) { + return false; + } + + return super.isFluentSetter(aMethod); + } + + @Override + public boolean isAdderMethod(ExecutableElement aMethod) { + if (isInternalProtobufMethod(aMethod) || isAuxiliaryProtobufPropertyAccessor(aMethod)) { + return false; + } + + return super.isAdderMethod(aMethod); + } + + @Override + public boolean isPresenceCheckMethod(ExecutableElement aMethod) { + if (isInternalProtobufMethod(aMethod) || isAuxiliaryProtobufPropertyAccessor(aMethod)) { + return false; + } + + return super.isPresenceCheckMethod(aMethod); + } + + @Override + public String getElementName(ExecutableElement aMethod) { + final String methodName = super.getElementName(aMethod); + if (isProtobufMethod(aMethod)) { + return Nouns.singularize(methodName); + } else { + return methodName; + } + } + + @Override + public String getPropertyName(ExecutableElement aMethod) { + // Protobuf list/map accessors have special naming conventions that we must "reverse" here in order to get the + // actual property name + + final Element receiver = aMethod.getEnclosingElement(); + if (receiver != null && (receiver.getKind() == ElementKind.CLASS || receiver.getKind() == ElementKind.INTERFACE)) { + final TypeElement type = (TypeElement) receiver; + if (isProtobufGeneratedMessage(type)) { + final String methodName = aMethod.getSimpleName().toString(); + + if (isListGetter(aMethod) || isListSetter(aMethod)) { + return IntrospectorUtils.decapitalize( + methodName.substring("get" .length(), methodName.length() - "List" .length())); + } else if (isMutableMapAccessor(aMethod)) { + return IntrospectorUtils.decapitalize(methodName.substring("getMutable" .length())); + } else if (isImmutableMapAccessor(aMethod)) { + return IntrospectorUtils.decapitalize(methodName.substring("get" .length())); + } + } + } + + return super.getPropertyName(aMethod); + } + +} diff --git a/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/builderprovider/DelegatingBuilderProvider.java b/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/builderprovider/DelegatingBuilderProvider.java new file mode 100644 index 0000000..947b49e --- /dev/null +++ b/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/builderprovider/DelegatingBuilderProvider.java @@ -0,0 +1,33 @@ +package de.firehead.mapstruct.spi.protobuf.builderprovider; + +import org.mapstruct.ap.internal.util.ImmutablesConstants; +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.DefaultBuilderProvider; +import org.mapstruct.ap.spi.MapStructProcessingEnvironment; + +import javax.lang.model.type.TypeMirror; + +public class DelegatingBuilderProvider implements BuilderProvider { + + protected BuilderProvider delegate; + + @Override + public void init(MapStructProcessingEnvironment aProcessingEnvironment) { + if (delegate == null) { + if (aProcessingEnvironment.getElementUtils().getTypeElement(ImmutablesConstants.IMMUTABLE_FQN) != null) { + delegate = new ImmutablesBuilderProvider(); + } else { + delegate = new DefaultBuilderProvider(); + } + } + + delegate.init(aProcessingEnvironment); + } + + @Override + public BuilderInfo findBuilderInfo(TypeMirror aType) { + return delegate.findBuilderInfo(aType); + } + +} diff --git a/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/builderprovider/ImmutablesBuilderProvider.java b/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/builderprovider/ImmutablesBuilderProvider.java new file mode 100644 index 0000000..a6ccb24 --- /dev/null +++ b/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/builderprovider/ImmutablesBuilderProvider.java @@ -0,0 +1,326 @@ +/* + * Original Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package de.firehead.mapstruct.spi.protobuf.builderprovider; + +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.DefaultBuilderProvider; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; +import org.mapstruct.util.Experimental; + +import javax.lang.model.element.*; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Builder provider for Immutables. A custom provider is needed because Immutables creates an implementation of an + * interface and that implementation has the builder. This implementation would try to find the type created by + * Immutables and would look for the builder in it. Only types annotated with the + * {@code org.immutables.value.Value.Immutable} are considered for this discovery. + * + * @author Filip Hrisafov + */ +// This ImmutablesBuilderProvider was taken from the following Pull Request: +// https://github.com/mapstruct/mapstruct/pull/2219 +// It fixes incompatibilities of the Mapstruct default ImmutablesBuilderProvider with inner classes and @Value.Enclosing +// The PR unfortunately has not been merged to official Mapstruct due to the maintainer not having the time to resolve +// some obscure problems with executing the associated tests within the PR in an ECJ JDK8 environment. I don't care +// about running those tests under ECJ, I want the fix. +// +@Experimental("The Immutables builder provider might change in a subsequent release") +public class ImmutablesBuilderProvider extends DefaultBuilderProvider { + + private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile("^javax?\\..*"); + + private static final String IMMUTABLE_FQN = "org.immutables.value.Value.Immutable"; + + private static final String VALUE_ENCLOSING_FQN = "org.immutables.value.Value.Enclosing"; + + private static final String VALUE_STYLE_FQN = "org.immutables.value.Value.Style"; + + @Override + protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if (name.length() == 0 || JAVA_JAVAX_PACKAGE.matcher(name).matches()) { + return null; + } + TypeElement immutableAnnotation = elementUtils.getTypeElement(IMMUTABLE_FQN); + if (immutableAnnotation != null) { + BuilderInfo info = findBuilderInfoForImmutables(typeElement, immutableAnnotation); + if (info != null) { + return info; + } + } + + return super.findBuilderInfo(typeElement); + } + + /** + * Finds the builder info for the given type or returns null if not found. + * + * @param targetTypeElement a type which may require a builder + * @param immutableAnnotation type of the immutables annotation we're looking for + * @return BuilderInfo or null if none found + * @throws TypeHierarchyErroneousException if unable to process in this round + */ + protected BuilderInfo findBuilderInfoForImmutables(TypeElement targetTypeElement, TypeElement immutableAnnotation) { + + // we can avoid any reflection/type mirror inspection of the annotations + // if the type we're dealing with now has a builder method on it. + BuilderInfo fromType = super.findBuilderInfo(targetTypeElement); + if (fromType != null) { + return fromType; + } + + // if there's no build method on the type, then look for the immutable annotation + // since it may be accompanied by a Value.Style which provides info on the + // name of the generated builder + return findTypeWithImmutableAnnotation(targetTypeElement, immutableAnnotation.asType()).map(typeElement -> { + TypeElement immutableElement = asImmutableElement(typeElement); + if (immutableElement != null) { + return super.findBuilderInfo(immutableElement); + } else { + // Immutables processor has not run yet. Trigger a postpone to the next round for MapStruct + throw new TypeHierarchyErroneousException(typeElement); + } + + }).orElse(null); + } + + /** + * This method looks for the Value.Immutable on the targetTypeElement in the following order: + *

+ * 1) directly on the element itself 2) on an interface in the same package that the element implements 3) on the + * superclass for the element + *

+ * We're looking for the immutable annotation since there could be additional annotations there which affect the + * name of the generated immutable builder. + * + * @param targetTypeElement element to analyze for the immutables annotation + * @param immutableAnnotationTypeMirror type of the annotation we're looking for + * @return first found element with the type or empty + */ + protected Optional findTypeWithImmutableAnnotation(TypeElement targetTypeElement, + TypeMirror immutableAnnotationTypeMirror) { + Predicate hasImmutableAnnotation = element -> elementUtils.getAllAnnotationMirrors(element) + .stream() + .anyMatch(am -> typeUtils.isSameType(am.getAnnotationType(), immutableAnnotationTypeMirror)); + + // 1. If the TypeElement has the immutable annotation + // then use the targetTypeElement to find the builder + // + if (hasImmutableAnnotation.test(targetTypeElement)) { + return Optional.of(targetTypeElement); + } + + String targetPackage = findPackage(targetTypeElement); + return Stream.concat( + // 2. we'll check interfaces second + targetTypeElement.getInterfaces().stream(), + // 3. if not found on an interface, check the super class + Stream.of(targetTypeElement.getSuperclass())) + .filter(intf -> intf.getKind() == TypeKind.DECLARED) + .map(DeclaredType.class::cast) + .map(DeclaredType::asElement) + .map(TypeElement.class::cast) + .filter(intf -> targetPackage.equals(findPackage(intf))) + .filter(hasImmutableAnnotation) + .findFirst(); + } + + /** + * @param typeElement element that has the Value.Immutable annotation + * @return type that should have the builder or null if none found + */ + protected TypeElement asImmutableElement(TypeElement typeElement) { + // the java package that the generated builder is in + String packageQualifier; + // optional enclosing qualifier if the generated builder is an inner class + // the value.enclosing annotation customizes this qualifier + String enclosingQualifier = ""; + // name of the builder, defaults to Immutable + non-abstract simple type name + // the style annotation customizes the builder + String builderName; + + AnnotationMirror style = null; + + Element enclosingElement = typeElement.getEnclosingElement(); + while (enclosingElement.getKind() != ElementKind.PACKAGE) { + // look for the first enclosing element with Value.Enclosing + if (hasValueEnclosingAnnotation(enclosingElement) && enclosingQualifier.isEmpty()) { + style = findStyle(enclosingElement); + if (style != null) { + enclosingQualifier = enclosingQualifierFromStyle(style, enclosingElement); + } else { + enclosingQualifier = "Immutable" + enclosingElement.getSimpleName(); + } + } + enclosingElement = enclosingElement.getEnclosingElement(); + } + packageQualifier = ((PackageElement) enclosingElement).getQualifiedName().toString(); + + builderName = builderFromStyle(style, typeElement, !enclosingQualifier.isEmpty()); + + // check for @Value.Enclosing + // ::= + String bqn = Stream.of(packageQualifier, enclosingQualifier, builderName) + .filter(segment -> !segment.isEmpty()) + .collect(Collectors.joining(".")); + + return elementUtils.getTypeElement(bqn); + } + + protected String enclosingQualifierFromStyle(AnnotationMirror style, Element element) { + // Value.Style influences the qualifier name through the typeAbstract, typeImmutable, and typeImmutableEnclosing + return immutableNameFromStylePattern(nameWithoutAbstractPrefix(style, element), + getSingleAnnotationValue("typeImmutable", style).orElseGet( + () -> getSingleAnnotationValue("typeImmutableEnclosing", style).orElse("Immutable*"))); + } + + protected String builderFromStyle(AnnotationMirror style, TypeElement element, boolean valueEnclosingFound) { + assert element != null; + + // if we're given a style, then use it. If not, then + // keep walking up until we find one or run out of enclosing elements + // If we don't find a style, then the naming behavior is driven + // by defaults as documented by the immutables annotations + AnnotationMirror resolvedStyle = Optional.ofNullable(style).orElseGet(() -> { + Element currentElement = element; + AnnotationMirror found = null; + while (currentElement != null && found == null) { + found = findStyle(currentElement); + currentElement = currentElement.getEnclosingElement(); + } + return found; + }); + + if (resolvedStyle == null && !valueEnclosingFound) { + // no @Value.Style found, use the default behavior from immutables + // no @Value.Enclosing + return "Immutable" + element.getSimpleName(); + } + + if (resolvedStyle == null) { + // no @Value.Style found, but there was a @Value.Enclosing, use the default behavior + return element.getSimpleName().toString(); + } + + // style is present, see what it has to say about the names + return immutableNameFromStylePattern( + // trim the abstract portion from the name (defaults to "Abstract*") + nameWithoutAbstractPrefix(resolvedStyle, element), + // use the value from typeImmutable + getSingleAnnotationValue("typeImmutable", resolvedStyle) + // Note: typeImmutable is defined as having a default value so we shouldn't + // hit this orElse. Leaving this instead of throwing since + // it's a reasonable default (and currently matches their docs) + .orElse("Immutable*")); + } + + protected String nameWithoutAbstractPrefix(AnnotationMirror style, Element element) { + final String simpleNameOfElement = element.getSimpleName().toString(); + return getTypeAbstractValues(style).stream() + .filter(p -> simpleNameOfElement.startsWith(p.substring(0, p.length() - 1))) + .map(p -> simpleNameOfElement.substring(p.length() - 1)) + .findFirst() + .orElseGet(() -> element.getSimpleName().toString()); + } + + protected Optional getSingleAnnotationValue(String annotationKey, AnnotationMirror style) { + return elementUtils.getElementValuesWithDefaults(style) + .entrySet() + .stream() + .filter(entry -> annotationKey.equals(entry.getKey().getSimpleName().toString())) + .map(Map.Entry::getValue) + .map(value -> value.accept(new SimpleAnnotationValueVisitor8() { + + @Override + public String visitString(String s, Void unused) { + return s; + } + }, null)) + .findFirst(); + } + + protected List getTypeAbstractValues(AnnotationMirror styleOrNull) { + + // this is the pattern if there is no style or if typeAbstract value not found + Supplier> noStyleOrMissingDefault = () -> Collections.singletonList("Abstract*"); + + return Optional.ofNullable(styleOrNull) + .map(style -> elementUtils.getElementValuesWithDefaults(style) + .entrySet() + .stream() + .filter(entry -> "typeAbstract".equals(entry.getKey().getSimpleName().toString())) + .map(Map.Entry::getValue) + .map(value -> value.accept(new SimpleAnnotationValueVisitor8, Void>() { + + @Override + public List visitArray(List values, Void unused) { + return values.stream() + .map(val -> val.accept(new SimpleAnnotationValueVisitor8() { + + @Override + public String visitString(String s, Void param) { + return s; + } + }, null)) + .collect(Collectors.toList()); + } + }, null)) + .findFirst() + .orElseGet(noStyleOrMissingDefault)) + .orElseGet(noStyleOrMissingDefault); + } + + protected boolean hasValueEnclosingAnnotation(Element enclosingElement) { + TypeElement typeElement = elementUtils.getTypeElement(VALUE_ENCLOSING_FQN); + + return Optional.ofNullable(typeElement) + .map(Element::asType) + .map(mirror -> elementUtils.getAllAnnotationMirrors(enclosingElement) + .stream() + .anyMatch(am -> typeUtils.isSameType(am.getAnnotationType(), mirror))) + .orElse(Boolean.FALSE); + } + + protected AnnotationMirror findStyle(Element element) { + TypeElement styleTypeElement = elementUtils.getTypeElement(VALUE_STYLE_FQN); + if (styleTypeElement == null) { + return null; + } + TypeMirror styleAnnotation = styleTypeElement.asType(); + return elementUtils.getAllAnnotationMirrors(element) + .stream() + .filter(am -> typeUtils.isSameType(am.getAnnotationType(), styleAnnotation)) + .findFirst() + .orElse(null); + } + + protected String immutableNameFromStylePattern(String simpleNameOfElement, String typeImmutablePattern) { + String fixedPattern = typeImmutablePattern.substring(0, typeImmutablePattern.length() - 1); + return fixedPattern + simpleNameOfElement; + } + + protected String findPackage(Element element) { + Element current = element; + while (current.getKind() != ElementKind.PACKAGE) { + current = current.getEnclosingElement(); + } + return current.getSimpleName().toString(); + } + +} diff --git a/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/enums/ProtobufEnumMappingStrategy.java b/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/enums/ProtobufEnumMappingStrategy.java new file mode 100644 index 0000000..30f2db9 --- /dev/null +++ b/mapstruct-spi-protobuf/src/main/java/de/firehead/mapstruct/spi/protobuf/enums/ProtobufEnumMappingStrategy.java @@ -0,0 +1,118 @@ +/* mapstruct-spi-protobuf + * + * Copyright (C) 2024 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +package de.firehead.mapstruct.spi.protobuf.enums; + +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.spi.DefaultEnumMappingStrategy; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +/** + * Enum mapping strategy implementing the common enum value name mapping suggestion from Google. + * + * @author Rene Schneider + */ +public class ProtobufEnumMappingStrategy extends DefaultEnumMappingStrategy { + + /** + * The postfix used for the default value (if enum is unset). + */ + private static final String DEFAULT_ENUM_POSTFIX = "UNSPECIFIED"; + + @Override + public String getDefaultNullEnumConstant(TypeElement anEnumType) { + if (isProtobufEnum(anEnumType)) { + final String prefix = upperCamelToUpperUnderscore(anEnumType.getSimpleName().toString()); + return prefix + "_" + DEFAULT_ENUM_POSTFIX; + } else { + return null; + } + } + + /** + * The enum value if an unrecognizable input is read from a serialized proto blob. + */ + private static final String UNPARSEABLE_ENUM_CONSTANT = "UNRECOGNIZED"; + + @Override + public String getEnumConstant(TypeElement anEnumType, String aSourceEnumValue) { + if (isProtobufEnum(anEnumType)) { + if (aSourceEnumValue == null) { + return getDefaultNullEnumConstant(anEnumType); + } + final String enumWithoutNamePrefix = removeEnumNamePrefixFromValueIfPossible(anEnumType, aSourceEnumValue); + if (UNPARSEABLE_ENUM_CONSTANT.equals(aSourceEnumValue) + || DEFAULT_ENUM_POSTFIX.equals(enumWithoutNamePrefix)) { + return MappingConstants.NULL; + } + return enumWithoutNamePrefix; + } + + return aSourceEnumValue; + } + + /** + * Removes the prefix expected according to Google's enum value prefixing rules from the provided enum value, if it + * is found to be present. If it's not present, this method will return the original value. + * + * @param anEnumType the enum type from which to generate the corresponding prefix + * @param anEnumValue the enum value to modify + * @return the modified value or the original value if the expected prefix was not found + */ + private String removeEnumNamePrefixFromValueIfPossible(TypeElement anEnumType, String anEnumValue) { + final String prefix = upperCamelToUpperUnderscore(anEnumType.getSimpleName().toString()); + return anEnumValue.replace(prefix + "_", ""); + } + + /** + * The interface signaling protobuf enums. + */ + private static final String PROTOBUF_ENUM_INTERFACE_NAME = "com.google.protobuf.ProtocolMessageEnum"; + + /** + * The interface signaling protobuf lite enums. + */ + private static final String PROTOBUF_LITE_ENUM_INTERFACE_NAME = "com.google.protobuf.Internal.EnumLite"; + + /** + * Checks whether the given {@link TypeElement} is a protobuf enum. + * + * @param anEnumType the enum type to check + * @return true if it's a protobuf enum, false otherwise + */ + private boolean isProtobufEnum(TypeElement anEnumType) { + for (final TypeMirror implementedInterface : anEnumType.getInterfaces()) { + final String implementedInterfaceName = implementedInterface.toString(); + if (PROTOBUF_ENUM_INTERFACE_NAME.equals(implementedInterfaceName) + || PROTOBUF_LITE_ENUM_INTERFACE_NAME.equals(implementedInterfaceName)) { + return true; + } + } + + return false; + } + + /** + * Converts upper camel case to upper underscore case. + * + * @param anInput the upper camel case input + * @return the upper underscore output + */ + private static String upperCamelToUpperUnderscore(String anInput) { + // Regular expression to find uppercase letters + final String regex = "([a-z])([A-Z]+)"; + // Replacement pattern to add underscore before uppercase letters + final String replacement = "$1_$2"; + + final String result = anInput.replaceAll(regex, replacement); + + return result.toUpperCase(); + } + +} diff --git a/mapstruct-spi-protobuf/src/main/resources/META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy b/mapstruct-spi-protobuf/src/main/resources/META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy new file mode 100644 index 0000000..887037b --- /dev/null +++ b/mapstruct-spi-protobuf/src/main/resources/META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy @@ -0,0 +1 @@ +de.firehead.mapstruct.spi.protobuf.accessors.ProtobufAccessorNamingStrategy \ No newline at end of file diff --git a/mapstruct-spi-protobuf/src/main/resources/META-INF/services/org.mapstruct.ap.spi.BuilderProvider b/mapstruct-spi-protobuf/src/main/resources/META-INF/services/org.mapstruct.ap.spi.BuilderProvider new file mode 100644 index 0000000..d70a9f0 --- /dev/null +++ b/mapstruct-spi-protobuf/src/main/resources/META-INF/services/org.mapstruct.ap.spi.BuilderProvider @@ -0,0 +1 @@ +de.firehead.mapstruct.spi.protobuf.builderprovider.DelegatingBuilderProvider \ No newline at end of file diff --git a/mapstruct-spi-protobuf/src/main/resources/META-INF/services/org.mapstruct.ap.spi.EnumMappingStrategy b/mapstruct-spi-protobuf/src/main/resources/META-INF/services/org.mapstruct.ap.spi.EnumMappingStrategy new file mode 100644 index 0000000..93d7e07 --- /dev/null +++ b/mapstruct-spi-protobuf/src/main/resources/META-INF/services/org.mapstruct.ap.spi.EnumMappingStrategy @@ -0,0 +1 @@ +de.firehead.mapstruct.spi.protobuf.enums.ProtobufEnumMappingStrategy \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..007ec43 --- /dev/null +++ b/pom.xml @@ -0,0 +1,248 @@ + + + 4.0.0 + + de.firehead + mapstruct-spi-protobuf-parent + 1.0.0-SNAPSHOT + pom + + Mapstruct SPI extension for mapping between protobuf and org.immutables + This Mapstruct SPI extension allows to perform seamless Mapstruct mapper generation to map between + protobuf and an org.immutables based data structures. + + https://github.com/S1artie/mapstruct-spi-protobuf + + + + MIT License + http://opensource.org/licenses/MIT + + + + + + Rene Schneider + rene@firehead.de + firehead.de + https://github.com/S1artie + + + + + https://github.com/S1artie/mapstruct-spi-protobuf + scm:git:git@github.com:S1artie/mapstruct-spi-protobuf.git + scm:git:ssh://github.com:S1artie/mapstruct-spi-protobuf.git + + + + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + + + mapstruct-spi-protobuf + mapstruct-spi-protobuf-test-immutables + mapstruct-spi-protobuf-test-pojo + mapstruct-spi-protobuf-test-protos + mapstruct-spi-protobuf-test-records + + + + UTF-8 + 17 + 17 + + 1.5.5.Final + 2.0.13 + 3.25.4 + 1.65.0 + 2.10.1 + 5.11.0-M2 + + + + + + + de.firehead + mapstruct-spi-protobuf-test-protos + ${project.version} + + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + org.immutables + value + ${immutables.version} + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + + + + dev.cookiecode + another-protobuf-maven-plugin + 2.1.0 + + + + test-compile + test-compile-custom + + generate-sources + + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + grpc-java + + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.7.0 + true + + ossrh + https://s01.oss.sonatype.org/ + false + + + + + + + + + release + + + + org.codehaus.mojo + flatten-maven-plugin + 1.6.0 + + ossrh + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + false + + + + + + +