Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/*
* 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 com.intellij.codeInspection.LocalQuickFixOnPsiElement;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.util.IntentionFamilyName;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.openapi.project.Project;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mapstruct.intellij.MapStructBundle;
import org.mapstruct.intellij.util.MapStructVersion;
import org.mapstruct.intellij.util.MapstructUtil;

import java.util.Set;

import static com.intellij.psi.PsiElementFactory.getInstance;
import static org.mapstruct.intellij.util.MapstructUtil.getSourceParameters;
import static org.mapstruct.intellij.util.SourceUtils.findAllDefinedMappingSources;
import static org.mapstruct.intellij.util.SourceUtils.getGenericTypes;
import static org.mapstruct.intellij.util.TargetUtils.findAllTargetProperties;
import static org.mapstruct.intellij.util.TargetUtils.getTargetType;

/**
* @author hduelme
*/
public class FromMapMappingMapTypeInspection extends InspectionBase {

@NotNull
@Override
PsiElementVisitor buildVisitorInternal(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new MyJavaElementVisitor( holder, MapstructUtil.resolveMapStructProjectVersion( holder.getFile() ) );
}

private static class MyJavaElementVisitor extends JavaElementVisitor {
private final ProblemsHolder holder;
private final MapStructVersion mapStructVersion;

private MyJavaElementVisitor(ProblemsHolder holder, MapStructVersion mapStructVersion) {
this.holder = holder;
this.mapStructVersion = mapStructVersion;
}

@Override
public void visitMethod(@NotNull PsiMethod method) {
super.visitMethod( method );

if (!MapstructUtil.isMapper( method.getContainingClass() ) ) {
return;
}

PsiType targetType = getTargetType( method );
if (targetType == null) {
return;
}

PsiParameter fromMapMappingParameter = getFromMapMappingParameter( method );
if (fromMapMappingParameter == null) {
return;
}
PsiType[] parameters = getGenericTypes( fromMapMappingParameter );
if (parameters == null) {
return;
}
Set<String> allTargetProperties = findAllTargetProperties( targetType, mapStructVersion, method );
if ( allTargetProperties.contains( fromMapMappingParameter.getName() ) ) {
return;
}
if ( findAllDefinedMappingSources( method, mapStructVersion )
.anyMatch( source -> fromMapMappingParameter.getName().equals( source ) ) ) {
return;
}
if (parameters.length == 0) {
// handle raw type
holder.registerProblem( fromMapMappingParameter,
MapStructBundle.message( "inspection.wrong.map.mapping.map.type.raw" ),
new ReplaceByStringStringMapTypeFix( fromMapMappingParameter ) );
}
else if (parameters.length == 2) {
// only if both parameters of the map are set
PsiType keyParameter = parameters[0];
if ( !keyParameter.equalsToText( "java.lang.String" ) ) {
// handle wrong map key type
holder.registerProblem( fromMapMappingParameter,
MapStructBundle.message( "inspection.wrong.map.mapping.map.key" ),
new ReplaceMapKeyByStringTypeFix( fromMapMappingParameter ) );
}
}
}

@Nullable
private static PsiParameter getFromMapMappingParameter(@NotNull PsiMethod method) {
PsiParameter[] sourceParameters = getSourceParameters( method );
if (sourceParameters.length == 1) {
PsiParameter parameter = sourceParameters[0];
if (parameter != null && PsiType.getTypeByName( "java.util.Map", method.getProject(),
method.getResolveScope() ).isAssignableFrom( parameter.getType() ) ) {
return parameter;
}
}
return null;
}

private static class ReplaceByStringStringMapTypeFix extends LocalQuickFixOnPsiElement {

private final String text;

private ReplaceByStringStringMapTypeFix(@NotNull PsiParameter element) {
super( element );
this.text = MapStructBundle.message( "inspection.wrong.map.mapping.map.type.raw.set.default",
element.getType().getPresentableText() );
}

@Override
public @IntentionName @NotNull String getText() {
return text;
}

@Override
public boolean isAvailable(@NotNull Project project, @NotNull PsiFile file,
@NotNull PsiElement startElement, @NotNull PsiElement endElement) {
if (!super.isAvailable( project, file, startElement, endElement ) ) {
return false;
}
if (startElement instanceof PsiParameter) {
PsiParameter parameter = (PsiParameter) startElement;
PsiType[] parameters = getGenericTypes( parameter );
return parameters != null && parameters.length == 0;
}
return false;
}

@Override
public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @NotNull PsiElement psiElement,
@NotNull PsiElement psiElement1) {
if (psiElement instanceof PsiParameter) {
String mapText = psiElement.getText();
String prefix = mapText.substring( 0, mapText.indexOf( ' ' ) );
String end = mapText.substring( mapText.lastIndexOf( ' ' ) );
String result = prefix + "<String, String>" + end;
psiElement.replace( getInstance( project ).createParameterFromText( result, psiElement ) );
}
}

@Override
public @IntentionFamilyName @NotNull String getFamilyName() {
return MapStructBundle.message( "intention.wrong.map.mapping.map.type.raw" );
}
}

private static class ReplaceMapKeyByStringTypeFix extends LocalQuickFixOnPsiElement {

private final String text;

private ReplaceMapKeyByStringTypeFix(@NotNull PsiParameter element) {
super( element );
this.text = MapStructBundle.message( "inspection.wrong.map.mapping.map.key.change.to.string" );
}

@Override
public @IntentionName @NotNull String getText() {
return text;
}

@Override
public boolean isAvailable(@NotNull Project project, @NotNull PsiFile file,
@NotNull PsiElement startElement, @NotNull PsiElement endElement) {
if (!super.isAvailable( project, file, startElement, endElement ) ) {
return false;
}
if (startElement instanceof PsiParameter) {
PsiParameter parameter = (PsiParameter) startElement;
PsiType[] parameters = getGenericTypes( parameter );
if (parameters == null || parameters.length != 2) {
return false;
}
return !parameters[0].equalsToText( "java.lang.String" );
}
return false;
}

@Override
public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @NotNull PsiElement psiElement,
@NotNull PsiElement psiElement1) {
if (psiElement instanceof PsiParameter) {
String mapText = psiElement.getText();
String prefix = mapText.substring( 0, mapText.indexOf( '<' ) + 1 );
String end = mapText.substring( mapText.indexOf( ',' ) );
String result = prefix + "String" + end;
psiElement.replace( getInstance( project ).createParameterFromText( result, psiElement ) );
}
}

@Override
public @IntentionFamilyName @NotNull String getFamilyName() {
return MapStructBundle.message( "intention.wrong.map.mapping.map.key" );
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,16 @@
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.intellij.MapStructBundle;
import org.mapstruct.intellij.settings.ProjectSettings;
Expand All @@ -44,14 +42,13 @@
import static org.mapstruct.intellij.inspection.inheritance.InheritConfigurationUtils.findInheritedTargetProperties;
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.addMappingAnnotation;
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.getUnmappedTargetPolicy;
import static org.mapstruct.intellij.util.MapstructUtil.isInheritInverseConfiguration;
import static org.mapstruct.intellij.util.MapstructUtil.isMapper;
import static org.mapstruct.intellij.util.MapstructUtil.isMapperConfig;
import static org.mapstruct.intellij.util.MapstructUtil.getSourceParameters;
import static org.mapstruct.intellij.util.SourceUtils.findAllSourceProperties;
import static org.mapstruct.intellij.util.SourceUtils.getGenericTypes;
import static org.mapstruct.intellij.util.TargetUtils.findAllDefinedMappingTargets;
import static org.mapstruct.intellij.util.TargetUtils.findAllSourcePropertiesForCurrentTarget;
import static org.mapstruct.intellij.util.TargetUtils.findAllTargetProperties;
import static org.mapstruct.intellij.util.TargetUtils.getRelevantType;
import static org.mapstruct.intellij.util.TargetUtils.getTargetType;

/**
* Inspection that checks if there are unmapped target properties.
Expand Down Expand Up @@ -91,6 +88,10 @@ public void visitMethod(PsiMethod method) {
return;
}

if ( isFromMapMapping( method ) ) {
return;
}

ReportingPolicy reportingPolicy = getUnmappedTargetPolicy( method );
if (reportingPolicy == ReportingPolicy.IGNORE) {
return;
Expand Down Expand Up @@ -185,28 +186,18 @@ private static boolean isBeanMappingIgnoreByDefault(PsiMethod method) {
return false;
}

/**
* @param method the method to be used
*
* @return the target class for the inspection, or {@code null} if no inspection needs to be performed
*/
@Nullable
private static PsiType getTargetType(PsiMethod method) {
if ( !method.getModifierList().hasModifierProperty( PsiModifier.ABSTRACT ) ) {
return null;
}

if ( isInheritInverseConfiguration( method ) ) {
return null;
}
PsiClass containingClass = method.getContainingClass();

if ( containingClass == null
|| method.getNameIdentifier() == null
|| !( isMapper( containingClass ) || isMapperConfig( containingClass ) ) ) {
return null;
private static boolean isFromMapMapping(@NotNull PsiMethod method) {
PsiParameter[] sourceParameters = getSourceParameters( method );
for (PsiParameter parameter : sourceParameters) {
if (parameter != null && PsiType.getTypeByName( "java.util.Map", method.getProject(),
method.getResolveScope() ).isAssignableFrom( parameter.getType() ) ) {
PsiType[] generics = getGenericTypes( parameter );
if (generics != null && generics.length > 0) {
return generics[0].equalsToText( "java.lang.String" );
}
}
}
return getRelevantType( method );
return false;
}

}
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/mapstruct/intellij/util/SourceUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.intellij.psi.PsiRecordComponent;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiType;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -211,4 +212,13 @@ else if ( methodName.startsWith( "is" ) && (
}

}

@Nullable
public static PsiType[] getGenericTypes(@Nullable PsiParameter fromMapMappingParameter) {
if (fromMapMappingParameter == null ||
!(fromMapMappingParameter.getType() instanceof PsiClassReferenceType)) {
return null;
}
return ((PsiClassReferenceType) fromMapMappingParameter.getType()).getParameters();
}
}
28 changes: 28 additions & 0 deletions src/main/java/org/mapstruct/intellij/util/TargetUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiMember;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiSubstitutor;
Expand All @@ -45,6 +46,9 @@
import static org.mapstruct.intellij.util.MapstructUtil.MAPPER_ANNOTATION_FQN;
import static org.mapstruct.intellij.util.MapstructUtil.canDescendIntoType;
import static org.mapstruct.intellij.util.MapstructUtil.isFluentSetter;
import static org.mapstruct.intellij.util.MapstructUtil.isInheritInverseConfiguration;
import static org.mapstruct.intellij.util.MapstructUtil.isMapper;
import static org.mapstruct.intellij.util.MapstructUtil.isMapperConfig;
import static org.mapstruct.intellij.util.MapstructUtil.publicFields;

/**
Expand Down Expand Up @@ -433,4 +437,28 @@ public static Set<String> findAllTargetProperties(@NotNull PsiType targetType, M
return publicWriteAccessors( targetType, mapStructVersion, mappingMethod ).keySet();
}

/**
* @param method the method to be used
*
* @return the target class for the inspection, or {@code null} if no inspection needs to be performed
*/
@Nullable
public static PsiType getTargetType( @NotNull PsiMethod method) {
if ( !method.getModifierList().hasModifierProperty( PsiModifier.ABSTRACT ) ) {
return null;
}

if ( isInheritInverseConfiguration( method ) ) {
return null;
}
PsiClass containingClass = method.getContainingClass();

if ( containingClass == null
|| method.getNameIdentifier() == null
|| !( isMapper( containingClass ) || isMapperConfig( containingClass ) ) ) {
return null;
}
return getRelevantType( method );
}

}
8 changes: 8 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@
key="inspection.java.expression.unnecessary.whitespace.title"
shortName="JavaExpressionUnnecessaryWhitespaces"
implementationClass="org.mapstruct.intellij.inspection.JavaExpressionUnnecessaryWhitespacesInspector"/>
<localInspection
language="JAVA"
enabledByDefault="true"
level="WARNING"
bundle="org.mapstruct.intellij.messages.MapStructBundle"
key="inspection.wrong.map.mapping.map.type"
shortName="FromMapMappingInspection"
implementationClass="org.mapstruct.intellij.inspection.FromMapMappingMapTypeInspection"/>
</extensions>

<actions>
Expand Down
Loading