|
6 | 6 | package org.mapstruct.intellij.util; |
7 | 7 |
|
8 | 8 | import java.beans.Introspector; |
| 9 | +import java.util.ArrayList; |
9 | 10 | import java.util.Arrays; |
10 | 11 | import java.util.Collections; |
| 12 | +import java.util.HashMap; |
11 | 13 | import java.util.HashSet; |
12 | 14 | import java.util.LinkedHashMap; |
| 15 | +import java.util.List; |
13 | 16 | import java.util.Map; |
14 | 17 | import java.util.Objects; |
15 | 18 | import java.util.Set; |
16 | 19 | import java.util.stream.Stream; |
17 | 20 |
|
| 21 | +import com.intellij.lang.jvm.JvmModifier; |
18 | 22 | import com.intellij.openapi.util.Pair; |
19 | 23 | import com.intellij.psi.ElementManipulators; |
20 | 24 | import com.intellij.psi.PsiAnnotation; |
21 | 25 | import com.intellij.psi.PsiAnnotationMemberValue; |
22 | 26 | import com.intellij.psi.PsiArrayInitializerMemberValue; |
23 | 27 | import com.intellij.psi.PsiClass; |
| 28 | +import com.intellij.psi.PsiElement; |
| 29 | +import com.intellij.psi.PsiJavaCodeReferenceElement; |
24 | 30 | import com.intellij.psi.PsiMember; |
25 | 31 | import com.intellij.psi.PsiMethod; |
26 | 32 | import com.intellij.psi.PsiNameValuePair; |
@@ -84,25 +90,98 @@ public static PsiType getRelevantType(@NotNull PsiMethod mappingMethod) { |
84 | 90 | * |
85 | 91 | * @return a stream that holds all public write accessors for the given {@code psiType} |
86 | 92 | */ |
87 | | - public static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicWriteAccessors(@NotNull PsiType psiType, |
| 93 | + public static Map<String, Pair<? extends PsiElement, PsiSubstitutor>> publicWriteAccessors(@NotNull PsiType psiType, |
88 | 94 | MapStructVersion mapStructVersion) { |
89 | 95 | boolean builderSupportPresent = mapStructVersion.isBuilderSupported(); |
90 | 96 | Pair<PsiClass, PsiType> classAndType = resolveBuilderOrSelfClass( psiType, builderSupportPresent ); |
91 | 97 | if ( classAndType == null ) { |
92 | 98 | return Collections.emptyMap(); |
93 | 99 | } |
94 | 100 |
|
95 | | - Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicWriteAccessors = new LinkedHashMap<>(); |
| 101 | + Map<String, Pair<? extends PsiElement, PsiSubstitutor>> publicWriteAccessors = new LinkedHashMap<>(); |
96 | 102 |
|
97 | 103 | PsiClass psiClass = classAndType.getFirst(); |
98 | 104 | PsiType typeToUse = classAndType.getSecond(); |
99 | 105 |
|
100 | 106 | publicWriteAccessors.putAll( publicSetters( psiClass, typeToUse, builderSupportPresent ) ); |
101 | 107 | publicWriteAccessors.putAll( publicFields( psiClass ) ); |
102 | 108 |
|
| 109 | + if ( mapStructVersion.isConstructorSupported() ) { |
| 110 | + publicWriteAccessors.putAll( constructorParameters( psiClass ) ); |
| 111 | + } |
| 112 | + |
103 | 113 | return publicWriteAccessors; |
104 | 114 | } |
105 | 115 |
|
| 116 | + private static Map<String, Pair<PsiParameter, PsiSubstitutor>> constructorParameters(@NotNull PsiClass psiClass) { |
| 117 | + PsiMethod constructor = resolveMappingConstructor( psiClass ); |
| 118 | + if ( constructor == null || !constructor.hasParameters() ) { |
| 119 | + return Collections.emptyMap(); |
| 120 | + } |
| 121 | + |
| 122 | + Map<String, Pair<PsiParameter, PsiSubstitutor>> constructorParameters = new HashMap<>(); |
| 123 | + |
| 124 | + for ( PsiParameter parameter : constructor.getParameterList().getParameters() ) { |
| 125 | + constructorParameters.put( parameter.getName(), Pair.create( parameter, PsiSubstitutor.EMPTY ) ); |
| 126 | + } |
| 127 | + |
| 128 | + return constructorParameters; |
| 129 | + } |
| 130 | + |
| 131 | + /** |
| 132 | + * Find the constructor that the code generation will use when mapping the psiClass. |
| 133 | + * |
| 134 | + * @param psiClass the class for which the constructor should be found |
| 135 | + * @return the constructor or {@code null} is there is no constructor that the mapping will use |
| 136 | + */ |
| 137 | + public static PsiMethod resolveMappingConstructor(@NotNull PsiClass psiClass) { |
| 138 | + |
| 139 | + PsiMethod[] constructors = psiClass.getConstructors(); |
| 140 | + if ( constructors.length == 0 ) { |
| 141 | + return null; |
| 142 | + } |
| 143 | + |
| 144 | + if ( constructors.length == 1 ) { |
| 145 | + PsiMethod constructor = constructors[0]; |
| 146 | + return !constructor.hasModifier( JvmModifier.PRIVATE ) ? constructor : null; |
| 147 | + } |
| 148 | + |
| 149 | + List<PsiMethod> accessibleConstructors = new ArrayList<>(constructors.length); |
| 150 | + |
| 151 | + for ( PsiMethod constructor : constructors ) { |
| 152 | + if ( constructor.hasModifier( JvmModifier.PRIVATE ) ) { |
| 153 | + // private constructors are ignored |
| 154 | + continue; |
| 155 | + } |
| 156 | + if ( !constructor.hasParameters() ) { |
| 157 | + // If there is an empty constructor then that constructor should be used |
| 158 | + return constructor; |
| 159 | + } |
| 160 | + |
| 161 | + accessibleConstructors.add( constructor ); |
| 162 | + } |
| 163 | + |
| 164 | + if ( accessibleConstructors.size() == 1 ) { |
| 165 | + // If there is only one accessible constructor then use that one |
| 166 | + return accessibleConstructors.get( 0 ); |
| 167 | + } |
| 168 | + |
| 169 | + // If there are more accessible constructor then look for one annotated with @Default. |
| 170 | + // Otherwise return null |
| 171 | + for ( PsiMethod constructor : accessibleConstructors ) { |
| 172 | + for ( PsiAnnotation annotation : constructor.getAnnotations() ) { |
| 173 | + PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement(); |
| 174 | + if ( nameReferenceElement != null && "Default".equals( nameReferenceElement.getReferenceName() ) ) { |
| 175 | + // If there is a constructor annotated with an annotation named @Default |
| 176 | + // then we should use that one |
| 177 | + return constructor; |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + return null; |
| 183 | + } |
| 184 | + |
106 | 185 | /** |
107 | 186 | * Extract all public setters with their psi substitutors from the given {@code psiClass} |
108 | 187 | * |
|
0 commit comments