diff --git a/src/main/resources/inspectionDescriptions/SubclassMappingSourceSubclassMappedMoreThanOnceInspection.html b/src/main/resources/inspectionDescriptions/SubclassMappingSourceSubclassMappedMoreThanOnceInspection.html
new file mode 100644
index 00000000..8091be39
--- /dev/null
+++ b/src/main/resources/inspectionDescriptions/SubclassMappingSourceSubclassMappedMoreThanOnceInspection.html
@@ -0,0 +1,31 @@
+
+
+
+ This inspection reports when a subclass is mapped more than once
+
+
+
+//wrong
+@Mapper
+public interface FruitMapper {
+ @SubclassMapping( source = AppleDto.class, target = Apple.class )
+ @SubclassMapping( source = AppleDto.class, target = Fruit.class )
+ @SubclassMapping( source = BananaDto.class, target = Banana.class )
+ Fruit map( FruitDto source );
+}
+
+
+
+
+//correct
+@Mapper
+public interface FruitMapper {
+ @SubclassMapping( source = AppleDto.class, target = Apple.class )
+ @SubclassMapping( source = BananaDto.class, target = Banana.class )
+ Fruit map( FruitDto source );
+}
+
+
+
+
+
diff --git a/src/main/resources/inspectionDescriptions/ValueMappingSourceMappedMoreThanOnceInspection.html b/src/main/resources/inspectionDescriptions/ValueMappingSourceMappedMoreThanOnceInspection.html
new file mode 100644
index 00000000..d7e75c70
--- /dev/null
+++ b/src/main/resources/inspectionDescriptions/ValueMappingSourceMappedMoreThanOnceInspection.html
@@ -0,0 +1,33 @@
+
+
+
+ This inspection reports when a value mapping source property is mapped more than once
+
+
+
+//wrong
+@Mapper
+public interface OrderMapper {
+
+ @ValueMapping(target = "SPECIAL", source = "EXTRA")
+ @ValueMapping(target = "DEFAULT", source = "EXTRA")
+ @ValueMapping(target = "DEFAULT", source = "NORMAL")
+ ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
+}
+
+
+
+
+//correct
+@Mapper
+public interface OrderMapper {
+
+ @ValueMapping(target = "SPECIAL", source = "EXTRA")
+ @ValueMapping(target = "DEFAULT", source = "NORMAL")
+ Fruit map( FruitDto source );
+}
+
+
+
+
+
diff --git a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties
index 9a5608bd..9ea7e638 100644
--- a/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties
+++ b/src/main/resources/org/mapstruct/intellij/messages/MapStructBundle.properties
@@ -29,6 +29,10 @@ inspection.target.property.mapped.more.than.once=Target property ''{0}'' must no
inspection.target.property.mapped.more.than.once.title=Target properties must not be mapped more than once.
inspection.source.property.this.used=''.'' should not be used as a source.
inspection.mapstruct.references=Injected mapstruct references
+inspection.subclass.mapping.source.subclass.already.defined=Subclass ''{0}'' is already defined as a source.
+inspection.subclass.mapping.source.subclass.already.defined.title=Subclass source property is already defined as a source.
+inspection.value.mapping.source.mapped.more.than.once=Value mapping source property ''{0}'' must not be mapped more than once.
+inspection.value.mapping.source.mapped.more.than.once.title=Value mapping source property must not be mapped more than once.
intention.add.ignore.all.unmapped.target.properties=Add ignore all unmapped target properties
intention.add.ignore.unmapped.target.property=Add ignore unmapped target property
intention.add.unmapped.target.property=Add unmapped target property
@@ -40,6 +44,7 @@ intention.wrong.map.mapping.map.type.raw=Add type to Map for mapping Map to Bean
intention.wrong.map.mapping.map.key=Use Map with key of type String for mapping Map to Bean
intention.remove.annotation=Remove {0} annotation
intention.change.target.property=Change target property
+intention.change.source.property=Change source property
intention.replace.source.property=Replace source ''.'' with ''{0}''
plugin.settings.title=MapStruct
plugin.settings.quickFix.title=Quick fix properties
diff --git a/src/test/java/org/mapstruct/intellij/inspection/SubclassMappingSourceSubclassMappedMoreThanOnceInspectionTest.java b/src/test/java/org/mapstruct/intellij/inspection/SubclassMappingSourceSubclassMappedMoreThanOnceInspectionTest.java
new file mode 100644
index 00000000..c87168cd
--- /dev/null
+++ b/src/test/java/org/mapstruct/intellij/inspection/SubclassMappingSourceSubclassMappedMoreThanOnceInspectionTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.intellij.inspection;
+
+import java.util.List;
+
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.codeInspection.LocalInspectionTool;
+import com.intellij.openapi.editor.Caret;
+import org.jetbrains.annotations.NotNull;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author hduelme
+ */
+public class SubclassMappingSourceSubclassMappedMoreThanOnceInspectionTest extends BaseInspectionTest {
+
+ @Override
+ protected @NotNull Class extends LocalInspectionTool> getInspection() {
+ return SubclassMappingSourceSubclassMappedMoreThanOnceInspection.class;
+ }
+
+ public void testSubclassMappingSourceSubclassMappedMoreThanOnce() {
+ doTest();
+ String testName = getTestName( false );
+ List allQuickFixes = myFixture.getAllQuickFixes();
+
+ assertThat( allQuickFixes )
+ .extracting( IntentionAction::getText )
+ .as( "Intent Text" )
+ .containsExactly(
+ "Remove SubclassMapping annotation",
+ "Change source property",
+ "Remove SubclassMapping annotation",
+ "Change source property",
+ "Remove SubclassMapping annotation",
+ "Change source property",
+ "Remove SubclassMapping annotation",
+ "Change source property",
+ "Remove SubclassMapping annotation",
+ "Change source property",
+ "Remove SubclassMapping annotation",
+ "Change source property",
+ "Remove SubclassMapping annotation",
+ "Change source property",
+ "Remove MySubclassMappingAnnotation annotation",
+ "Remove SubclassMapping annotation",
+ "Change source property",
+ "Remove MySubclassMappingAnnotation annotation"
+ );
+
+ // Delete annotations
+ myFixture.launchAction( allQuickFixes.get( 0 ) );
+ myFixture.launchAction( allQuickFixes.get( 3 ) );
+ myFixture.launchAction( allQuickFixes.get( 6 ) );
+ myFixture.launchAction( allQuickFixes.get( 8 ) );
+ myFixture.launchAction( allQuickFixes.get( 14 ) );
+ // Set cursor
+ myFixture.launchAction( allQuickFixes.get( 16 ) );
+ myFixture.checkResultByFile( testName + "_after.java" );
+ Caret currentCaret = myFixture.getEditor().getCaretModel().getCurrentCaret();
+ assertThat( currentCaret.getSelectedText( ) ).isEqualTo( "SourceSubclass1" );
+ }
+}
diff --git a/src/test/java/org/mapstruct/intellij/inspection/ValueMappingSourceMappedMoreThanOnceInspectionTest.java b/src/test/java/org/mapstruct/intellij/inspection/ValueMappingSourceMappedMoreThanOnceInspectionTest.java
new file mode 100644
index 00000000..1529f727
--- /dev/null
+++ b/src/test/java/org/mapstruct/intellij/inspection/ValueMappingSourceMappedMoreThanOnceInspectionTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.intellij.inspection;
+
+import java.util.List;
+
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.codeInspection.LocalInspectionTool;
+import com.intellij.openapi.editor.Caret;
+import org.jetbrains.annotations.NotNull;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author hduelme
+ */
+public class ValueMappingSourceMappedMoreThanOnceInspectionTest extends BaseInspectionTest {
+
+ @Override
+ protected @NotNull Class extends LocalInspectionTool> getInspection() {
+ return ValueMappingSourceMappedMoreThanOnceInspection.class;
+ }
+
+ public void testValueMappingSourceMappedMoreThanOnce() {
+ doTest();
+ String testName = getTestName( false );
+ List allQuickFixes = myFixture.getAllQuickFixes();
+
+ assertThat( allQuickFixes )
+ .extracting( IntentionAction::getText )
+ .as( "Intent Text" )
+ .containsExactly(
+ "Remove ValueMapping annotation",
+ "Change source property",
+ "Remove ValueMapping annotation",
+ "Change source property",
+ "Remove ValueMapping annotation",
+ "Change source property",
+ "Remove ValueMapping annotation",
+ "Change source property",
+ "Remove ValueMapping annotation",
+ "Change source property",
+ "Remove ValueMapping annotation",
+ "Change source property",
+ "Remove ValueMapping annotation",
+ "Change source property",
+ "Remove MyValueMappingAnnotation annotation",
+ "Remove ValueMapping annotation",
+ "Change source property",
+ "Remove MyValueMappingAnnotation annotation"
+ );
+
+ // Delete annotations
+ myFixture.launchAction( allQuickFixes.get( 0 ) );
+ myFixture.launchAction( allQuickFixes.get( 3 ) );
+ myFixture.launchAction( allQuickFixes.get( 6 ) );
+ myFixture.launchAction( allQuickFixes.get( 8 ) );
+ myFixture.launchAction( allQuickFixes.get( 14 ) );
+ // Set cursor
+ myFixture.launchAction( allQuickFixes.get( 16 ) );
+ myFixture.checkResultByFile( testName + "_after.java" );
+ Caret currentCaret = myFixture.getEditor().getCaretModel().getCurrentCaret();
+ assertThat( currentCaret.getSelectedText( ) ).isEqualTo( "A" );
+ }
+}
diff --git a/testData/inspection/SubclassMappingSourceSubclassMappedMoreThanOnce.java b/testData/inspection/SubclassMappingSourceSubclassMappedMoreThanOnce.java
new file mode 100644
index 00000000..b637e902
--- /dev/null
+++ b/testData/inspection/SubclassMappingSourceSubclassMappedMoreThanOnce.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.example.data;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.SubclassMappings;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+class Source {
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+
+class SourceSubclass1 extends Source {
+ private String type;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+}
+
+class SourceSubclass2 extends Source {
+ private String category;
+
+ public String getCategory() {
+ return category;
+ }
+
+ public void setCategory(String category) {
+ this.category = category;
+ }
+}
+
+class Target {
+ private String testName;
+
+ public String getTestName() {
+ return testName;
+ }
+
+ public void setTestName(String testName) {
+ this.testName = testName;
+ }
+}
+
+class TargetSubclass1 extends Target {
+ private String kind;
+
+ public String getKind() {
+ return kind;
+ }
+
+ public void setKind(String kind) {
+ this.kind = kind;
+ }
+}
+
+class TargetSubclass2 extends Target {
+ private String label;
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+}
+
+@Retention(RetentionPolicy.CLASS)
+@SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass1.class)
+@interface MySubclassMappingAnnotation {
+}
+
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceBySubclassMappingAnnotationMapper {
+
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass1.class)
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass2.class)
+ Target map(Source source);
+}
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceBySubclassMappingsAnnotationMapper {
+
+ @SubclassMappings({
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass1.class),
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass2.class)
+ })
+ Target map(Source source);
+}
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceBySubclassMappingAnnotationAndSubclassMappingsAnnotationMapper {
+
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass1.class)
+ @SubclassMappings({
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass2.class)
+ })
+ Target map(Source source);
+}
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceByMySubclassMappingAnnotationAndSubclassMappingAnnotationMapper {
+
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass2.class)
+ @MySubclassMappingAnnotation
+ Target map(Source source);
+}
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceByMySubclassMappingAnnotationAndSubclassMappingsAnnotationMapper {
+
+ @SubclassMappings({
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass2.class)
+ })
+ @MySubclassMappingAnnotation
+ Target map(Source source);
+}
diff --git a/testData/inspection/SubclassMappingSourceSubclassMappedMoreThanOnce_after.java b/testData/inspection/SubclassMappingSourceSubclassMappedMoreThanOnce_after.java
new file mode 100644
index 00000000..4e42d35a
--- /dev/null
+++ b/testData/inspection/SubclassMappingSourceSubclassMappedMoreThanOnce_after.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.example.data;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.SubclassMapping;
+import org.mapstruct.SubclassMappings;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+class Source {
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+
+class SourceSubclass1 extends Source {
+ private String type;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+}
+
+class SourceSubclass2 extends Source {
+ private String category;
+
+ public String getCategory() {
+ return category;
+ }
+
+ public void setCategory(String category) {
+ this.category = category;
+ }
+}
+
+class Target {
+ private String testName;
+
+ public String getTestName() {
+ return testName;
+ }
+
+ public void setTestName(String testName) {
+ this.testName = testName;
+ }
+}
+
+class TargetSubclass1 extends Target {
+ private String kind;
+
+ public String getKind() {
+ return kind;
+ }
+
+ public void setKind(String kind) {
+ this.kind = kind;
+ }
+}
+
+class TargetSubclass2 extends Target {
+ private String label;
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+}
+
+@Retention(RetentionPolicy.CLASS)
+@SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass1.class)
+@interface MySubclassMappingAnnotation {
+}
+
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceBySubclassMappingAnnotationMapper {
+
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass2.class)
+ Target map(Source source);
+}
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceBySubclassMappingsAnnotationMapper {
+
+ @SubclassMappings({
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass1.class)
+ })
+ Target map(Source source);
+}
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceBySubclassMappingAnnotationAndSubclassMappingsAnnotationMapper {
+
+ @SubclassMappings({
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass2.class)
+ })
+ Target map(Source source);
+}
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceByMySubclassMappingAnnotationAndSubclassMappingAnnotationMapper {
+
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass2.class)
+ Target map(Source source);
+}
+
+@Mapper
+interface SubclassMappingSourceMappedMoreThanOnceByMySubclassMappingAnnotationAndSubclassMappingsAnnotationMapper {
+
+ @SubclassMappings({
+ @SubclassMapping(source = SourceSubclass1.class, target = TargetSubclass2.class)
+ })
+ @MySubclassMappingAnnotation
+ Target map(Source source);
+}
diff --git a/testData/inspection/ValueMappingSourceMappedMoreThanOnce.java b/testData/inspection/ValueMappingSourceMappedMoreThanOnce.java
new file mode 100644
index 00000000..8fb6def4
--- /dev/null
+++ b/testData/inspection/ValueMappingSourceMappedMoreThanOnce.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
+ */
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ValueMapping;
+import org.mapstruct.ValueMappings;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+enum Target {
+ A,
+ B,
+}
+
+enum Source {
+ A,
+ B,
+}
+
+
+
+@Retention(RetentionPolicy.CLASS)
+@ValueMapping(target = "B", source = "A")
+@interface MyValueMappingAnnotation {
+}
+
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMappingAnnotationMapper {
+
+ @ValueMapping(target = "A", source = "A")
+ @ValueMapping(target = "B", source = "A")
+ Target map(Source source);
+}
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMappingsAnnotationsMapper {
+
+ @ValueMappings({
+ @ValueMapping(target = "A", source = "A"),
+ @ValueMapping(target = "B", source = "A")
+ })
+ Target map(Source source);
+}
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMappingsAnnotationsAndMappingAnnotationMapper {
+
+ @ValueMapping(target = "A", source = "A")
+ @ValueMappings({
+ @ValueMapping(target = "B", source = "A")
+ })
+ Target map(Source source);
+}
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMyMappingAnnotationAndMappingAnnotationMapper {
+
+ @ValueMapping(target = "A", source = "A")
+ @MyValueMappingAnnotation
+ Target map(Source source);
+}
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMyMappingAnnotationAndMappingsAnnotationMapper {
+
+ @ValueMappings({
+ @ValueMapping(target = "A", source = "A")
+ })
+ @MyValueMappingAnnotation
+ Target map(Source source);
+}
\ No newline at end of file
diff --git a/testData/inspection/ValueMappingSourceMappedMoreThanOnce_after.java b/testData/inspection/ValueMappingSourceMappedMoreThanOnce_after.java
new file mode 100644
index 00000000..83fbbce6
--- /dev/null
+++ b/testData/inspection/ValueMappingSourceMappedMoreThanOnce_after.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
+ */
+
+import org.mapstruct.Mapper;
+import org.mapstruct.ValueMapping;
+import org.mapstruct.ValueMappings;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+enum Target {
+ A,
+ B,
+}
+
+enum Source {
+ A,
+ B,
+}
+
+
+
+@Retention(RetentionPolicy.CLASS)
+@ValueMapping(target = "B", source = "A")
+@interface MyValueMappingAnnotation {
+}
+
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMappingAnnotationMapper {
+
+ @ValueMapping(target = "B", source = "A")
+ Target map(Source source);
+}
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMappingsAnnotationsMapper {
+
+ @ValueMappings({
+ @ValueMapping(target = "A", source = "A")
+ })
+ Target map(Source source);
+}
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMappingsAnnotationsAndMappingAnnotationMapper {
+
+ @ValueMappings({
+ @ValueMapping(target = "B", source = "A")
+ })
+ Target map(Source source);
+}
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMyMappingAnnotationAndMappingAnnotationMapper {
+
+ @ValueMapping(target = "A", source = "A")
+ Target map(Source source);
+}
+
+@Mapper
+interface ValueMappingSourceMappedMoreThanOnceByMyMappingAnnotationAndMappingsAnnotationMapper {
+
+ @ValueMappings({
+ @ValueMapping(target = "A", source = "A")
+ })
+ @MyValueMappingAnnotation
+ Target map(Source source);
+}
\ No newline at end of file