org.mapstruct.eclipse.internal.quickfix.fixes.AddIgnoreTargetMappingAnnotationQuickFix.java Source code

Java tutorial

Introduction

Here is the source code for org.mapstruct.eclipse.internal.quickfix.fixes.AddIgnoreTargetMappingAnnotationQuickFix.java

Source

/**
 *  Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
 *  and/or other contributors as indicated by the @authors tag. See the
 *  copyright.txt file in the distribution for a full listing of all
 *  contributors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.mapstruct.eclipse.internal.quickfix.fixes;

import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.mapstruct.eclipse.internal.quickfix.MapStructQuickFix;
import org.mapstruct.eclipse.internal.quickfix.visitors.FindAnnotationByNameVisitor;

import static org.mapstruct.eclipse.internal.MapStructAPIConstants.MAPPINGS_FQ_NAME;
import static org.mapstruct.eclipse.internal.MapStructAPIConstants.MAPPINGS_SIMPLE_NAME;
import static org.mapstruct.eclipse.internal.MapStructAPIConstants.MAPPING_FQ_NAME;
import static org.mapstruct.eclipse.internal.MapStructAPIConstants.MAPPING_MEMBER_IGNORE;
import static org.mapstruct.eclipse.internal.MapStructAPIConstants.MAPPING_MEMBER_TARGET;
import static org.mapstruct.eclipse.internal.MapStructAPIConstants.MAPPING_SIMPLE_NAME;

/**
 * Quick fix for error/warning "Unmapped target property" / "Unmapped target properties" that adds
 * {@code @Mapping( target = "<property>", ignore = true)} to the method.
 *
 * @author Andreas Gudian
 */
public class AddIgnoreTargetMappingAnnotationQuickFix extends MapStructQuickFix {

    public AddIgnoreTargetMappingAnnotationQuickFix() {
        super("Ignore unmapped target properties");
    }

    @Override
    public boolean canFix(IMarker marker) {
        String message = getMessage(marker);

        return message.startsWith("Unmapped target property:") || message.startsWith("Unmapped target properties:");
    }

    @Override
    protected ASTRewrite getASTRewrite(CompilationUnit unit, ASTNode nodeWithMarker, IMarker marker) {
        Collection<String> properties = extractPropertiesFromMessage(getMessage(marker));

        if (properties == null) {
            return null;
        }

        AST ast = unit.getAST();

        ASTRewrite rewrite = ASTRewrite.create(ast);
        MethodDeclaration method = (MethodDeclaration) nodeWithMarker;

        ListRewrite mappingList = getListForAddingMappingAnnotations(unit, properties, ast, rewrite, method);

        addMappingAnnotations(properties, ast, mappingList);

        addImportIfRequired(unit, rewrite, MAPPING_FQ_NAME);

        return rewrite;
    }

    private ListRewrite getListForAddingMappingAnnotations(CompilationUnit unit, Collection<String> properties,
            AST ast, ASTRewrite rewrite, MethodDeclaration method) {

        // if there is already an @Mappings annotation, add the new @Mapping's there
        Annotation mappingsAnnotation = findAnnotation(method, MAPPINGS_FQ_NAME);
        if (mappingsAnnotation != null) {
            return rewrite.getListRewrite(((SingleMemberAnnotation) mappingsAnnotation).getValue(),
                    ArrayInitializer.EXPRESSIONS_PROPERTY);
        }

        // if repeatable @Mapping's are supported, then add the annotations directly to the method
        if (supportsRepeatableMapping(unit)) {
            return rewrite.getListRewrite(method, MethodDeclaration.MODIFIERS2_PROPERTY);
        }

        // if we only need to add one @Mapping and there is none, yet, then add the single annotation directly
        Annotation singleMappingAnnotation = findAnnotation(method, MAPPING_FQ_NAME);
        if (singleMappingAnnotation == null && properties.size() == 1) {
            return rewrite.getListRewrite(method, MethodDeclaration.MODIFIERS2_PROPERTY);
        }

        // create a new @Mappings annotation and add the @Mapping's there
        ListRewrite mappingList = addNewMappingsAnnotation(unit, rewrite, ast, method);

        if (singleMappingAnnotation != null) {
            rewrite.getListRewrite(method, MethodDeclaration.MODIFIERS2_PROPERTY).remove(singleMappingAnnotation,
                    null);

            mappingList.insertFirst(singleMappingAnnotation, null);
        }
        return mappingList;
    }

    private ListRewrite addNewMappingsAnnotation(CompilationUnit unit, ASTRewrite rewrite, AST ast,
            MethodDeclaration method) {
        SingleMemberAnnotation mappings = ast.newSingleMemberAnnotation();
        mappings.setTypeName(ast.newName(MAPPINGS_SIMPLE_NAME));

        ArrayInitializer mappingArray = ast.newArrayInitializer();
        mappings.setValue(mappingArray);

        ListRewrite annotations = rewrite.getListRewrite(method, MethodDeclaration.MODIFIERS2_PROPERTY);
        annotations.insertFirst(mappings, null);

        addImportIfRequired(unit, rewrite, MAPPINGS_FQ_NAME);

        return rewrite.getListRewrite(mappingArray, ArrayInitializer.EXPRESSIONS_PROPERTY);
    }

    private Annotation findAnnotation(MethodDeclaration method, String annotationName) {
        FindAnnotationByNameVisitor locatedAnnotation = new FindAnnotationByNameVisitor(annotationName);
        method.accept(locatedAnnotation);

        return locatedAnnotation.getLocatedNode();
    }

    private boolean supportsRepeatableMapping(CompilationUnit unit) {
        try {
            IJavaElement javaElement = unit.getJavaElement();
            if (javaElement == null) {
                return false;
            }

            IType mappingType = javaElement.getJavaProject().findType(MAPPING_FQ_NAME);
            if (mappingType == null) {
                return false;
            }

            for (IAnnotation annotation : mappingType.getAnnotations()) {
                if (annotation.getElementName().equals("Repeatable")
                        || annotation.getElementName().equals("java.lang.annotation.Repeatable")) {
                    return true;
                }
            }
        } catch (CoreException e) {
            return false;
        }

        return false;
    }

    @SuppressWarnings("unchecked")
    private void addMappingAnnotations(Collection<String> properties, AST ast, ListRewrite mappingList) {
        LinkedList<NormalAnnotation> toAdd = new LinkedList<NormalAnnotation>();
        for (String property : properties) {
            NormalAnnotation mapping = ast.newNormalAnnotation();
            mapping.setTypeName(ast.newName(MAPPING_SIMPLE_NAME));

            MemberValuePair valuePair = ast.newMemberValuePair();

            valuePair.setName(ast.newSimpleName(MAPPING_MEMBER_TARGET));
            StringLiteral literal = ast.newStringLiteral();
            literal.setLiteralValue(property);
            valuePair.setValue(literal);
            mapping.values().add(valuePair);

            valuePair = ast.newMemberValuePair();

            valuePair.setName(ast.newSimpleName(MAPPING_MEMBER_IGNORE));
            valuePair.setValue(ast.newBooleanLiteral(true));
            mapping.values().add(valuePair);

            toAdd.addFirst(mapping);
        }

        for (NormalAnnotation mapping : toAdd) {
            mappingList.insertFirst(mapping, null);
        }
    }

    private static Collection<String> extractPropertiesFromMessage(String msg) {
        Pattern p = Pattern.compile("Unmapped target (property|properties): \"([^\"]+)\".");
        Matcher matcher = p.matcher(msg);
        if (matcher.matches()) {
            String props = matcher.group(2);
            return Arrays.asList(props.split(", "));
        }
        return null;
    }
}