blob: 019f5b6bed8c492f4183ff93a053eddb0d86f2ec [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Red Hat Inc. - created based on PotentialProgrammingProblemsFix
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NameQualifiedType;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.ui.text.correction.IProblemLocationCore;
import org.eclipse.jdt.internal.ui.text.correction.ProblemLocationCore;
import org.eclipse.jdt.internal.ui.text.correction.SerialVersionHashOperationCore;
public class PotentialProgrammingProblemsFixCore extends CompilationUnitRewriteOperationsFixCore {
/** Name of the serializable class */
private static final String SERIALIZABLE_NAME= "java.io.Serializable"; //$NON-NLS-1$
/** The name of the serial version field */
private static final String NAME_FIELD= "serialVersionUID"; //$NON-NLS-1$
public interface ISerialVersionFixContext {
public RefactoringStatus initialize(IProgressMonitor monitor) throws CoreException;
public Long getSerialVersionId(ITypeBinding binding);
}
public static class SerialVersionHashContext implements ISerialVersionFixContext {
private final IJavaProject fProject;
private final ICompilationUnit[] fCompilationUnits;
private final Hashtable<String, Long> fIdsTable;
public SerialVersionHashContext(IJavaProject project, ICompilationUnit[] compilationUnits) {
fProject= project;
fCompilationUnits= compilationUnits;
fIdsTable= new Hashtable<>();
}
@Override
public RefactoringStatus initialize(IProgressMonitor monitor) throws CoreException {
if (monitor == null)
monitor= new NullProgressMonitor();
RefactoringStatus result;
try {
monitor.beginTask("", 10); //$NON-NLS-1$
IType[] types= findTypesWithMissingUID(fProject, fCompilationUnits, new SubProgressMonitor(monitor, 1));
if (types.length == 0)
return new RefactoringStatus();
fProject.getProject().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new SubProgressMonitor(monitor, 60));
if (monitor.isCanceled())
throw new OperationCanceledException();
result= new RefactoringStatus();
ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
parser.setProject(fProject);
IBinding[] bindings= parser.createBindings(types, new SubProgressMonitor(monitor, 1));
for (int i= 0; i < bindings.length; i++) {
IBinding curr= bindings[i];
if (curr instanceof ITypeBinding) {
ITypeBinding typeBinding= (ITypeBinding) curr;
try {
Long id= SerialVersionHashOperationCore.calculateSerialVersionId(typeBinding, new SubProgressMonitor(monitor, 1));
if (id != null) {
setSerialVersionId(typeBinding, id);
} else {
result.addWarning(Messages.format(FixMessages.PotentialProgrammingProblemsFix_calculatingUIDFailed_unknown, BasicElementLabels.getJavaElementName(typeBinding.getName())));
}
} catch (IOException e) {
result.addWarning(Messages.format(FixMessages.PotentialProgrammingProblemsFix_calculatingUIDFailed_exception, new String[] { BasicElementLabels.getJavaElementName(typeBinding.getName()), e.getLocalizedMessage()}), JavaStatusContext.create(types[i]));
} catch (CoreException e) {
result.addWarning(Messages.format(FixMessages.PotentialProgrammingProblemsFix_calculatingUIDFailed_exception, new String[] { BasicElementLabels.getJavaElementName(typeBinding.getName()), e.getLocalizedMessage()}), JavaStatusContext.create(types[i]));
}
}
}
} finally {
monitor.done();
}
return result;
}
@Override
public Long getSerialVersionId(ITypeBinding binding) {
return fIdsTable.get(binding.getKey());
}
protected void setSerialVersionId(ITypeBinding binding, Long id) {
fIdsTable.put(binding.getKey(), id);
}
private IType[] findTypesWithMissingUID(IJavaProject project, ICompilationUnit[] compilationUnits, IProgressMonitor monitor) throws CoreException {
try {
monitor.beginTask("", compilationUnits.length); //$NON-NLS-1$
IType serializable= project.findType(SERIALIZABLE_NAME);
List<IType> types= new ArrayList<>();
if (compilationUnits.length > 500) {
//500 is a guess. Building the type hierarchy on serializable is very expensive
//depending on how many subtypes exit in the project.
HashSet<ICompilationUnit> cus= new HashSet<>();
cus.addAll(Arrays.asList(compilationUnits));
monitor.subTask(Messages.format(FixMessages.Java50Fix_SerialVersion_CalculateHierarchy_description, SERIALIZABLE_NAME));
ITypeHierarchy hierarchy1= serializable.newTypeHierarchy(project, new SubProgressMonitor(monitor, compilationUnits.length));
IType[] allSubtypes1= hierarchy1.getAllSubtypes(serializable);
addTypes(allSubtypes1, cus, types);
} else {
monitor.subTask(FixMessages.Java50Fix_InitializeSerialVersionId_subtask_description);
for (int i= 0; i < compilationUnits.length; i++) {
collectChildrenWithMissingSerialVersionId(compilationUnits[i].getChildren(), serializable, types);
if (monitor.isCanceled())
throw new OperationCanceledException();
monitor.worked(1);
}
}
return types.toArray(new IType[types.size()]);
} finally {
monitor.done();
}
}
private void addTypes(IType[] allSubtypes, HashSet<ICompilationUnit> cus, List<IType> types) throws JavaModelException {
for (int i= 0; i < allSubtypes.length; i++) {
IType type= allSubtypes[i];
IField field= type.getField(NAME_FIELD);
if (!field.exists()) {
if (type.isClass() && cus.contains(type.getCompilationUnit())){
types.add(type);
}
}
}
}
private void collectChildrenWithMissingSerialVersionId(IJavaElement[] children, IType serializable, List<IType> result) throws JavaModelException {
for (int i= 0; i < children.length; i++) {
IJavaElement child= children[i];
if (child instanceof IType) {
IType type= (IType)child;
if (type.isClass()) {
IField field= type.getField(NAME_FIELD);
if (!field.exists()) {
ITypeHierarchy hierarchy= type.newSupertypeHierarchy(new NullProgressMonitor());
IType[] interfaces= hierarchy.getAllSuperInterfaces(type);
for (int j= 0; j < interfaces.length; j++) {
if (interfaces[j].equals(serializable)) {
result.add(type);
break;
}
}
}
}
collectChildrenWithMissingSerialVersionId(type.getChildren(), serializable, result);
} else if (child instanceof IMethod) {
collectChildrenWithMissingSerialVersionId(((IMethod)child).getChildren(), serializable, result);
} else if (child instanceof IField) {
collectChildrenWithMissingSerialVersionId(((IField)child).getChildren(), serializable, result);
}
}
}
}
public static class SerialVersionHashBatchOperation extends AbstractSerialVersionOperationCore {
private final ISerialVersionFixContext fContext;
protected SerialVersionHashBatchOperation(ICompilationUnit unit, ASTNode[] node, ISerialVersionFixContext context) {
super(unit, node);
fContext= context;
}
@Override
protected boolean addInitializer(VariableDeclarationFragment fragment, ASTNode declarationNode) {
ITypeBinding typeBinding= getTypeBinding(declarationNode);
if (typeBinding == null)
return false;
Long id= fContext.getSerialVersionId(typeBinding);
if (id == null)
return false;
fragment.setInitializer(fragment.getAST().newNumberLiteral(id.toString() + LONG_SUFFIX));
return true;
}
@Override
protected void addLinkedPositions(ASTRewrite rewrite, VariableDeclarationFragment fragment, LinkedProposalModelCore positionGroups) {}
}
private static ISerialVersionFixContext fCurrentContext;
public static IProposableFix[] createMissingSerialVersionFixes(CompilationUnit compilationUnit, IProblemLocationCore problem) {
if (problem.getProblemId() != IProblem.MissingSerialVersion)
return null;
final ICompilationUnit unit= (ICompilationUnit)compilationUnit.getJavaElement();
if (unit == null)
return null;
final SimpleName simpleName= getSelectedName(compilationUnit, problem);
if (simpleName == null)
return null;
ASTNode declaringNode= getDeclarationNode(simpleName);
if (declaringNode == null)
return null;
SerialVersionDefaultOperationCore defop= new SerialVersionDefaultOperationCore(unit, new ASTNode[] {declaringNode});
IProposableFix fix1= new PotentialProgrammingProblemsFixCore(FixMessages.Java50Fix_SerialVersion_default_description, compilationUnit, new CompilationUnitRewriteOperation[] {defop});
SerialVersionHashOperationCore hashop= new SerialVersionHashOperationCore(unit, new ASTNode[] {declaringNode});
IProposableFix fix2= new PotentialProgrammingProblemsFixCore(FixMessages.Java50Fix_SerialVersion_hash_description, compilationUnit, new CompilationUnitRewriteOperation[] {hashop});
return new IProposableFix[] {fix1, fix2};
}
public static RefactoringStatus checkPreConditions(IJavaProject project, ICompilationUnit[] compilationUnits, IProgressMonitor monitor,
boolean calculatedId,
boolean defaultId,
boolean randomId) throws CoreException {
if (defaultId) {
fCurrentContext= new ISerialVersionFixContext() {
@Override
public Long getSerialVersionId(ITypeBinding binding) {
return Long.valueOf(1);
}
@Override
public RefactoringStatus initialize(IProgressMonitor pm) throws CoreException {
return new RefactoringStatus();
}
};
return fCurrentContext.initialize(monitor);
} else if (randomId) {
fCurrentContext= new ISerialVersionFixContext() {
private Random rng;
@Override
public Long getSerialVersionId(ITypeBinding binding) {
return Long.valueOf(rng.nextLong());
}
@Override
public RefactoringStatus initialize(IProgressMonitor pm) throws CoreException {
rng= new Random((new Date()).getTime());
return new RefactoringStatus();
}
};
return fCurrentContext.initialize(monitor);
} else if (calculatedId) {
fCurrentContext= new SerialVersionHashContext(project, compilationUnits);
return fCurrentContext.initialize(monitor);
} else {
return new RefactoringStatus();
}
}
public static RefactoringStatus checkPostConditions(IProgressMonitor monitor) {
if (monitor != null)
monitor.done();
fCurrentContext= null;
return new RefactoringStatus();
}
public static ICleanUpFixCore createCleanUp(CompilationUnit compilationUnit, boolean addSerialVersionIds) {
IProblem[] problems= compilationUnit.getProblems();
IProblemLocationCore[] locations= new IProblemLocationCore[problems.length];
for (int i= 0; i < problems.length; i++) {
locations[i]= new ProblemLocationCore(problems[i]);
}
return createCleanUp(compilationUnit, locations, addSerialVersionIds);
}
public static ICleanUpFixCore createCleanUp(CompilationUnit compilationUnit, IProblemLocationCore[] problems, boolean addSerialVersionIds) {
if (addSerialVersionIds) {
final ICompilationUnit unit= (ICompilationUnit)compilationUnit.getJavaElement();
if (unit == null)
return null;
List<ASTNode> declarationNodes= new ArrayList<>();
for (int i= 0; i < problems.length; i++) {
if (problems[i].getProblemId() == IProblem.MissingSerialVersion) {
final SimpleName simpleName= getSelectedName(compilationUnit, problems[i]);
if (simpleName != null) {
ASTNode declarationNode= getDeclarationNode(simpleName);
if (declarationNode != null) {
declarationNodes.add(declarationNode);
}
}
}
}
if (declarationNodes.size() == 0)
return null;
for (Iterator<ASTNode> iter= declarationNodes.iterator(); iter.hasNext();) {
ASTNode declarationNode= iter.next();
ITypeBinding binding= getTypeBinding(declarationNode);
if (fCurrentContext.getSerialVersionId(binding) != null) {
SerialVersionHashBatchOperation op= new SerialVersionHashBatchOperation(unit, declarationNodes.toArray(new ASTNode[declarationNodes.size()]), fCurrentContext);
return new PotentialProgrammingProblemsFixCore(FixMessages.PotentialProgrammingProblemsFix_add_id_change_name, compilationUnit, new CompilationUnitRewriteOperation[] {op});
}
}
}
return null;
}
public static SimpleName getSelectedName(CompilationUnit compilationUnit, IProblemLocationCore problem) {
final ASTNode selection= problem.getCoveredNode(compilationUnit);
if (selection == null)
return null;
Name name= null;
if (selection instanceof SimpleType) {
name= ((SimpleType) selection).getName();
} else if (selection instanceof NameQualifiedType) {
name= ((NameQualifiedType) selection).getName();
} else if (selection instanceof QualifiedType) {
name= ((QualifiedType) selection).getName();
} else if (selection instanceof ParameterizedType) {
final ParameterizedType type= (ParameterizedType) selection;
final Type raw= type.getType();
if (raw instanceof SimpleType)
name= ((SimpleType) raw).getName();
else if (raw instanceof NameQualifiedType)
name= ((NameQualifiedType) raw).getName();
else if (raw instanceof QualifiedType)
name= ((QualifiedType) raw).getName();
} else if (selection instanceof Name) {
name= (Name) selection;
}
if (name == null)
return null;
if (name.isSimpleName()) {
return (SimpleName)name;
} else {
return ((QualifiedName)name).getName();
}
}
/**
* Returns the declaration node for the originally selected node.
* @param name the name of the node
*
* @return the declaration node
*/
public static ASTNode getDeclarationNode(SimpleName name) {
ASTNode parent= name.getParent();
if (!(parent instanceof AbstractTypeDeclaration)) {
parent= parent.getParent();
if (parent instanceof ParameterizedType || parent instanceof Type)
parent= parent.getParent();
if (parent instanceof ClassInstanceCreation) {
final ClassInstanceCreation creation= (ClassInstanceCreation) parent;
parent= creation.getAnonymousClassDeclaration();
}
}
return parent;
}
/**
* Returns the type binding of the class declaration node.
*
* @param parent the node to get the type for
* @return the type binding
*/
public static ITypeBinding getTypeBinding(final ASTNode parent) {
if (parent instanceof AbstractTypeDeclaration) {
final AbstractTypeDeclaration declaration= (AbstractTypeDeclaration) parent;
return declaration.resolveBinding();
} else if (parent instanceof AnonymousClassDeclaration) {
final AnonymousClassDeclaration declaration= (AnonymousClassDeclaration) parent;
return declaration.resolveBinding();
} else if (parent instanceof ParameterizedType) {
final ParameterizedType type= (ParameterizedType) parent;
return type.resolveBinding();
}
return null;
}
protected PotentialProgrammingProblemsFixCore(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] fixRewriteOperations) {
super(name, compilationUnit, fixRewriteOperations);
}
}