// ===================================================================== // // Copyright (C) 2012 - 2016, Philip Graf // // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // which accompanies this distribution, and is available at // http://www.eclipse.org/legal/epl-v10.html // // ===================================================================== package ch.acanda.eclipse.pmd.java.resolution; import java.util.List; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.EnumConstantDeclaration; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IExtendedModifier; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.Position; import ch.acanda.eclipse.pmd.marker.PMDMarker; import ch.acanda.eclipse.pmd.ui.util.PMDPluginImages; /** * Quick fix for all PMD rule violations in Java 5 and later. It adds a {@code @SuppressWarnings} annotation for the * respective rule to the enclosing class, method, field or parameter. * * @author Philip Graf */ @SuppressWarnings({ "PMD.CouplingBetweenObjects", "PMD.TooManyMethods" }) public final class SuppressWarningsQuickFix extends ASTQuickFix<ASTNode> { public SuppressWarningsQuickFix(final PMDMarker marker) { super(marker); } @Override protected ImageDescriptor getImageDescriptor() { return PMDPluginImages.QUICKFIX_ADD; } @Override public String getLabel() { return "Add @SuppressWarnings 'PMD." + marker.getRuleName() + "'"; } @Override public String getDescription() { return "Adds @SuppressWarnings(\"PMD." + marker.getRuleName() + "\")."; } @Override protected NodeFinder<CompilationUnit, ASTNode> getNodeFinder(final Position position) { return Finders.positionWithinNode(position, AbstractTypeDeclaration.class, AnnotationTypeMemberDeclaration.class, EnumConstantDeclaration.class, FieldDeclaration.class, MethodDeclaration.class, VariableDeclarationStatement.class, CompilationUnit.class); } @Override protected boolean apply(final ASTNode node) { final ASTNode annotatableNode = findAnnotatableASTNode(node); if (annotatableNode != null) { final AST ast = node.getAST(); final List<IExtendedModifier> modifiers = getModifiers(annotatableNode); final Annotation existingAnnotation = findExistingSuppressWarningsAnnotation(modifiers); final Annotation annotation = createReplacementSuppressWarningsAnnotation(existingAnnotation, ast); if (existingAnnotation == null) { final int position = findPosition(modifiers); modifiers.add(position, annotation); } else { ASTUtil.replace(existingAnnotation, annotation); } return !annotation.equals(existingAnnotation); } return false; } private ASTNode findAnnotatableASTNode(final ASTNode node) { if (node instanceof CompilationUnit) { return findBodyDeclaration(node); } return node; } @SuppressWarnings("unchecked") private List<IExtendedModifier> getModifiers(final ASTNode node) { if (node instanceof VariableDeclarationStatement) { return ((VariableDeclarationStatement) node).modifiers(); } return ((BodyDeclaration) node).modifiers(); } private BodyDeclaration findBodyDeclaration(final ASTNode node) { final BodyDeclaration[] bodyDeclaration = new BodyDeclaration[1]; node.accept(new ASTVisitor() { @Override public boolean visit(final EnumDeclaration node) { bodyDeclaration[0] = node; return false; } @Override public boolean visit(final TypeDeclaration node) { bodyDeclaration[0] = node; return false; } @Override public boolean visit(final AnnotationTypeDeclaration node) { bodyDeclaration[0] = node; return false; } }); return bodyDeclaration[0]; } private Annotation findExistingSuppressWarningsAnnotation(final List<IExtendedModifier> modifiers) { for (final IExtendedModifier modifier : modifiers) { if (modifier.isAnnotation()) { final Annotation annotation = (Annotation) modifier; final Name typeName = annotation.getTypeName(); if (typeName.isSimpleName() && "SuppressWarnings".equals(typeName.getFullyQualifiedName()) || typeName.isQualifiedName() && "java.lang.SuppressWarnings".equals(typeName.getFullyQualifiedName())) { return annotation; } } } return null; } private Annotation createReplacementSuppressWarningsAnnotation(final Annotation existingAnnotation, final AST ast) { final Annotation replacement; if (existingAnnotation == null || existingAnnotation.isMarkerAnnotation()) { final SingleMemberAnnotation annotation = createAnnotation(ast, SingleMemberAnnotation.class); annotation.setValue(createPMDLiteralValue(ast)); replacement = annotation; } else if (existingAnnotation.isSingleMemberAnnotation()) { final SingleMemberAnnotation existingSingleMemberAnnotation = (SingleMemberAnnotation) existingAnnotation; final SingleMemberAnnotation annotation = createAnnotation(ast, SingleMemberAnnotation.class); annotation.setValue(createArrayInitializer(existingSingleMemberAnnotation.getValue())); replacement = annotation; } else if (existingAnnotation.isNormalAnnotation()) { final NormalAnnotation existingNormalAnnotation = (NormalAnnotation) existingAnnotation; final NormalAnnotation annotation = createAnnotation(ast, NormalAnnotation.class); createAnnotationValues(existingNormalAnnotation, annotation); replacement = annotation; } else { replacement = existingAnnotation; } return replacement; } private <T extends Annotation> T createAnnotation(final AST ast, final Class<T> cls) { @SuppressWarnings("unchecked") final T annotation = (T) ast.createInstance(cls); final SimpleName name = (SimpleName) ast.createInstance(SimpleName.class); name.setIdentifier("SuppressWarnings"); annotation.setTypeName(name); return annotation; } /** * Create the "PMD.<i>RuleName</i>" string literal for the {@code @SuppressWarnings} annotation. */ private StringLiteral createPMDLiteralValue(final AST ast) { final StringLiteral newValue = (StringLiteral) ast.createInstance(StringLiteral.class); newValue.setLiteralValue("PMD." + marker.getRuleName()); return newValue; } /** * Creates the member value pairs of the annotation. */ @SuppressWarnings("unchecked") private void createAnnotationValues(final NormalAnnotation existingAnnotation, final NormalAnnotation annotation) { final AST ast = annotation.getAST(); final List<MemberValuePair> values = annotation.values(); final List<MemberValuePair> existingValues = existingAnnotation.values(); for (final MemberValuePair existingPair : existingValues) { if ("value".equals(existingPair.getName().getFullyQualifiedName())) { final MemberValuePair pair = (MemberValuePair) ast.createInstance(MemberValuePair.class); pair.setName(ASTUtil.copy(existingPair.getName())); pair.setValue(createArrayInitializer(existingPair.getValue())); values.add(pair); } else { values.add(ASTUtil.copy(existingPair)); } } } @SuppressWarnings("unchecked") private ArrayInitializer createArrayInitializer(final Expression value) { final AST ast = value.getAST(); final ArrayInitializer array; if (value instanceof ArrayInitializer) { array = createArrayInitializerAndCopyExpressions(ast, (ArrayInitializer) value); } else { array = (ArrayInitializer) ast.createInstance(ArrayInitializer.class); array.expressions().add(ASTUtil.copy(value)); } array.expressions().add(createPMDLiteralValue(ast)); return array; } @SuppressWarnings("unchecked") private ArrayInitializer createArrayInitializerAndCopyExpressions(final AST ast, final ArrayInitializer existingArray) { final ArrayInitializer array; array = (ArrayInitializer) ast.createInstance(ArrayInitializer.class); final List<Expression> expressions = array.expressions(); final List<Expression> existingExpressions = existingArray.expressions(); for (final Expression existingExpression : existingExpressions) { expressions.add(ASTUtil.copy(existingExpression)); } return array; } /** * @return The position after the last existing annotation. */ private int findPosition(final List<IExtendedModifier> modifiers) { int position = 0; int index = 0; for (final IExtendedModifier modifier : modifiers) { index++; if (modifier.isAnnotation()) { position = index; } } return position; } }