blob: ecf720be61f471351cd12d849aab63cf38dcad52 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* 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
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
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.CompilationUnit;
import org.eclipse.jdt.core.dom.Name;
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.internal.corext.util.Messages;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.text.correction.ProblemLocation;
import org.eclipse.jdt.internal.ui.text.correction.SerialVersionHashOperation;
import org.eclipse.jdt.internal.ui.text.correction.SerialVersionLaunchConfigurationDelegate;
public class PotentialProgrammingProblemsFix extends AbstractFix {
/** Name of the externalizable class */
private static final String EXTERNALIZABLE_NAME= "java.io.Externalizable"; //$NON-NLS-1$
/** 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$
/** The default serial value */
private static final long SERIAL_VALUE= 1;
public interface ISerialVersionFixContext {
public long getSerialVersionId(String qualifiedName) throws CoreException;
}
private static class SerialVersionHashContext implements ISerialVersionFixContext {
private final IJavaProject fProject;
private final String[] fQualifiedNames;
private Hashtable fIdsTable;
public SerialVersionHashContext(IJavaProject project, String[] qualifiedNames) {
fProject= project;
fQualifiedNames= qualifiedNames;
}
public void initialize(IProgressMonitor monitor) throws CoreException, IOException {
fIdsTable= new Hashtable();
if (fQualifiedNames.length > 0) {
long[] ids= SerialVersionHashOperation.calculateSerialVersionIds(fQualifiedNames, fProject, monitor);
if (monitor.isCanceled())
throw new OperationCanceledException();
if (ids.length != fQualifiedNames.length) {
for (int i= 0; i < fQualifiedNames.length; i++) {
fIdsTable.put(fQualifiedNames[i], new Long(SERIAL_VALUE));
}
return;
}
for (int i= 0; i < ids.length; i++) {
long id= ids[i];
if (id != SerialVersionLaunchConfigurationDelegate.FAILING_ID)
fIdsTable.put(fQualifiedNames[i], new Long(id));
}
}
}
/**
* {@inheritDoc}
*/
public long getSerialVersionId(String qualifiedName) throws CoreException {
if (fIdsTable == null)
throw new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), 0, FixMessages.Java50Fix_SerialVersionNotInitialized_exception_description, null));
Long id= (Long)fIdsTable.get(qualifiedName);
if (id == null) {
try {
long[] ids= SerialVersionHashOperation.calculateSerialVersionIds(new String[] {qualifiedName}, fProject, new NullProgressMonitor());
if (ids.length == 0)
throw new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), 0, Messages.format(FixMessages.Java50Fix_SerialVersionNotFound_exception_description, qualifiedName), null));
fIdsTable.put(qualifiedName, new Long(ids[0]));
return ids[0];
} catch (CoreException e) {
throw new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), 0, Messages.format(FixMessages.Java50Fix_SerialVersionNotFound_exception_description, qualifiedName), e));
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), 0, Messages.format(FixMessages.Java50Fix_SerialVersionNotFound_exception_description, qualifiedName), e));
}
}
return id.longValue();
}
}
private static class SerialVersionHashBatchOperation extends AbstractSerialVersionOperation {
private final ISerialVersionFixContext fContext;
protected SerialVersionHashBatchOperation(ICompilationUnit unit, ASTNode[] node, ISerialVersionFixContext context) {
super(unit, node);
fContext= context;
}
/**
* {@inheritDoc}
*/
protected boolean addInitializer(VariableDeclarationFragment fragment, ASTNode declarationNode) throws CoreException {
long id= fContext.getSerialVersionId(getQualifiedName(declarationNode));
if (id == SerialVersionLaunchConfigurationDelegate.FAILING_ID)
return false;
fragment.setInitializer(fragment.getAST().newNumberLiteral(id + LONG_SUFFIX));
return true;
}
/**
* {@inheritDoc}
*/
protected void addLinkedPositions(ASTRewrite rewrite, VariableDeclarationFragment fragment, List positionGroups) {
//Do nothing
}
}
public static IFix[] createMissingSerialVersionFixes(CompilationUnit compilationUnit, IProblemLocation problem) throws CoreException {
if (problem.getProblemId() != IProblem.MissingSerialVersion)
return null;
final ICompilationUnit unit= (ICompilationUnit)compilationUnit.getJavaElement();
if (unit == null)
return null;
final SimpleName simpleName= getSimpleTypeName(compilationUnit, problem);
if (simpleName == null)
return null;
SerialVersionDefaultOperation defop= new SerialVersionDefaultOperation(unit, new SimpleName[] {simpleName});
IFix fix1= new PotentialProgrammingProblemsFix(FixMessages.Java50Fix_SerialVersion_default_description, compilationUnit, new IFixRewriteOperation[] {defop});
SerialVersionHashOperation hashop= new SerialVersionHashOperation(unit, new SimpleName[] {simpleName});
IFix fix2= new PotentialProgrammingProblemsFix(FixMessages.Java50Fix_SerialVersion_hash_description, compilationUnit, new IFixRewriteOperation[] {hashop});
return new IFix[] {fix1, fix2};
}
private static SerialVersionHashBatchOperation createSerialVersionHashOperation(CompilationUnit compilationUnit, IProblemLocation[] problems, ISerialVersionFixContext context) {
final ICompilationUnit unit= (ICompilationUnit)compilationUnit.getJavaElement();
if (unit == null)
return null;
List simpleNames= new ArrayList();
for (int i= 0; i < problems.length; i++) {
if (problems[i].getProblemId() == IProblem.MissingSerialVersion) {
final SimpleName simpleName= getSimpleTypeName(compilationUnit, problems[i]);
if (simpleName != null) {
simpleNames.add(simpleName);
}
}
}
if (simpleNames.size() == 0)
return null;
return new SerialVersionHashBatchOperation(unit, (SimpleName[])simpleNames.toArray(new SimpleName[simpleNames.size()]), context);
}
public static IFix createCleanUp(CompilationUnit compilationUnit,
boolean addSerialVersionIds, ISerialVersionFixContext context) {
IProblem[] problems= compilationUnit.getProblems();
IProblemLocation[] locations= new IProblemLocation[problems.length];
for (int i= 0; i < problems.length; i++) {
locations[i]= new ProblemLocation(problems[i]);
}
return createCleanUp(compilationUnit, locations, addSerialVersionIds, context);
}
public static IFix createCleanUp(CompilationUnit compilationUnit, IProblemLocation[] problems,
boolean addSerialVersionIds, ISerialVersionFixContext context) {
List operations= new ArrayList();
if (addSerialVersionIds) {
IFixRewriteOperation operation= PotentialProgrammingProblemsFix.createSerialVersionHashOperation(compilationUnit, problems, context);
if (operation != null)
operations.add(operation);
}
if (operations.isEmpty())
return null;
IFixRewriteOperation[] ops= (IFixRewriteOperation[])operations.toArray(new IFixRewriteOperation[operations.size()]);
return new PotentialProgrammingProblemsFix("", compilationUnit, ops); //$NON-NLS-1$
}
public static SerialVersionHashContext createSerialVersionHashContext(IJavaProject project, ICompilationUnit[] compilationUnits, IProgressMonitor monitor) throws CoreException {
try {
monitor.beginTask("", compilationUnits.length * 2 + 20); //$NON-NLS-1$
List qualifiedClassNames= 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. Finding out how many
//suptypes exist would be as expensive as finding the subtypes...
findWithTypeHierarchy(project, compilationUnits, qualifiedClassNames, monitor);
} else {
findWithRecursion(project, compilationUnits, qualifiedClassNames, monitor);
}
SerialVersionHashContext result= new SerialVersionHashContext(project, (String[])qualifiedClassNames.toArray(new String[qualifiedClassNames.size()]));
try {
result.initialize(new SubProgressMonitor(monitor, 20));
} catch (IOException e) {
JavaPlugin.log(e);
}
return result;
} finally {
monitor.done();
}
}
private static void findWithRecursion(IJavaProject project, ICompilationUnit[] compilationUnits, List qualifiedClassNames, IProgressMonitor monitor) throws JavaModelException {
IType serializable= project.findType(SERIALIZABLE_NAME);
IType externalizable= project.findType(EXTERNALIZABLE_NAME);
for (int i= 0; i < compilationUnits.length; i++) {
monitor.subTask(Messages.format(FixMessages.Java50Fix_InitializeSerialVersionId_subtask_description, new Object[] {project.getElementName(), compilationUnits[i].getElementName()}));
findTypesWithoutSerialVersionId(compilationUnits[i].getChildren(), serializable, externalizable, qualifiedClassNames);
if (monitor.isCanceled())
throw new OperationCanceledException();
monitor.worked(2);
}
}
private static void findWithTypeHierarchy(IJavaProject project, ICompilationUnit[] compilationUnits, List qualifiedClassNames, IProgressMonitor monitor) throws JavaModelException {
IType serializable= project.findType(SERIALIZABLE_NAME);
IType externalizable= project.findType(EXTERNALIZABLE_NAME);
HashSet cus= new HashSet();
for (int i= 0; i < compilationUnits.length; i++) {
cus.add(compilationUnits[i]);
}
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, qualifiedClassNames);
monitor.subTask(Messages.format(FixMessages.Java50Fix_SerialVersion_CalculateHierarchy_description, EXTERNALIZABLE_NAME));
ITypeHierarchy hierarchy2= externalizable.newTypeHierarchy(project, new SubProgressMonitor(monitor, compilationUnits.length));
IType[] allSubtypes2= hierarchy2.getAllSubtypes(externalizable);
addTypes(allSubtypes2, cus, qualifiedClassNames);
}
private static void addTypes(IType[] allSubtypes, HashSet cus, List qualifiedClassNames) throws JavaModelException {
for (int i= 0; i < allSubtypes.length; i++) {
IType type= allSubtypes[i];
if (type.isClass() && cus.contains(type.getCompilationUnit())){
IField field= type.getField(NAME_FIELD);
if (!field.exists()) {
qualifiedClassNames.add(type.getFullyQualifiedName());
}
}
}
}
private static void findTypesWithoutSerialVersionId(IJavaElement[] children, IType serializable, IType externalizable, List/*<String>*/ qualifiedClassNames) throws JavaModelException {
for (int i= 0; i < children.length; i++) {
IJavaElement child= children[i];
if (child instanceof IType) {
IType type= (IType)child;
ITypeHierarchy hierarchy= type.newSupertypeHierarchy(new NullProgressMonitor());
IType[] allInterfaces= hierarchy.getAllSuperInterfaces(type);
for (int j= 0; j < allInterfaces.length; j++) {
if (allInterfaces[j].equals(serializable) || allInterfaces[j].equals(externalizable)) {
IField field= type.getField(NAME_FIELD);
if (!field.exists()) {
qualifiedClassNames.add(type.getFullyQualifiedName());
}
break;
}
}
findTypesWithoutSerialVersionId(type.getChildren(), serializable, externalizable, qualifiedClassNames);
} else if (child instanceof IMethod) {
IMethod method= (IMethod)child;
findTypesWithoutSerialVersionId(method.getChildren(), serializable, externalizable, qualifiedClassNames);
} else if (child instanceof IField) {
IField field= (IField)child;
findTypesWithoutSerialVersionId(field.getChildren(), serializable, externalizable, qualifiedClassNames);
}
}
}
private static SimpleName getSimpleTypeName(CompilationUnit compilationUnit, IProblemLocation problem) {
final ASTNode selection= problem.getCoveredNode(compilationUnit);
if (selection == null)
return null;
Name name= null;
if (selection instanceof SimpleType) {
final SimpleType type= (SimpleType) selection;
name= type.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 QualifiedType)
name= ((QualifiedType) raw).getName();
} else if (selection instanceof Name) {
name= (Name) selection;
}
if (name == null)
return null;
final SimpleName result= name.isSimpleName() ? (SimpleName) name : ((QualifiedName) name).getName();
return result;
}
protected PotentialProgrammingProblemsFix(String name, CompilationUnit compilationUnit, IFixRewriteOperation[] fixRewriteOperations) {
super(name, compilationUnit, fixRewriteOperations);
}
}