blob: 0f588622d6fe2f35110e6b1f8b2a335d730a0749 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 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
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.rename;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ltk.core.refactoring.GroupCategorySet;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
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.Signature;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringAvailabilityTester;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
public class RenameVirtualMethodProcessor extends RenameMethodProcessor {
private IMethod fOriginalMethod;
private boolean fActivationChecked;
private ITypeHierarchy fCachedHierarchy= null;
/**
* Creates a new rename method processor.
*
* @param method the method
*/
public RenameVirtualMethodProcessor(IMethod method) {
super(method);
fOriginalMethod= getMethod();
}
/**
* Creates a new rename method processor from arguments
*
* @param method the method
* @param arguments the arguments
* @param status the resulting status
*/
public RenameVirtualMethodProcessor(IMethod method, JavaRefactoringArguments arguments, RefactoringStatus status) {
this(method);
RefactoringStatus initializeStatus= initialize(arguments);
status.merge(initializeStatus);
fOriginalMethod= getMethod();
}
/*
* int. not javadoc'd
*
* Protected constructor; only called from RenameTypeProcessor. Initializes
* the method processor with an already resolved top level and ripple
* methods.
*
*/
RenameVirtualMethodProcessor(IMethod topLevel, IMethod[] ripples, TextChangeManager changeManager, ITypeHierarchy hierarchy, GroupCategorySet categorySet) {
super(topLevel, changeManager, categorySet);
fOriginalMethod= getMethod();
fActivationChecked= true; // is top level
fCachedHierarchy= hierarchy; // may be null
setMethodsToRename(ripples);
}
public IMethod getOriginalMethod() {
return fOriginalMethod;
}
private ITypeHierarchy getCachedHierarchy(IType declaring, IProgressMonitor monitor) throws JavaModelException {
if (fCachedHierarchy != null && declaring.equals(fCachedHierarchy.getType()))
return fCachedHierarchy;
fCachedHierarchy= declaring.newTypeHierarchy(new SubProgressMonitor(monitor, 1));
return fCachedHierarchy;
}
@Override
public boolean isApplicable() throws CoreException {
return RefactoringAvailabilityTester.isRenameVirtualMethodAvailable(getMethod());
}
//------------ preconditions -------------
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor monitor) throws CoreException {
RefactoringStatus result= super.checkInitialConditions(monitor);
if (result.hasFatalError())
return result;
try{
monitor.beginTask("", 3); //$NON-NLS-1$
if (!fActivationChecked) {
// the following code may change the method to be changed.
IMethod method= getMethod();
fOriginalMethod= method;
ITypeHierarchy hierarchy= null;
IType declaringType= method.getDeclaringType();
if (!declaringType.isInterface())
hierarchy= getCachedHierarchy(declaringType, new SubProgressMonitor(monitor, 1));
IMethod topmost= getMethod();
if (MethodChecks.isVirtual(topmost))
topmost= MethodChecks.getTopmostMethod(getMethod(), hierarchy, monitor);
if (topmost != null)
initialize(topmost);
fActivationChecked= true;
}
} finally{
monitor.done();
}
return result;
}
@Override
protected RefactoringStatus doCheckFinalConditions(IProgressMonitor pm, CheckConditionsContext checkContext) throws CoreException {
try{
pm.beginTask("", 9); //$NON-NLS-1$
RefactoringStatus result= new RefactoringStatus();
result.merge(super.doCheckFinalConditions(new SubProgressMonitor(pm, 7), checkContext));
if (result.hasFatalError())
return result;
final IMethod method= getMethod();
final IType declaring= method.getDeclaringType();
final ITypeHierarchy hierarchy= getCachedHierarchy(declaring, new SubProgressMonitor(pm, 1));
final String name= getNewElementName();
if (declaring.isInterface()) {
if (isSpecialCase())
result.addError(RefactoringCoreMessages.RenameMethodInInterfaceRefactoring_special_case);
pm.worked(1);
for (IMethod relatedMethod : relatedTypeDeclaresMethodName(new SubProgressMonitor(pm, 1), method, name)) {
RefactoringStatusContext context= JavaStatusContext.create(relatedMethod);
result.addError(RefactoringCoreMessages.RenameMethodInInterfaceRefactoring_already_defined, context);
}
} else {
if (classesDeclareOverridingNativeMethod(hierarchy.getAllSubtypes(declaring))) {
result.addError(Messages.format(
RefactoringCoreMessages.RenameVirtualMethodRefactoring_requieres_renaming_native,
new String[]{ BasicElementLabels.getJavaElementName(method.getElementName()), "UnsatisfiedLinkError"})); //$NON-NLS-1$
}
for (IMethod hierarchyMethod : hierarchyDeclaresMethodName(new SubProgressMonitor(pm, 1), hierarchy, method, name)) {
RefactoringStatusContext context= JavaStatusContext.create(hierarchyMethod);
if (Checks.compareParamTypes(method.getParameterTypes(), hierarchyMethod.getParameterTypes())) {
result.addError(Messages.format(
RefactoringCoreMessages.RenameVirtualMethodRefactoring_hierarchy_declares2,
BasicElementLabels.getJavaElementName(name)), context);
} else {
result.addWarning(Messages.format(
RefactoringCoreMessages.RenameVirtualMethodRefactoring_hierarchy_declares1,
BasicElementLabels.getJavaElementName(name)), context);
}
}
}
fCachedHierarchy= null;
return result;
} finally{
pm.done();
}
}
//---- Interface checks -------------------------------------
private IMethod[] relatedTypeDeclaresMethodName(IProgressMonitor pm, IMethod method, String newName) throws CoreException {
try{
Set<IMethod> result= new HashSet<>();
Set<IType> types= getRelatedTypes();
pm.beginTask("", types.size()); //$NON-NLS-1$
for (IType type : types) {
final IMethod found= Checks.findMethod(method, type);
final IType declaring= found.getDeclaringType();
result.addAll(Arrays.asList(hierarchyDeclaresMethodName(new SubProgressMonitor(pm, 1), declaring.newTypeHierarchy(new SubProgressMonitor(pm, 1)), found, newName)));
}
return result.toArray(new IMethod[result.size()]);
} finally {
pm.done();
}
}
private boolean isSpecialCase() throws CoreException {
String[] noParams= new String[0];
String[] specialNames= new String[]{"toString", "toString", "toString", "toString", "equals", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
"equals", "getClass", "getClass", "hashCode", "notify", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
"notifyAll", "wait", "wait", "wait"}; //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
String[][] specialParamTypes= new String[][]{noParams, noParams, noParams, noParams,
{"QObject;"}, {"Qjava.lang.Object;"}, noParams, noParams, //$NON-NLS-2$ //$NON-NLS-1$
noParams, noParams, noParams, {Signature.SIG_LONG, Signature.SIG_INT},
{Signature.SIG_LONG}, noParams};
String[] specialReturnTypes= new String[]{"QString;", "QString;", "Qjava.lang.String;", "Qjava.lang.String;", //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
Signature.SIG_BOOLEAN, Signature.SIG_BOOLEAN, "QClass;", "Qjava.lang.Class;", //$NON-NLS-2$ //$NON-NLS-1$
Signature.SIG_INT, Signature.SIG_VOID, Signature.SIG_VOID, Signature.SIG_VOID,
Signature.SIG_VOID, Signature.SIG_VOID};
Assert.isTrue((specialNames.length == specialParamTypes.length) && (specialParamTypes.length == specialReturnTypes.length));
for (int i= 0; i < specialNames.length; i++){
if (specialNames[i].equals(getNewElementName())
&& Checks.compareParamTypes(getMethod().getParameterTypes(), specialParamTypes[i])
&& !specialReturnTypes[i].equals(getMethod().getReturnType())){
return true;
}
}
return false;
}
private Set<IType> getRelatedTypes() {
Set<IMethod> methods= getMethodsToRename();
Set<IType> result= new HashSet<>(methods.size());
for (IMethod method : methods) {
result.add(method.getDeclaringType());
}
return result;
}
//---- Class checks -------------------------------------
private boolean classesDeclareOverridingNativeMethod(IType[] classes) throws CoreException {
for (IType type : classes) {
for (IMethod method : type.getMethods()) {
if ((!method.equals(getMethod())) && (JdtFlags.isNative(method)) && (null != Checks.findSimilarMethod(getMethod(), new IMethod[]{method}))) {
return true;
}
}
}
return false;
}
@Override
public String getDelegateUpdatingTitle(boolean plural) {
if (plural)
return RefactoringCoreMessages.DelegateMethodCreator_keep_original_renamed_plural;
else
return RefactoringCoreMessages.DelegateMethodCreator_keep_original_renamed_singular;
}
}