From 263ff3c22c91211ce9eb895ea7b825b07dee854b Mon Sep 17 00:00:00 2001 From: Shi Chen Date: Wed, 8 Feb 2023 10:07:37 +0800 Subject: [PATCH] Support change signature refactoring Signed-off-by: Shi Chen --- .../CuCollectingSearchRequestor.java | 98 + .../corext/refactoring/ExceptionInfo.java | 103 + .../corext/refactoring/ParameterInfo.java | 232 ++ .../corext/refactoring/ReturnTypeInfo.java | 61 + .../corext/refactoring/StubTypeContext.java | 42 + .../refactoring/TypeContextChecker.java | 864 +++++ .../changes/CreatePackageChange.java | 82 + .../rename/RippleMethodFinder2.java | 539 +++ .../refactoring/structure/BodyUpdater.java | 48 + .../structure/ChangeSignatureProcessor.java | 2914 +++++++++++++++++ .../structure/IDefaultValueAdvisor.java | 49 + .../IntroduceParameterObjectProcessor.java | 746 +++++ .../structure/ParameterObjectFactory.java | 728 ++++ .../JavaTypeCompletionProcessorCore.java | 28 + .../ls/core/internal/JavaCodeActionKind.java | 5 + .../RefactoringAvailabilityTester.java | 2 +- .../refactoring/RefactoringCoreMessages.java | 2 + .../corext/refactoring/refactoring.properties | 1 + .../corrections/RefactorProcessor.java | 16 + .../handlers/ChangeSignatureHandler.java | 168 + .../handlers/GetRefactorEditHandler.java | 29 + .../correction/RefactorProposalUtility.java | 61 + .../handlers/ChangeSignatureHandlerTest.java | 160 + 23 files changed, 6977 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/CuCollectingSearchRequestor.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ExceptionInfo.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ParameterInfo.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ReturnTypeInfo.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/StubTypeContext.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/TypeContextChecker.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/changes/CreatePackageChange.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/rename/RippleMethodFinder2.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/BodyUpdater.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/ChangeSignatureProcessor.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/IDefaultValueAdvisor.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/IntroduceParameterObjectProcessor.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/ParameterObjectFactory.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/ui/refactoring/contentassist/JavaTypeCompletionProcessorCore.java create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/ChangeSignatureHandler.java create mode 100644 org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ChangeSignatureHandlerTest.java diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/CuCollectingSearchRequestor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/CuCollectingSearchRequestor.java new file mode 100644 index 0000000000..6e85bfb324 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/CuCollectingSearchRequestor.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011 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; + +import org.eclipse.core.runtime.CoreException; + +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.ToolFactory; +import org.eclipse.jdt.core.compiler.IScanner; +import org.eclipse.jdt.core.search.SearchMatch; + +import org.eclipse.jdt.internal.corext.refactoring.base.ReferencesInBinaryContext; +import org.eclipse.jdt.internal.corext.util.SearchUtils; + +/** + * Collects the results returned by a SearchEngine. + * Only collects matches in CUs ands offers a scanner to trim match ranges. + * If a {@link ReferencesInBinaryContext} is passed, matches that are + * inside a binary element are not collected (but added to the context if they are accurate). + */ +public class CuCollectingSearchRequestor extends CollectingSearchRequestor { + + private IJavaProject fProjectCache; + private IScanner fScannerCache; + + public CuCollectingSearchRequestor() { + this(null); + } + + public CuCollectingSearchRequestor(ReferencesInBinaryContext binaryRefs) { + super(binaryRefs); + } + + protected IScanner getScanner(ICompilationUnit unit) { + IJavaProject project= unit.getJavaProject(); + if (project.equals(fProjectCache)) + return fScannerCache; + + fProjectCache= project; + String sourceLevel= project.getOption(JavaCore.COMPILER_SOURCE, true); + String complianceLevel= project.getOption(JavaCore.COMPILER_COMPLIANCE, true); + fScannerCache= ToolFactory.createScanner(false, false, false, sourceLevel, complianceLevel); + return fScannerCache; + } + + /** + * This is an internal method. Do not call from subclasses! + * Use {@link #collectMatch(SearchMatch)} instead. + * @param match + * @throws CoreException + * @deprecated + */ + @Deprecated + @Override + public final void acceptSearchMatch(SearchMatch match) throws CoreException { + if (filterMatch(match)) + return; + + ICompilationUnit unit= SearchUtils.getCompilationUnit(match); + if (unit != null) { + acceptSearchMatch(unit, match); + } + } + + /** + * Handles the given match in the given compilation unit. + * The default implementation accepts all matches. + * Subclasses can override and call {@link #collectMatch(SearchMatch)} to collect matches. + * + * @param unit the enclosing CU of the match, never null + * @param match the match + * @throws CoreException if something bad happens + */ + protected void acceptSearchMatch(ICompilationUnit unit, SearchMatch match) throws CoreException { + collectMatch(match); + } + + @Override + public void endReporting() { + fProjectCache= null; + fScannerCache= null; + } +} + + diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ExceptionInfo.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ExceptionInfo.java new file mode 100644 index 0000000000..ea0980b5a0 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ExceptionInfo.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011 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; + +import org.eclipse.core.runtime.Assert; + +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeParameter; +import org.eclipse.jdt.core.dom.ITypeBinding; + + +public class ExceptionInfo { + private final IJavaElement fElement; + private final ITypeBinding fTypeBinding; + private int fKind; + + public static final int OLD= 0; + public static final int ADDED= 1; + public static final int DELETED= 2; + + public ExceptionInfo(IJavaElement element, int kind, ITypeBinding binding) { + Assert.isNotNull(element); + Assert.isTrue(element instanceof IType || element instanceof ITypeParameter); + fElement= element; + fKind= kind; + fTypeBinding= binding; + } + + public static ExceptionInfo createInfoForOldException(IJavaElement element, ITypeBinding binding){ + return new ExceptionInfo(element, OLD, binding); + } + public static ExceptionInfo createInfoForAddedException(IType type){ + return new ExceptionInfo(type, ADDED, null); + } + + public void markAsDeleted(){ + Assert.isTrue(! isAdded());//added exception infos should be simply removed from the list + fKind= DELETED; + } + + public void markAsOld(){ + Assert.isTrue(isDeleted()); + fKind= OLD; + } + + public boolean isAdded(){ + return fKind == ADDED; + } + + public boolean isDeleted(){ + return fKind == DELETED; + } + + public boolean isOld(){ + return fKind == OLD; + } + + public IJavaElement getElement() { + return fElement; + } + + public String getFullyQualifiedName() { + return fElement instanceof IType ? ((IType) fElement).getFullyQualifiedName('.') : fElement.getElementName(); + } + + public int getKind() { + return fKind; + } + + /** + * @return ITypeBinding the typeBinding (for OLD and DELETED exceptions) or null + */ + public ITypeBinding getTypeBinding() { + return fTypeBinding; + } + + @Override + public String toString() { + StringBuilder result= new StringBuilder(); + switch (fKind) { + case OLD : result.append("OLD: "); break; //$NON-NLS-1$ + case ADDED : result.append("ADDED: "); break; //$NON-NLS-1$ + case DELETED : result.append("DELETED: "); break; //$NON-NLS-1$ + } + if (fElement == null) + result.append("null"); //$NON-NLS-1$ + else + result.append(fElement.toString()); + return result.toString(); + } +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ParameterInfo.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ParameterInfo.java new file mode 100644 index 0000000000..685d438853 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ParameterInfo.java @@ -0,0 +1,232 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011 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; + +import org.eclipse.core.runtime.Assert; + +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; + + +public class ParameterInfo { + + public static final int INDEX_FOR_ADDED= -1; + public static final String ELLIPSIS= "..."; //$NON-NLS-1$ + + private IVariableBinding fOldBinding; + private ITypeBinding fOldTypeBinding; + private final String fOldName; + private final String fOldTypeName; + private final int fOldIndex; + + private String fNewTypeName; + private ITypeBinding fNewTypeBinding; + private String fDefaultValue; + private String fNewName; + private boolean fIsDeleted; + private boolean fCreateField=true; + private boolean fInlined; + private boolean fResolve= true; + + public ParameterInfo(String type, String name, int index) { + this(null, null, type, name, index); + } + + public ParameterInfo(IVariableBinding binding, String type, String name, int index) { + this(binding, null, type, name, index); + } + + private ParameterInfo(IVariableBinding binding, ITypeBinding typeBinding, String type, String name, int index) { + fOldBinding= binding; + fOldTypeBinding= typeBinding; + fNewTypeBinding= typeBinding; + fOldTypeName= type; + fNewTypeName= type; + fOldName= name; + fNewName= name; + fOldIndex= index; + fDefaultValue= ""; //$NON-NLS-1$ + fIsDeleted= false; + } + + + /** + * Creates a new ParameterInfo. Parameter is marked as added and not resolvable + * @param type the fullyqualified type + * @param name the name + * @return the parameter info object + */ + public static ParameterInfo createInfoForAddedParameter(String type, String name) { + ParameterInfo info= new ParameterInfo("", "", INDEX_FOR_ADDED); //$NON-NLS-1$ //$NON-NLS-2$ + info.setNewTypeName(type); + info.setNewName(name); + info.setResolve(false); + return info; + } + + private void setResolve(boolean resolve) { + fResolve= resolve; + } + + public static ParameterInfo createInfoForAddedParameter(String type, String name, String defaultValue) { + ParameterInfo info= new ParameterInfo("", "", INDEX_FOR_ADDED); //$NON-NLS-1$ //$NON-NLS-2$ + info.setNewTypeName(type); + info.setNewName(name); + info.setDefaultValue(defaultValue); + return info; + } + + public static ParameterInfo createInfoForAddedParameter(ITypeBinding typeBinding, String type, String name, String defaultValue) { + ParameterInfo info= new ParameterInfo(null, typeBinding, "", "", INDEX_FOR_ADDED); //$NON-NLS-1$ //$NON-NLS-2$ + info.setNewTypeName(type); + info.setNewName(name); + info.setDefaultValue(defaultValue); + return info; + } + + public int getOldIndex() { + return fOldIndex; + } + + public boolean isDeleted(){ + return fIsDeleted; + } + + public void markAsDeleted(){ + Assert.isTrue(! isAdded());//added param infos should be simply removed from the list + fIsDeleted= true; + } + + public boolean isAdded(){ + return fOldIndex == INDEX_FOR_ADDED; + } + + public boolean isTypeNameChanged() { + return !fOldTypeName.equals(fNewTypeName); + } + + public boolean isRenamed() { + return !fOldName.equals(fNewName); + } + + public boolean isVarargChanged() { + return isOldVarargs() != isNewVarargs(); + } + + public IVariableBinding getOldBinding() { + return fOldBinding; + } + + public String getOldTypeName() { + return fOldTypeName; + } + + public String getNewTypeName() { + return fNewTypeName; + } + + public void setNewTypeName(String type){ + Assert.isNotNull(type); + fNewTypeName= type; + } + + public ITypeBinding getNewTypeBinding() { + return fNewTypeBinding; + } + + public void setNewTypeBinding(ITypeBinding typeBinding){ + fNewTypeBinding= typeBinding; + } + + public boolean isOldVarargs() { + return isVarargs(fOldTypeName); + } + + public boolean isNewVarargs() { + return isVarargs(fNewTypeName); + } + + public String getOldName() { + return fOldName; + } + + public String getNewName() { + return fNewName; + } + + public void setNewName(String newName) { + Assert.isNotNull(newName); + fNewName= newName; + } + + public String getDefaultValue(){ + return fDefaultValue; + } + + public void setDefaultValue(String value){ + Assert.isNotNull(value); + fDefaultValue= value; + } + + @Override + public String toString() { + return fOldTypeName + " " + fOldName + " @" + fOldIndex + " -> " //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + + fNewTypeName + " " + fNewName + ": " + fDefaultValue //$NON-NLS-1$//$NON-NLS-2$ + + (fIsDeleted ? " (deleted)" : " (stays)"); //$NON-NLS-1$//$NON-NLS-2$ + } + + public static String stripEllipsis(String typeName) { + if (isVarargs(typeName)) + return typeName.substring(0, typeName.length() - 3); + else + return typeName; + } + + public static boolean isVarargs(String typeName) { + return typeName.endsWith("..."); //$NON-NLS-1$ + } + + public ITypeBinding getOldTypeBinding() { + return fOldTypeBinding; + } + + public boolean isCreateField() { + return fCreateField; + } + + public void setCreateField(boolean createField) { + fIsDeleted= createField; + fCreateField= createField; + } + + public void setOldBinding(IVariableBinding binding) { + //The variableBinding is needed by IPOR to check what modifier were present + fOldBinding=binding; + fOldTypeBinding=binding.getType(); + fNewTypeBinding=binding.getType(); + } + + public void setInlined(boolean inlined) { + fInlined=inlined; + } + + public boolean isInlined() { + return fInlined; + } + + public boolean isResolve() { + return fResolve; + } + +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ReturnTypeInfo.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ReturnTypeInfo.java new file mode 100644 index 0000000000..e737bc7f93 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/ReturnTypeInfo.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011 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; + +import org.eclipse.core.runtime.Assert; + +import org.eclipse.jdt.core.dom.ITypeBinding; + + +public class ReturnTypeInfo { + + private final String fOldTypeName; + private String fNewTypeName; + private ITypeBinding fNewTypeBinding; + + public ReturnTypeInfo(String returnType) { + fOldTypeName= returnType; + fNewTypeName= returnType; + } + + public String getOldTypeName() { + return fOldTypeName; + } + + public String getNewTypeName() { + return fNewTypeName; + } + + public void setNewTypeName(String type){ + Assert.isNotNull(type); + fNewTypeName= type; + } + + public ITypeBinding getNewTypeBinding() { + return fNewTypeBinding; + } + + public void setNewTypeBinding(ITypeBinding typeBinding){ + fNewTypeBinding= typeBinding; + } + + public boolean isTypeNameChanged() { + return !fOldTypeName.equals(fNewTypeName); + } + + @Override + public String toString() { + return fOldTypeName + " -> " + fNewTypeName; //$NON-NLS-1$ + } +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/StubTypeContext.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/StubTypeContext.java new file mode 100644 index 0000000000..54e66ad636 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/StubTypeContext.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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; + + import org.eclipse.jdt.core.ICompilationUnit; + + + public class StubTypeContext { + private String fBeforeString; + private String fAfterString; + private final ICompilationUnit fCuHandle; + + public StubTypeContext(ICompilationUnit cuHandle, String beforeString, String afterString) { + fCuHandle= cuHandle; + fBeforeString= beforeString; + fAfterString= afterString; + } + + public ICompilationUnit getCuHandle() { + return fCuHandle; + } + + public String getBeforeString() { + return fBeforeString; + } + + public String getAfterString() { + return fAfterString; + } + } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/TypeContextChecker.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/TypeContextChecker.java new file mode 100644 index 0000000000..3a8e501b6a --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/TypeContextChecker.java @@ -0,0 +1,864 @@ +/******************************************************************************* + * Copyright (c) 2000, 2020 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; + + import java.util.ArrayList; + import java.util.Collections; + import java.util.HashMap; + import java.util.List; + import java.util.Map; + + import org.eclipse.core.runtime.Assert; + import org.eclipse.core.runtime.CoreException; + import org.eclipse.core.runtime.IProgressMonitor; + import org.eclipse.core.runtime.NullProgressMonitor; + + import org.eclipse.core.resources.IProject; + + import org.eclipse.ltk.core.refactoring.RefactoringStatus; + + import org.eclipse.jdt.core.Flags; + import org.eclipse.jdt.core.ICompilationUnit; + import org.eclipse.jdt.core.IJavaProject; + import org.eclipse.jdt.core.IMethod; + import org.eclipse.jdt.core.IPackageFragment; + import org.eclipse.jdt.core.IPackageFragmentRoot; + import org.eclipse.jdt.core.ISourceRange; + import org.eclipse.jdt.core.IType; + import org.eclipse.jdt.core.ITypeParameter; + import org.eclipse.jdt.core.JavaCore; + import org.eclipse.jdt.core.JavaModelException; + import org.eclipse.jdt.core.WorkingCopyOwner; + 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.AnnotationTypeDeclaration; + import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; + import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; + import org.eclipse.jdt.core.dom.ArrayType; + import org.eclipse.jdt.core.dom.Block; + import org.eclipse.jdt.core.dom.BodyDeclaration; + import org.eclipse.jdt.core.dom.ClassInstanceCreation; + import org.eclipse.jdt.core.dom.CompilationUnit; + import org.eclipse.jdt.core.dom.EnumDeclaration; + import org.eclipse.jdt.core.dom.IExtendedModifier; + import org.eclipse.jdt.core.dom.ITypeBinding; + import org.eclipse.jdt.core.dom.ImportDeclaration; + import org.eclipse.jdt.core.dom.MethodDeclaration; + import org.eclipse.jdt.core.dom.Modifier; + import org.eclipse.jdt.core.dom.Name; + import org.eclipse.jdt.core.dom.NameQualifiedType; + import org.eclipse.jdt.core.dom.NodeFinder; + import org.eclipse.jdt.core.dom.PackageDeclaration; + import org.eclipse.jdt.core.dom.PrimitiveType; + import org.eclipse.jdt.core.dom.QualifiedName; + import org.eclipse.jdt.core.dom.QualifiedType; + import org.eclipse.jdt.core.dom.RecordDeclaration; + import org.eclipse.jdt.core.dom.SimpleName; + import org.eclipse.jdt.core.dom.SingleVariableDeclaration; + import org.eclipse.jdt.core.dom.Type; + import org.eclipse.jdt.core.dom.TypeDeclaration; + import org.eclipse.jdt.core.dom.TypeParameter; + import org.eclipse.jdt.core.manipulation.TypeNameMatchCollector; + import org.eclipse.jdt.core.search.IJavaSearchConstants; + import org.eclipse.jdt.core.search.IJavaSearchScope; + import org.eclipse.jdt.core.search.SearchEngine; + import org.eclipse.jdt.core.search.SearchPattern; + import org.eclipse.jdt.core.search.TypeNameMatch; + + import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin; + import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; + import org.eclipse.jdt.internal.corext.dom.ASTFlattener; + import org.eclipse.jdt.internal.corext.dom.ASTNodes; + import org.eclipse.jdt.internal.corext.dom.HierarchicalASTVisitor; + import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; + import org.eclipse.jdt.internal.corext.dom.Selection; + import org.eclipse.jdt.internal.corext.dom.SelectionAnalyzer; + import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; + import org.eclipse.jdt.internal.corext.util.JavaModelUtil; + import org.eclipse.jdt.internal.corext.util.Messages; + + import org.eclipse.jdt.internal.ui.refactoring.contentassist.JavaTypeCompletionProcessorCore; + + public class TypeContextChecker { + private static class MethodTypesChecker { + + private static final String METHOD_NAME= "__$$__"; //$NON-NLS-1$ + + private final IMethod fMethod; + private final StubTypeContext fStubTypeContext; + private final List fParameterInfos; + private final ReturnTypeInfo fReturnTypeInfo; + + public MethodTypesChecker(IMethod method, StubTypeContext stubTypeContext, List parameterInfos, ReturnTypeInfo returnTypeInfo) { + fMethod= method; + fStubTypeContext= stubTypeContext; + fParameterInfos= parameterInfos; + fReturnTypeInfo= returnTypeInfo; + } + + public RefactoringStatus[] checkAndResolveMethodTypes() throws CoreException { + RefactoringStatus[] results= new MethodTypesSyntaxChecker(fMethod, fParameterInfos, fReturnTypeInfo).checkSyntax(); + for (RefactoringStatus result : results) { + if (result != null && result.hasFatalError()) { + return results; + } + } + + int parameterCount= fParameterInfos.size(); + String[] types= new String[parameterCount + 1]; + for (int i= 0; i < parameterCount; i++) + types[i]= ParameterInfo.stripEllipsis((fParameterInfos.get(i)).getNewTypeName()); + types[parameterCount]= fReturnTypeInfo.getNewTypeName(); + RefactoringStatus[] semanticsResults= new RefactoringStatus[parameterCount + 1]; + ITypeBinding[] typeBindings= resolveBindings(types, semanticsResults, true); + + boolean needsSecondPass= false; + for (int i= 0; i < types.length; i++) + if (typeBindings[i] == null || ! semanticsResults[i].isOK()) + needsSecondPass= true; + + RefactoringStatus[] semanticsResults2= new RefactoringStatus[parameterCount + 1]; + if (needsSecondPass) + typeBindings= resolveBindings(types, semanticsResults2, false); + + for (int i= 0; i < fParameterInfos.size(); i++) { + ParameterInfo parameterInfo= fParameterInfos.get(i); + if (!parameterInfo.isResolve()) + continue; + if (parameterInfo.getOldTypeBinding() != null && ! parameterInfo.isTypeNameChanged()) { + parameterInfo.setNewTypeBinding(parameterInfo.getOldTypeBinding()); + } else { + parameterInfo.setNewTypeBinding(typeBindings[i]); + if (typeBindings[i] == null || (needsSecondPass && ! semanticsResults2[i].isOK())) { + if (results[i] == null) + results[i]= semanticsResults2[i]; + else + results[i].merge(semanticsResults2[i]); + } + } + } + fReturnTypeInfo.setNewTypeBinding(typeBindings[fParameterInfos.size()]); + if (typeBindings[parameterCount] == null || (needsSecondPass && ! semanticsResults2[parameterCount].isOK())) { + if (results[parameterCount] == null) + results[parameterCount]= semanticsResults2[parameterCount]; + else + results[parameterCount].merge(semanticsResults2[parameterCount]); + } + + return results; + } + + private ITypeBinding[] resolveBindings(String[] types, RefactoringStatus[] results, boolean firstPass) throws CoreException { + //TODO: split types into parameterTypes and returnType + int parameterCount= types.length - 1; + ITypeBinding[] typeBindings= new ITypeBinding[types.length]; + + StringBuilder cuString= new StringBuilder(); + cuString.append(fStubTypeContext.getBeforeString()); + int offsetBeforeMethodName= appendMethodDeclaration(cuString, types, parameterCount); + cuString.append(fStubTypeContext.getAfterString()); + + // need a working copy to tell the parser where to resolve (package visible) types + ICompilationUnit wc= fMethod.getCompilationUnit().getWorkingCopy(new WorkingCopyOwner() {/*subclass*/}, new NullProgressMonitor()); + try { + wc.getBuffer().setContents(cuString.toString()); + CompilationUnit compilationUnit= new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL).parse(wc, true); + ASTNode method= NodeFinder.perform(compilationUnit, offsetBeforeMethodName, METHOD_NAME.length()).getParent(); + Type[] typeNodes= new Type[types.length]; + if (method instanceof MethodDeclaration) { + MethodDeclaration methodDeclaration= (MethodDeclaration) method; + typeNodes[parameterCount]= methodDeclaration.getReturnType2(); + List parameters= methodDeclaration.parameters(); + for (int i= 0; i < parameterCount; i++) + typeNodes[i]= parameters.get(i).getType(); + + } else if (method instanceof AnnotationTypeMemberDeclaration) { + typeNodes[0]= ((AnnotationTypeMemberDeclaration) method).getType(); + } + + for (int i= 0; i < types.length; i++) { + Type type= typeNodes[i]; + if (type == null) { + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_couldNotResolveType, BasicElementLabels.getJavaElementName(types[i])); + results[i]= RefactoringStatus.createErrorStatus(msg); + continue; + } + results[i]= new RefactoringStatus(); + for (IProblem problem : ASTNodes.getProblems(type, ASTNodes.NODE_ONLY, ASTNodes.PROBLEMS)) { + if (isError(problem, type)) { + results[i].addError(problem.getMessage()); + } + } + ITypeBinding binding= handleBug84585(type.resolveBinding()); + if (firstPass && (binding == null || binding.isRecovered())) { + types[i]= qualifyTypes(type, results[i]); + } + typeBindings[i]= binding; + } + return typeBindings; + } finally { + wc.discardWorkingCopy(); + } + } + + /** + * Decides if a problem matters. + * @param problem the problem + * @param type the current type + * @return return if a problem matters. + */ + private boolean isError(IProblem problem, Type type) { + return true; + } + + private int appendMethodDeclaration(StringBuilder cuString, String[] types, int parameterCount) throws JavaModelException { + int flags= fMethod.getFlags(); + if (Flags.isStatic(flags)) { + cuString.append("static "); //$NON-NLS-1$ + } else if (Flags.isDefaultMethod(flags)) { + cuString.append("default "); //$NON-NLS-1$ + } + + ITypeParameter[] methodTypeParameters= fMethod.getTypeParameters(); + if (methodTypeParameters.length != 0) { + cuString.append('<'); + for (int i= 0; i < methodTypeParameters.length; i++) { + ITypeParameter typeParameter= methodTypeParameters[i]; + if (i > 0) + cuString.append(','); + cuString.append(typeParameter.getElementName()); + } + cuString.append("> "); //$NON-NLS-1$ + } + + cuString.append(types[parameterCount]).append(' '); + int offsetBeforeMethodName= cuString.length(); + cuString.append(METHOD_NAME).append('('); + for (int i= 0; i < parameterCount; i++) { + if (i > 0) + cuString.append(','); + cuString.append(types[i]).append(" p").append(i); //$NON-NLS-1$ + } + cuString.append(");"); //$NON-NLS-1$ + + return offsetBeforeMethodName; + } + + private String qualifyTypes(Type type, final RefactoringStatus result) throws CoreException { + class NestedException extends RuntimeException { + private static final long serialVersionUID= 1L; + NestedException(CoreException e) { + super(e); + } + } + ASTFlattener flattener= new ASTFlattener() { + @Override + public boolean visit(SimpleName node) { + appendResolved(node.getIdentifier()); + return false; + } + @Override + public boolean visit(QualifiedName node) { + appendResolved(node.getFullyQualifiedName()); + return false; + } + @Override + public boolean visit(QualifiedType node) { + appendResolved(ASTNodes.getQualifiedTypeName(node)); + return false; + } + @Override + public boolean visit(NameQualifiedType node) { + appendResolved(ASTNodes.getQualifiedTypeName(node)); + return false; + } + private void appendResolved(String typeName) { + String resolvedType; + try { + resolvedType= resolveType(typeName, result, fMethod.getDeclaringType(), null); + } catch (CoreException e) { + throw new NestedException(e); + } + this.fBuffer.append(resolvedType); + } + }; + try { + type.accept(flattener); + } catch (NestedException e) { + throw ((CoreException) e.getCause()); + } + return flattener.getResult(); + } + + private static String resolveType(String elementTypeName, RefactoringStatus status, IType declaringType, IProgressMonitor pm) throws CoreException { + String[][] fqns= declaringType.resolveType(elementTypeName); + if (fqns != null) { + if (fqns.length == 1) { + return JavaModelUtil.concatenateName(fqns[0][0], fqns[0][1]); + } else if (fqns.length > 1){ + String[] keys= { BasicElementLabels.getJavaElementName(elementTypeName), String.valueOf(fqns.length)}; + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_ambiguous, keys); + status.addError(msg); + return elementTypeName; + } + } + + List typeRefsFound= findTypeInfos(elementTypeName, declaringType, pm); + if (typeRefsFound.isEmpty()){ + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_not_unique, BasicElementLabels.getJavaElementName(elementTypeName)); + status.addError(msg); + return elementTypeName; + } else if (typeRefsFound.size() == 1){ + TypeNameMatch typeInfo= typeRefsFound.get(0); + return typeInfo.getFullyQualifiedName(); + } else { + Assert.isTrue(typeRefsFound.size() > 1); + String[] keys= {BasicElementLabels.getJavaElementName(elementTypeName), String.valueOf(typeRefsFound.size())}; + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_ambiguous, keys); + status.addError(msg); + return elementTypeName; + } + } + + private static List findTypeInfos(String typeName, IType contextType, IProgressMonitor pm) throws JavaModelException { + IJavaSearchScope scope= SearchEngine.createJavaSearchScope(new IJavaProject[]{contextType.getJavaProject()}, true); + IPackageFragment currPackage= contextType.getPackageFragment(); + ArrayList collectedInfos= new ArrayList<>(); + TypeNameMatchCollector requestor= new TypeNameMatchCollector(collectedInfos); + int matchMode= SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE; + new SearchEngine().searchAllTypeNames(null, matchMode, typeName.toCharArray(), matchMode, IJavaSearchConstants.TYPE, scope, requestor, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, pm); + + List result= new ArrayList<>(); + for (TypeNameMatch curr : collectedInfos) { + IType type= curr.getType(); + if (type != null) { + boolean visible=true; + try { + visible= JavaModelUtil.isVisible(type, currPackage); + } catch (JavaModelException e) { + //Assume visibile if not available + } + if (visible) { + result.add(curr); + } + } + } + return result; + } + + } + + private static class MethodTypesSyntaxChecker { + + private final IMethod fMethod; + private final List fParameterInfos; + private final ReturnTypeInfo fReturnTypeInfo; + + public MethodTypesSyntaxChecker(IMethod method, List parameterInfos, ReturnTypeInfo returnTypeInfo) { + fMethod= method; + fParameterInfos= parameterInfos; + fReturnTypeInfo= returnTypeInfo; + } + + public RefactoringStatus[] checkSyntax() { + int parameterCount= fParameterInfos.size(); + RefactoringStatus[] results= new RefactoringStatus[parameterCount + 1]; + results[parameterCount]= checkReturnTypeSyntax(); + for (int i= 0; i < parameterCount; i++) { + ParameterInfo info= fParameterInfos.get(i); + if (!info.isDeleted()) + results[i]= checkParameterTypeSyntax(info); + } + return results; + } + + private RefactoringStatus checkParameterTypeSyntax(ParameterInfo info) { + if (!info.isAdded() && !info.isTypeNameChanged() && !info.isDeleted()) + return null; + return TypeContextChecker.checkParameterTypeSyntax(info.getNewTypeName(), fMethod.getJavaProject()); + } + + private RefactoringStatus checkReturnTypeSyntax() { + String newTypeName= fReturnTypeInfo.getNewTypeName(); + if ("".equals(newTypeName.trim())) { //$NON-NLS-1$ + String msg= RefactoringCoreMessages.TypeContextChecker_return_type_not_empty; + return RefactoringStatus.createFatalErrorStatus(msg); + } + List problemsCollector= new ArrayList<>(0); + Type parsedType= parseType(newTypeName, fMethod.getJavaProject(), problemsCollector); + if (parsedType == null) { + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_invalid_return_type, BasicElementLabels.getJavaElementName(newTypeName)); + return RefactoringStatus.createFatalErrorStatus(msg); + } + if (problemsCollector.isEmpty()) + return null; + + RefactoringStatus result= new RefactoringStatus(); + for (String problem : problemsCollector) { + String[] keys= new String[]{ BasicElementLabels.getJavaElementName(newTypeName), BasicElementLabels.getJavaElementName(problem)}; + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_invalid_return_type_syntax, keys); + result.addError(msg); + } + return result; + } + + private static boolean isVoidArrayType(Type type){ + if (! type.isArrayType()) + return false; + + ArrayType arrayType= (ArrayType)type; + if (! arrayType.getElementType().isPrimitiveType()) + return false; + PrimitiveType primitiveType= (PrimitiveType) arrayType.getElementType(); + return (primitiveType.getPrimitiveTypeCode() == PrimitiveType.VOID); + } + + } + + private static Type parseType(String typeString, IJavaProject javaProject, List problemsCollector) { + if ("".equals(typeString.trim())) //speed up for a common case //$NON-NLS-1$ + return null; + if (! typeString.trim().equals(typeString)) + return null; + + StringBuilder cuBuff= new StringBuilder(); + cuBuff.append("interface A{"); //$NON-NLS-1$ + int offset= cuBuff.length(); + cuBuff.append(typeString).append(" m();}"); //$NON-NLS-1$ + + ASTParser p= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL); + p.setSource(cuBuff.toString().toCharArray()); + p.setProject(javaProject); + CompilationUnit cu= (CompilationUnit) p.createAST(null); + Selection selection= Selection.createFromStartLength(offset, typeString.length()); + SelectionAnalyzer analyzer= new SelectionAnalyzer(selection, false); + cu.accept(analyzer); + ASTNode selected= analyzer.getFirstSelectedNode(); + if (!(selected instanceof Type)) + return null; + Type type= (Type)selected; + if (MethodTypesSyntaxChecker.isVoidArrayType(type)) + return null; + for (IProblem problem : ASTNodes.getProblems(type, ASTNodes.NODE_ONLY, ASTNodes.PROBLEMS)) { + problemsCollector.add(problem.getMessage()); + } + String typeNodeRange= cuBuff.substring(type.getStartPosition(), ASTNodes.getExclusiveEnd(type)); + if (typeString.equals(typeNodeRange)) + return type; + else + return null; + } + + private static ITypeBinding handleBug84585(ITypeBinding typeBinding) { + if (typeBinding != null + && typeBinding.isGenericType() + && !typeBinding.isRawType() + && !typeBinding.isParameterizedType()) { + return null; + } + + return typeBinding; + } + + public static RefactoringStatus[] checkAndResolveMethodTypes(IMethod method, StubTypeContext stubTypeContext, List parameterInfos, ReturnTypeInfo returnTypeInfo) throws CoreException { + MethodTypesChecker checker= new MethodTypesChecker(method, stubTypeContext, parameterInfos, returnTypeInfo); + return checker.checkAndResolveMethodTypes(); + } + + public static RefactoringStatus[] checkMethodTypesSyntax(IMethod method, List parameterInfos, ReturnTypeInfo returnTypeInfo) { + MethodTypesSyntaxChecker checker= new MethodTypesSyntaxChecker(method, parameterInfos, returnTypeInfo); + return checker.checkSyntax(); + } + + public static RefactoringStatus checkParameterTypeSyntax(String type, IJavaProject project) { + String newTypeName= ParameterInfo.stripEllipsis(type.trim()).trim(); + String typeLabel= BasicElementLabels.getJavaElementName(type); + + if ("".equals(newTypeName.trim())){ //$NON-NLS-1$ + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_parameter_type, typeLabel); + return RefactoringStatus.createFatalErrorStatus(msg); + } + + if (ParameterInfo.isVarargs(type) && ! JavaModelUtil.is50OrHigher(project)) { + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_no_vararg_below_50, typeLabel); + return RefactoringStatus.createFatalErrorStatus(msg); + } + + List problemsCollector= new ArrayList<>(0); + Type parsedType= parseType(newTypeName, project, problemsCollector); + boolean valid= parsedType != null; + if (valid && parsedType instanceof PrimitiveType) + valid= ! PrimitiveType.VOID.equals(((PrimitiveType) parsedType).getPrimitiveTypeCode()); + if (! valid) { + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_invalid_type_name, BasicElementLabels.getJavaElementName(newTypeName)); + return RefactoringStatus.createFatalErrorStatus(msg); + } + if (problemsCollector.isEmpty()) + return null; + + RefactoringStatus result= new RefactoringStatus(); + for (String problem : problemsCollector) { + String msg= Messages.format(RefactoringCoreMessages.TypeContextChecker_invalid_type_syntax, + new String[]{BasicElementLabels.getJavaElementName(newTypeName), BasicElementLabels.getJavaElementName(problem)}); + result.addError(msg); + } + return result; + } + + public static StubTypeContext createStubTypeContext(ICompilationUnit cu, CompilationUnit root, int focalPosition) throws CoreException { + StringBuilder bufBefore= new StringBuilder(); + StringBuilder bufAfter= new StringBuilder(); + + int introEnd= 0; + PackageDeclaration pack= root.getPackage(); + if (pack != null) + introEnd= pack.getStartPosition() + pack.getLength(); + List imports= root.imports(); + if (imports.size() > 0) { + ImportDeclaration lastImport= imports.get(imports.size() - 1); + introEnd= lastImport.getStartPosition() + lastImport.getLength(); + } + bufBefore.append(cu.getBuffer().getText(0, introEnd)); + + fillWithTypeStubs(bufBefore, bufAfter, focalPosition, root.types()); + bufBefore.append(' '); + bufAfter.insert(0, ' '); + return new StubTypeContext(cu, bufBefore.toString(), bufAfter.toString()); + } + + private static void fillWithTypeStubs(final StringBuilder bufBefore, final StringBuilder bufAfter, final int focalPosition, List types) { + StringBuilder buf; + for (BodyDeclaration bodyDeclaration : types) { + if (! (bodyDeclaration instanceof AbstractTypeDeclaration)) { + //account for local classes: + if (! (bodyDeclaration instanceof MethodDeclaration)) + continue; + int bodyStart= bodyDeclaration.getStartPosition(); + int bodyEnd= bodyDeclaration.getStartPosition() + bodyDeclaration.getLength(); + if ((bodyStart >= focalPosition) || (focalPosition >= bodyEnd)) + continue; + MethodDeclaration methodDeclaration= (MethodDeclaration) bodyDeclaration; + buf= bufBefore; + appendModifiers(buf, methodDeclaration.modifiers()); + appendTypeParameters(buf, methodDeclaration.typeParameters()); + buf.append(" void "); //$NON-NLS-1$ + buf.append(methodDeclaration.getName().getIdentifier()); + buf.append("(){\n"); //$NON-NLS-1$ + Block body= methodDeclaration.getBody(); + body.accept(new HierarchicalASTVisitor() { + @Override + public boolean visit(AbstractTypeDeclaration node) { + fillWithTypeStubs(bufBefore, bufAfter, focalPosition, Collections.singletonList(node)); + return false; + } + @Override + public boolean visit(ClassInstanceCreation node) { + AnonymousClassDeclaration anonDecl= node.getAnonymousClassDeclaration(); + if (anonDecl == null) + return true; // could be in CIC parameter list + int anonStart= anonDecl.getStartPosition(); + int anonEnd= anonDecl.getStartPosition() + anonDecl.getLength(); + if ((anonStart >= focalPosition) || (focalPosition >= anonEnd)) + return false; + bufBefore.append(" new "); //$NON-NLS-1$ + bufBefore.append(node.getType().toString()); + bufBefore.append("(){\n"); //$NON-NLS-1$ + fillWithTypeStubs(bufBefore, bufAfter, focalPosition, anonDecl.bodyDeclarations()); + bufAfter.append("};\n"); //$NON-NLS-1$ + return false; + } + }); + buf= bufAfter; + buf.append("}\n"); //$NON-NLS-1$ + continue; + } + + AbstractTypeDeclaration decl= (AbstractTypeDeclaration) bodyDeclaration; + buf= decl.getStartPosition() < focalPosition ? bufBefore : bufAfter; + appendModifiers(buf, decl.modifiers()); + + if (decl instanceof TypeDeclaration) { + TypeDeclaration type= (TypeDeclaration) decl; + buf.append(type.isInterface() ? "interface " : "class "); //$NON-NLS-1$//$NON-NLS-2$ + buf.append(type.getName().getIdentifier()); + appendTypeParameters(buf, type.typeParameters()); + if (type.getSuperclassType() != null) { + buf.append(" extends "); //$NON-NLS-1$ + buf.append(ASTNodes.asString(type.getSuperclassType())); + } + List superInterfaces= type.superInterfaceTypes(); + appendSuperInterfaces(buf, superInterfaces); + + } else if (decl instanceof AnnotationTypeDeclaration) { + AnnotationTypeDeclaration annotation= (AnnotationTypeDeclaration) decl; + buf.append("@interface "); //$NON-NLS-1$ + buf.append(annotation.getName().getIdentifier()); + + } else if (decl instanceof EnumDeclaration) { + EnumDeclaration enumDecl= (EnumDeclaration) decl; + buf.append("enum "); //$NON-NLS-1$ + buf.append(enumDecl.getName().getIdentifier()); + List superInterfaces= enumDecl.superInterfaceTypes(); + appendSuperInterfaces(buf, superInterfaces); + } else if (decl instanceof RecordDeclaration) { + RecordDeclaration recordDecl= (RecordDeclaration) decl; + buf.append("record "); //$NON-NLS-1$ + buf.append(recordDecl.getName().getIdentifier()); + buf.append("("); //$NON-NLS-1$ + appendRecordComponents(buf, recordDecl.recordComponents()); + buf.append(")"); //$NON-NLS-1$ + appendTypeParameters(buf, recordDecl.typeParameters()); + List superInterfaces= recordDecl.superInterfaceTypes(); + appendSuperInterfaces(buf, superInterfaces); + } + + buf.append("{\n"); //$NON-NLS-1$ + if (decl instanceof EnumDeclaration) + buf.append(";\n"); //$NON-NLS-1$ + fillWithTypeStubs(bufBefore, bufAfter, focalPosition, decl.bodyDeclarations()); + buf= decl.getStartPosition() + decl.getLength() < focalPosition ? bufBefore : bufAfter; + buf.append("}\n"); //$NON-NLS-1$ + } + } + + private static void appendRecordComponents(StringBuilder buf, List recordComponents) { + int recCompsCount= recordComponents.size(); + if (recCompsCount > 0) { + for (int i= 0; i < recCompsCount; i++) { + SingleVariableDeclaration recComp= recordComponents.get(i); + buf.append(ASTNodes.asString(recComp)); + if (i < recCompsCount - 1) + buf.append(','); + } + } + } + + private static void appendTypeParameters(StringBuilder buf, List typeParameters) { + int typeParametersCount= typeParameters.size(); + if (typeParametersCount > 0) { + buf.append('<'); + for (int i= 0; i < typeParametersCount; i++) { + TypeParameter typeParameter= typeParameters.get(i); + buf.append(ASTNodes.asString(typeParameter)); + if (i < typeParametersCount - 1) + buf.append(','); + } + buf.append('>'); + } + } + + private static void appendModifiers(StringBuilder buf, List modifiers) { + for (IExtendedModifier extendedModifier : modifiers) { + if (extendedModifier.isModifier()) { + Modifier modifier= (Modifier) extendedModifier; + buf.append(modifier.getKeyword().toString()).append(' '); + } + } + } + + private static void appendSuperInterfaces(StringBuilder buf, List superInterfaces) { + int superInterfaceCount= superInterfaces.size(); + if (superInterfaceCount > 0) { + buf.append(" implements "); //$NON-NLS-1$ + for (int i= 0; i < superInterfaceCount; i++) { + Type superInterface= superInterfaces.get(i); + buf.append(ASTNodes.asString(superInterface)); + if (i < superInterfaceCount - 1) + buf.append(','); + } + } + } + + public static StubTypeContext createSuperInterfaceStubTypeContext(String typeName, IType enclosingType, IPackageFragment packageFragment) { + return createSupertypeStubTypeContext(typeName, true, enclosingType, packageFragment); + } + + public static StubTypeContext createSuperClassStubTypeContext(String typeName, IType enclosingType, IPackageFragment packageFragment) { + return createSupertypeStubTypeContext(typeName, false, enclosingType, packageFragment); + } + + private static StubTypeContext createSupertypeStubTypeContext(String typeName, boolean isInterface, IType enclosingType, IPackageFragment packageFragment) { + StubTypeContext stubTypeContext; + String prolog= "class " + typeName + (isInterface ? " implements " : " extends "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + String epilog= " {} "; //$NON-NLS-1$ + if (enclosingType != null) { + try { + ICompilationUnit cu= enclosingType.getCompilationUnit(); + ISourceRange typeSourceRange= enclosingType.getSourceRange(); + int focalPosition= typeSourceRange.getOffset() + typeSourceRange.getLength() - 1; // before closing brace + + ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL); + parser.setSource(cu); + parser.setFocalPosition(focalPosition); + CompilationUnit compilationUnit= (CompilationUnit) parser.createAST(null); + + stubTypeContext= createStubTypeContext(cu, compilationUnit, focalPosition); + stubTypeContext= new StubTypeContext(stubTypeContext.getCuHandle(), + stubTypeContext.getBeforeString() + prolog, + epilog + stubTypeContext.getAfterString()); + } catch (CoreException e) { + JavaManipulationPlugin.log(e); + stubTypeContext= new StubTypeContext(null, null, null); + } + + } else if (packageFragment != null) { + ICompilationUnit cu= packageFragment.getCompilationUnit(JavaTypeCompletionProcessorCore.DUMMY_CU_NAME); + stubTypeContext= new StubTypeContext(cu, "package " + packageFragment.getElementName() + ";" + prolog, epilog); //$NON-NLS-1$//$NON-NLS-2$ + + } else { + stubTypeContext= new StubTypeContext(null, null, null); + } + return stubTypeContext; + } + + public static StubTypeContext createAnnotationStubTypeContext(/*@NonNull*/ IProject project) { + try { + for (IPackageFragmentRoot root : JavaCore.create(project).getPackageFragmentRoots()) { + if (!root.isReadOnly()) { + IPackageFragment packageFragment= root.getPackageFragment(""); //$NON-NLS-1$ + String prolog= "abstract class __X__ {\n\tabstract @"; //$NON-NLS-1$ + String epilog= " __X__ dummy();\n} "; //$NON-NLS-1$ + ICompilationUnit cu= packageFragment.getCompilationUnit(JavaTypeCompletionProcessorCore.DUMMY_CU_NAME); + return new StubTypeContext(cu, prolog, epilog); + } + } + } catch (JavaModelException e) { + // fall through + } + return new StubTypeContext(null, null, null); + } + + public static Type parseSuperClass(String superClass) { + return parseSuperType(superClass, false); + } + + public static Type parseSuperInterface(String superInterface) { + return parseSuperType(superInterface, true); + } + + private static Type parseSuperType(String superType, boolean isInterface) { + if (! superType.trim().equals(superType)) { + return null; + } + + StringBuilder cuBuff= new StringBuilder(); + if (isInterface) + cuBuff.append("class __X__ implements "); //$NON-NLS-1$ + else + cuBuff.append("class __X__ extends "); //$NON-NLS-1$ + int offset= cuBuff.length(); + cuBuff.append(superType).append(" {}"); //$NON-NLS-1$ + + ASTParser p= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL); + p.setSource(cuBuff.toString().toCharArray()); + Map options= new HashMap<>(); + JavaModelUtil.setComplianceOptions(options, JavaModelUtil.VERSION_LATEST); + p.setCompilerOptions(options); + CompilationUnit cu= (CompilationUnit) p.createAST(null); + ASTNode selected= NodeFinder.perform(cu, offset, superType.length()); + if (selected instanceof Name) + selected= selected.getParent(); + if (selected.getStartPosition() != offset + || selected.getLength() != superType.length() + || ! (selected instanceof Type) + || selected instanceof PrimitiveType) { + return null; + } + Type type= (Type) selected; + + String typeNodeRange= cuBuff.substring(type.getStartPosition(), ASTNodes.getExclusiveEnd(type)); + if (! superType.equals(typeNodeRange)){ + return null; + } + return type; + } + + public static ITypeBinding resolveSuperClass(String superclass, IType typeHandle, StubTypeContext superClassContext) { + StringBuilder cuString= new StringBuilder(); + cuString.append(superClassContext.getBeforeString()); + cuString.append(superclass); + cuString.append(superClassContext.getAfterString()); + + try { + ICompilationUnit wc= typeHandle.getCompilationUnit().getWorkingCopy(new WorkingCopyOwner() {/*subclass*/}, new NullProgressMonitor()); + try { + wc.getBuffer().setContents(cuString.toString()); + CompilationUnit compilationUnit= new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL).parse(wc, true); + ASTNode type= NodeFinder.perform(compilationUnit, superClassContext.getBeforeString().length(), superclass.length()); + if (type instanceof Type) { + return handleBug84585(((Type) type).resolveBinding()); + } else if (type instanceof Name) { + ASTNode parent= type.getParent(); + if (parent instanceof Type) + return handleBug84585(((Type) parent).resolveBinding()); + } + throw new IllegalStateException(); + } finally { + wc.discardWorkingCopy(); + } + } catch (JavaModelException e) { + return null; + } + } + + public static ITypeBinding[] resolveSuperInterfaces(String[] interfaces, IType typeHandle, StubTypeContext superInterfaceContext) { + ITypeBinding[] result= new ITypeBinding[interfaces.length]; + + int[] interfaceOffsets= new int[interfaces.length]; + StringBuilder cuString= new StringBuilder(); + cuString.append(superInterfaceContext.getBeforeString()); + int last= interfaces.length - 1; + for (int i= 0; i <= last; i++) { + interfaceOffsets[i]= cuString.length(); + cuString.append(interfaces[i]); + if (i != last) + cuString.append(", "); //$NON-NLS-1$ + } + cuString.append(superInterfaceContext.getAfterString()); + + try { + ICompilationUnit wc= typeHandle.getCompilationUnit().getWorkingCopy(new WorkingCopyOwner() {/*subclass*/}, new NullProgressMonitor()); + try { + wc.getBuffer().setContents(cuString.toString()); + CompilationUnit compilationUnit= new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL).parse(wc, true); + for (int i= 0; i <= last; i++) { + ASTNode type= NodeFinder.perform(compilationUnit, interfaceOffsets[i], interfaces[i].length()); + if (type instanceof Type) { + result[i]= handleBug84585(((Type) type).resolveBinding()); + } else if (type instanceof Name) { + ASTNode parent= type.getParent(); + if (parent instanceof Type) { + result[i]= handleBug84585(((Type) parent).resolveBinding()); + } else { + throw new IllegalStateException(); + } + } else { + throw new IllegalStateException(); + } + } + } finally { + wc.discardWorkingCopy(); + } + } catch (JavaModelException e) { + // won't happen + } + return result; + } + + private TypeContextChecker() { + } + } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/changes/CreatePackageChange.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/changes/CreatePackageChange.java new file mode 100644 index 0000000000..6c5175a266 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/changes/CreatePackageChange.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011 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.changes; + + import org.eclipse.core.runtime.CoreException; + import org.eclipse.core.runtime.IProgressMonitor; + + import org.eclipse.core.resources.IResource; + + import org.eclipse.ltk.core.refactoring.Change; + import org.eclipse.ltk.core.refactoring.NullChange; + import org.eclipse.ltk.core.refactoring.RefactoringStatus; + import org.eclipse.ltk.core.refactoring.resource.DeleteResourceChange; + import org.eclipse.ltk.core.refactoring.resource.ResourceChange; + + import org.eclipse.jdt.core.IPackageFragment; + import org.eclipse.jdt.core.IPackageFragmentRoot; + + import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; + + public class CreatePackageChange extends ResourceChange { + + private IPackageFragment fPackageFragment; + + public CreatePackageChange(IPackageFragment pack) { + fPackageFragment= pack; + } + + @Override + public RefactoringStatus isValid(IProgressMonitor pm) { + // Don't do any checking. Peform handles the case + // that the package already exists. Furthermore + // create package change isn't used as a undo + // redo change right now + return new RefactoringStatus(); + } + + @Override + public Change perform(IProgressMonitor pm) throws CoreException { + try { + pm.beginTask(RefactoringCoreMessages.CreatePackageChange_Creating_package, 1); + + if (fPackageFragment.exists()) { + return new NullChange(); + } else { + IPackageFragmentRoot root= (IPackageFragmentRoot) fPackageFragment.getParent(); + root.createPackageFragment(fPackageFragment.getElementName(), false, pm); + + return new DeleteResourceChange(fPackageFragment.getPath(), true); + } + } finally { + pm.done(); + } + } + + @Override + public String getName() { + return RefactoringCoreMessages.CreatePackageChange_Create_package; + } + + @Override + public Object getModifiedElement() { + return fPackageFragment; + } + + @Override + protected IResource getModifiedResource() { + return fPackageFragment.getResource(); + } + } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/rename/RippleMethodFinder2.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/rename/RippleMethodFinder2.java new file mode 100644 index 0000000000..3a7faffc08 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/rename/RippleMethodFinder2.java @@ -0,0 +1,539 @@ +/******************************************************************************* + * Copyright (c) 2000, 2022 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 + * Nikolay Metchev - [rename] https://bugs.eclipse.org/99622 + *******************************************************************************/ +package org.eclipse.jdt.internal.corext.refactoring.rename; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.eclipse.core.runtime.Assert; +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.jdt.core.IMethod; +import org.eclipse.jdt.core.IRegion; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeHierarchy; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchParticipant; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.SearchRequestor; + +import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory; +import org.eclipse.jdt.internal.corext.refactoring.base.ReferencesInBinaryContext; +import org.eclipse.jdt.internal.corext.util.JavaModelUtil; +import org.eclipse.jdt.internal.corext.util.MethodOverrideTester; +import org.eclipse.jdt.internal.corext.util.SearchUtils; + +public class RippleMethodFinder2 { + + private final IMethod fMethod; + private Set fDeclarations; + private ITypeHierarchy fHierarchy; + private MultiMap fTypeToMethod; + private Set fRootTypes; + private MultiMap fRootReps; + private Map fRootHierarchies; + private UnionFind fUnionFind; + + private final boolean fExcludeBinaries; + private final ReferencesInBinaryContext fBinaryRefs; + private Map fDeclarationToMatch; + private boolean fSearchOnlyInCompilationUnit = false; + + private static class MultiMap { + HashMap> fImplementation= new HashMap<>(); + + public void put(K key, V value) { + Collection collection= fImplementation.get(key); + if (collection == null) { + collection= new HashSet<>(); + fImplementation.put(key, collection); + } + collection.add(value); + } + + public Collection get(K key) { + return fImplementation.get(key); + } + } + private static class UnionFind { + HashMap fElementToRepresentative= new HashMap<>(); + + public void init(IType type) { + fElementToRepresentative.put(type, type); + } + + //path compression: + public IType find(IType element) { + IType root= element; + IType rep= fElementToRepresentative.get(root); + while (rep != null && ! rep.equals(root)) { + root= rep; + rep= fElementToRepresentative.get(root); + } + if (rep == null) + return null; + + rep= fElementToRepresentative.get(element); + while (! rep.equals(root)) { + IType temp= element; + element= rep; + fElementToRepresentative.put(temp, root); + rep= fElementToRepresentative.get(element); + } + return root; + } + +// //straightforward: +// public IType find(IType element) { +// IType current= element; +// IType rep= (IType) fElementToRepresentative.get(current); +// while (rep != null && ! rep.equals(current)) { +// current= rep; +// rep= (IType) fElementToRepresentative.get(current); +// } +// if (rep == null) +// return null; +// else +// return current; +// } + + public void union(IType rep1, IType rep2) { + fElementToRepresentative.put(rep1, rep2); + } + } + + + private RippleMethodFinder2(IMethod method, boolean excludeBinaries, boolean searchOnlyInCompilationUnit){ + fMethod= method; + fExcludeBinaries= excludeBinaries; + fSearchOnlyInCompilationUnit= searchOnlyInCompilationUnit; + fBinaryRefs= null; + } + + private RippleMethodFinder2(IMethod method, ReferencesInBinaryContext binaryRefs) { + fMethod= method; + fExcludeBinaries= true; + fDeclarationToMatch= new HashMap<>(); + fBinaryRefs= binaryRefs; + } + + public static IMethod[] getRelatedMethods(IMethod method, boolean excludeBinaries, IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { + try{ + if (! MethodChecks.isVirtual(method)) + return new IMethod[]{ method }; + + return new RippleMethodFinder2(method, excludeBinaries, false).getAllRippleMethods(pm, owner); + } finally{ + pm.done(); + } + } + public static IMethod[] getRelatedMethodsInCompilationUnit(IMethod method, NullProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { + try{ + if (! MethodChecks.isVirtual(method)) + return new IMethod[]{ method }; + + return new RippleMethodFinder2(method, true, true).getAllRippleMethods(pm, owner); + } finally{ + pm.done(); + } + } + public static IMethod[] getRelatedMethods(IMethod method, IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { + return getRelatedMethods(method, true, pm, owner); + } + + public static IMethod[] getRelatedMethods(IMethod method, ReferencesInBinaryContext binaryRefs, IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { + try { + if (! MethodChecks.isVirtual(method)) + return new IMethod[]{ method }; + + return new RippleMethodFinder2(method, binaryRefs).getAllRippleMethods(pm, owner); + } finally{ + pm.done(); + } + } + + private IMethod[] getAllRippleMethods(IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { + IMethod[] rippleMethods= findAllRippleMethods(pm, owner); + if (fDeclarationToMatch == null) + return rippleMethods; + + List filteredMethods= new ArrayList<>(rippleMethods.length / 2); + for (IMethod currentMethod : rippleMethods) { + Object match= fDeclarationToMatch.get(currentMethod); + if (match != null) { + fBinaryRefs.add((SearchMatch) match); + } else { + filteredMethods.add(currentMethod); + } + } + fDeclarationToMatch= null; + return toArray(filteredMethods); + } + + private IMethod[] findAllRippleMethods(IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { + pm.beginTask("", 4); //$NON-NLS-1$ + + findAllDeclarations(new SubProgressMonitor(pm, 1), owner); + + //TODO: report assertion as error status and fall back to only return fMethod + //check for bug 81058: + if (! fDeclarations.contains(fMethod)) { + if (fSearchOnlyInCompilationUnit) { + return new IMethod[0]; + } else { + Assert.isTrue(false, "Search for method declaration did not find original element: " + fMethod.toString()); //$NON-NLS-1$ + } + } + + createHierarchyOfDeclarations(new SubProgressMonitor(pm, 1), owner); + addMissedSuperTypes(); + createTypeToMethod(); + createUnionFind(); + checkCanceled(pm); + + fHierarchy= null; + fRootTypes= null; + + Map> partitioning= new HashMap<>(); + for (IType type : fTypeToMethod.fImplementation.keySet()) { + IType rep= fUnionFind.find(type); + List types= partitioning.get(rep); + if (types == null) + types= new ArrayList<>(); + types.add(type); + partitioning.put(rep, types); + } + Assert.isTrue(partitioning.size() > 0); + if (partitioning.size() == 1) + return fDeclarations.toArray(new IMethod[fDeclarations.size()]); + + //Multiple partitions; must look out for nasty marriage cases + //(types inheriting method from two ancestors, but without redeclaring it). + IType methodTypeRep= fUnionFind.find(fMethod.getDeclaringType()); + List relatedTypes= partitioning.get(methodTypeRep); + boolean hasRelatedInterfaces= false; + List relatedMethods= new ArrayList<>(); + for (IType relatedType : relatedTypes) { + relatedMethods.addAll(fTypeToMethod.get(relatedType)); + if (relatedType.isInterface()) + hasRelatedInterfaces= true; + } + + int numberOfSearchMatches= fDeclarations.size(); + //Definition: An alien type is a type that is not a related type. The set of + // alien types diminishes as new types become related (a.k.a marry a relatedType). + + Set alienDeclarations= new LinkedHashSet<>(fDeclarations); + fDeclarations= null; + alienDeclarations.removeAll(relatedMethods); + Set alienTypes= new LinkedHashSet<>(); + boolean hasAlienInterfaces= false; + for (IMethod alienDeclaration : alienDeclarations) { + IType alienType= alienDeclaration.getDeclaringType(); + alienTypes.add(alienType); + if (alienType.isInterface()) + hasAlienInterfaces= true; + } + if (alienTypes.isEmpty()) //no nasty marriage scenarios without types to marry with... + return toArray(relatedMethods); + if (! hasRelatedInterfaces && ! hasAlienInterfaces) //no nasty marriage scenarios without interfaces... + return toArray(relatedMethods); + + /* + * Go down the hierarchy of the type under rename, and build hierarchies of its sub-types. + * Check if any sub-type implements another interface with the same method name. + * If not, we cannot have married alien types, so we can skip building type hierarchies for the alien types. + */ + checkCanceled(pm); + IType methodType= fMethod.getDeclaringType(); + ITypeHierarchy methodHierarchy= hierarchy(pm, owner, fUnionFind.find(methodType)); + IType[] methodTypeSubtypes= methodHierarchy.getAllSubtypes(methodType); + // don't spend time on this check, unless we have a small hierarchy for the type under rename and a lot of search matches + if (methodTypeSubtypes.length <= numberOfSearchMatches / 10) { + boolean couldHaveMarriedAlienTypes= couldHaveMarriedAlienTypes(pm, owner, methodHierarchy, methodTypeSubtypes); + if (!couldHaveMarriedAlienTypes) { + return toArray(relatedMethods); + } + } + + //find all subtypes of related types: + HashSet relatedSubTypes= new HashSet<>(); + List relatedTypesToProcess= new ArrayList<>(relatedTypes); + while (relatedTypesToProcess.size() > 0) { + //TODO: would only need subtype hierarchies of all top-of-ripple relatedTypesToProcess + for (IType relatedType : relatedTypesToProcess) { + checkCanceled(pm); + ITypeHierarchy hierarchy= hierarchy(pm, owner, relatedType); + IType[] allSubTypes= hierarchy.getAllSubtypes(relatedType); + relatedSubTypes.addAll(Arrays.asList(allSubTypes)); + } + relatedTypesToProcess.clear(); //processed; make sure loop terminates + + HashSet marriedAlienTypeReps= new HashSet<>(); + for (IType alienType : alienTypes) { + checkCanceled(pm); + Collection alienMethods= fTypeToMethod.get(alienType); + for (IMethod alienMethod : alienMethods) { + ITypeHierarchy hierarchy= hierarchy(pm, owner, alienType); + + for (IType subtype : hierarchy.getAllSubtypes(alienType)) { + if (relatedSubTypes.contains(subtype)) { + if (JavaModelUtil.isVisibleInHierarchy(alienMethod, subtype.getPackageFragment())) { + marriedAlienTypeReps.add(fUnionFind.find(alienType)); + } else { + // not overridden + } + } + } + } + } + + if (marriedAlienTypeReps.isEmpty()) + return toArray(relatedMethods); + + for (IType marriedAlienTypeRep : marriedAlienTypeReps) { + List marriedAlienTypes= partitioning.get(marriedAlienTypeRep); + for (IType marriedAlienInterfaceType : marriedAlienTypes) { + relatedMethods.addAll(fTypeToMethod.get(marriedAlienInterfaceType)); + } + alienTypes.removeAll(marriedAlienTypes); //not alien any more + relatedTypesToProcess.addAll(marriedAlienTypes); //process freshly married types again + } + } + + fRootReps= null; + fRootHierarchies= null; + fTypeToMethod= null; + fUnionFind= null; + + return toArray(relatedMethods); + } + + /** + * For the method under rename, checks if any sub-type inherits a method with the same name from a different super type. + * + * @param pm progress monitor to + * @param owner owner of the compilation unit under rename + * @param methodHierarchy the type hierarchy of the method under rename + * @param methodTypeSubtypes the sub-types of the type under rename + * @return {@code false} if there can be no married alien types to the method under rename, {@code true} otherwise. + * @throws JavaModelException if creating a type hierarchy fails + */ + private boolean couldHaveMarriedAlienTypes(IProgressMonitor pm, WorkingCopyOwner owner, ITypeHierarchy methodHierarchy, IType[] methodTypeSubtypes) throws JavaModelException { + Set allTypesInMethodHierarchy= new HashSet<>(Arrays.asList(methodHierarchy.getAllClasses())); + allTypesInMethodHierarchy.addAll(Arrays.asList(methodHierarchy.getAllInterfaces())); + + + for (IType methodTypeSubtype : methodTypeSubtypes) { + checkCanceled(pm); + ITypeHierarchy subtypeHierarchy= methodTypeSubtype.newTypeHierarchy(owner, pm); + IType[] subtypeSuperTypes= subtypeHierarchy.getAllSupertypes(methodTypeSubtype); + for (IType subtypeSuperType : subtypeSuperTypes) { + checkCanceled(pm); + if (!allTypesInMethodHierarchy.contains(subtypeSuperType)) { + if (definesSimilarMethod(subtypeSuperType, fMethod)) { + return true; + } + } + } + } + + return false; + } + + /** + * @return {@code true} if the specified type defines a method which shares method name and parameter types with the specified method. + * + * @param type the type to check in + * @param method the method to check for + * @throws JavaModelException if listing the methods of the type fails + */ + private static boolean definesSimilarMethod(IType type, IMethod method) throws JavaModelException { + /* + * We don't use type.getMethod(method.getElementName(), method.getParameterTypes()), + * since types that come from binaries can list different parameter names. So we could miss a matching definition. + */ + String methodName= method.getElementName(); + IMethod[] typeMethods= type.getMethods(); + return Arrays.asList(typeMethods).stream().anyMatch(typeMethod -> methodName.equals(typeMethod.getElementName())); + } + + private static IMethod[] toArray(List methods) { + return methods.toArray(new IMethod[methods.size()]); + } + + private static void checkCanceled(IProgressMonitor pm) { + if (pm.isCanceled()) { + throw new OperationCanceledException(); + } + } + + private ITypeHierarchy hierarchy(IProgressMonitor pm, WorkingCopyOwner owner, IType type) + throws JavaModelException { + ITypeHierarchy hierarchy= getCachedHierarchy(type, owner, new SubProgressMonitor(pm, 1)); + if (hierarchy == null) + hierarchy= type.newTypeHierarchy(owner, new SubProgressMonitor(pm, 1)); + return hierarchy; + } + + private void addMissedSuperTypes() throws JavaModelException { + Set newDeclarations = new HashSet<>(); + for (IMethod method : fDeclarations) { + MethodOverrideTester methodOverrideTester= new MethodOverrideTester(method.getDeclaringType(), fHierarchy); + newDeclarations.addAll(methodOverrideTester.findAllOverridenMethods(method)); + } + fDeclarations.addAll(newDeclarations); + + } + + private ITypeHierarchy getCachedHierarchy(IType type, WorkingCopyOwner owner, IProgressMonitor monitor) throws JavaModelException { + IType rep= fUnionFind.find(type); + if (rep != null) { + for (IType root : fRootReps.get(rep)) { + ITypeHierarchy hierarchy= fRootHierarchies.get(root); + if (hierarchy == null) { + hierarchy= root.newTypeHierarchy(owner, new SubProgressMonitor(monitor, 1)); + fRootHierarchies.put(root, hierarchy); + } + if (hierarchy.contains(type)) + return hierarchy; + } + } + return null; + } + + private void findAllDeclarations(IProgressMonitor monitor, WorkingCopyOwner owner) throws CoreException { + fDeclarations= new HashSet<>(); + + class MethodRequestor extends SearchRequestor { + @Override + public void acceptSearchMatch(SearchMatch match) throws CoreException { + IMethod method= (IMethod) match.getElement(); + + boolean isVisible= JavaModelUtil.isVisibleInHierarchy(method, fMethod.getDeclaringType().getPackageFragment()); + + if (isVisible) { + boolean isBinary= method.isBinary(); + if (fBinaryRefs != null || !fExcludeBinaries || !isBinary) { + fDeclarations.add(method); + } + if (isBinary && fBinaryRefs != null) { + fDeclarationToMatch.put(method, match); + } + } + } + } + + int limitTo= IJavaSearchConstants.DECLARATIONS | IJavaSearchConstants.IGNORE_DECLARING_TYPE | IJavaSearchConstants.IGNORE_RETURN_TYPE; + int matchRule= SearchPattern.R_ERASURE_MATCH | SearchPattern.R_CASE_SENSITIVE; + SearchPattern pattern= SearchPattern.createPattern(fMethod, limitTo, matchRule); + if (pattern == null) { + return; + } + SearchParticipant[] participants= SearchUtils.getDefaultSearchParticipants(); + IJavaSearchScope scope; + if (fSearchOnlyInCompilationUnit) { + scope= RefactoringScopeFactory.create(fMethod.getCompilationUnit()); + } else { + scope= RefactoringScopeFactory.createRelatedProjectsScope(fMethod.getJavaProject(), IJavaSearchScope.SOURCES | IJavaSearchScope.APPLICATION_LIBRARIES | IJavaSearchScope.SYSTEM_LIBRARIES); + } + MethodRequestor requestor= new MethodRequestor(); + SearchEngine searchEngine= owner != null ? new SearchEngine(owner) : new SearchEngine(); + + searchEngine.search(pattern, participants, scope, requestor, monitor); + } + + private void createHierarchyOfDeclarations(IProgressMonitor pm, WorkingCopyOwner owner) throws JavaModelException { + Stream types= fDeclarations.stream().map(IMethod::getDeclaringType); + fHierarchy= createHierarchyOfTypes(pm, owner, types); + } + + private static ITypeHierarchy createHierarchyOfTypes(IProgressMonitor pm, WorkingCopyOwner owner, Stream types) throws JavaModelException { + IRegion region= JavaCore.newRegion(); + for (Iterator iter= types.iterator(); iter.hasNext(); ) { + IType type= iter.next(); + region.add(type); + } + return JavaCore.newTypeHierarchy(region, owner, pm); + } + + private void createTypeToMethod() { + fTypeToMethod= new MultiMap<>(); + for (IMethod declaration : fDeclarations) { + fTypeToMethod.put(declaration.getDeclaringType(), declaration); + } + } + + private void createUnionFind() throws JavaModelException { + fRootTypes= new HashSet<>(fTypeToMethod.fImplementation.keySet()); + fUnionFind= new UnionFind(); + for (IType type : fTypeToMethod.fImplementation.keySet()) { + fUnionFind.init(type); + } + for (IType type : fTypeToMethod.fImplementation.keySet()) { + uniteWithSupertypes(type, type); + } + fRootReps= new MultiMap<>(); + for (IType type : fRootTypes) { + IType rep= fUnionFind.find(type); + if (rep != null) + fRootReps.put(rep, type); + } + fRootHierarchies= new HashMap<>(); + } + + private void uniteWithSupertypes(IType anchor, IType type) throws JavaModelException { + for (IType supertype : fHierarchy.getSupertypes(type)) { + IType superRep= fUnionFind.find(supertype); + if (superRep == null) { + //Type doesn't declare method, but maybe supertypes? + uniteWithSupertypes(anchor, supertype); + } else { + //check whether method in supertype is really overridden: + Collection superMethods= fTypeToMethod.get(supertype); + for (IMethod superMethod : superMethods) { + if (JavaModelUtil.isVisibleInHierarchy(superMethod, anchor.getPackageFragment())) { + IType rep= fUnionFind.find(anchor); + fUnionFind.union(rep, superRep); + // current type is no root anymore + fRootTypes.remove(anchor); + uniteWithSupertypes(supertype, supertype); + } else { + //Not overridden -> overriding chain ends here. + } + } + } + } + } +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/BodyUpdater.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/BodyUpdater.java new file mode 100644 index 0000000000..6cf38c7578 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/BodyUpdater.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 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.structure; + +import org.eclipse.core.runtime.CoreException; + +import org.eclipse.ltk.core.refactoring.RefactoringStatus; + +import org.eclipse.jdt.core.dom.MethodDeclaration; + + +public abstract class BodyUpdater { + + /** + * Updates the body of a method declaration. This method is called by the + * {@link ChangeSignatureProcessor} and allows implementors to refactor the body + * of the given method declaration. + * + * @param methodDeclaration + * @param cuRewrite + * @param result + * @throws CoreException + */ + public abstract void updateBody(MethodDeclaration methodDeclaration, CompilationUnitRewrite cuRewrite, RefactoringStatus result) throws CoreException; + + /** + * Returns whether {@link ChangeSignatureProcessor} should check if + * deleted parameters are currently used in the method body. + * + * @return true by default, subclasses can override + */ + public boolean needsParameterUsedCheck() { + return true; + } + +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/ChangeSignatureProcessor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/ChangeSignatureProcessor.java new file mode 100644 index 0000000000..c5835d2e37 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/ChangeSignatureProcessor.java @@ -0,0 +1,2914 @@ +/******************************************************************************* + * 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.structure; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import org.eclipse.core.runtime.Assert; +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.IFile; + +import org.eclipse.text.edits.TextEditGroup; + +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.RefactoringStatusContext; +import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; +import org.eclipse.ltk.core.refactoring.TextChange; +import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; +import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant; +import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor; +import org.eclipse.ltk.core.refactoring.participants.SharableParticipants; + +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.ICompilationUnit; +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.Signature; +import org.eclipse.jdt.core.compiler.IProblem; +import org.eclipse.jdt.core.dom.AST; +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.Block; +import org.eclipse.jdt.core.dom.BodyDeclaration; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.ConstructorInvocation; +import org.eclipse.jdt.core.dom.Dimension; +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.ExpressionMethodReference; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.LambdaExpression; +import org.eclipse.jdt.core.dom.MemberRef; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.MethodRef; +import org.eclipse.jdt.core.dom.MethodRefParameter; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.PrimitiveType; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.TagElement; +import org.eclipse.jdt.core.dom.TextElement; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; +import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; +import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; +import org.eclipse.jdt.core.dom.rewrite.ListRewrite; +import org.eclipse.jdt.core.refactoring.IJavaRefactorings; +import org.eclipse.jdt.core.refactoring.descriptors.ChangeMethodSignatureDescriptor; +import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor; +import org.eclipse.jdt.core.refactoring.participants.ChangeMethodSignatureArguments; +import org.eclipse.jdt.core.refactoring.participants.ChangeMethodSignatureArguments.Parameter; +import org.eclipse.jdt.core.refactoring.participants.ChangeMethodSignatureArguments.ThrownException; +import org.eclipse.jdt.core.refactoring.participants.IRefactoringProcessorIds; +import org.eclipse.jdt.core.refactoring.participants.JavaParticipantManager; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.MethodReferenceMatch; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchPattern; + +import org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore; +import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin; +import org.eclipse.jdt.internal.core.manipulation.StubUtility; +import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; +import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory; +import org.eclipse.jdt.internal.corext.CorextCore; +import org.eclipse.jdt.internal.corext.SourceRangeFactory; +import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; +import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory; +import org.eclipse.jdt.internal.corext.dom.ASTNodes; +import org.eclipse.jdt.internal.corext.dom.Bindings; +import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; +import org.eclipse.jdt.internal.corext.dom.ModifierRewrite; +import org.eclipse.jdt.internal.corext.dom.Selection; +import org.eclipse.jdt.internal.corext.dom.SelectionAnalyzer; +import org.eclipse.jdt.internal.corext.refactoring.Checks; +import org.eclipse.jdt.internal.corext.refactoring.CuCollectingSearchRequestor; +import org.eclipse.jdt.internal.corext.refactoring.ExceptionInfo; +import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment; +import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments; +import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil; +import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo; +import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; +import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory; +import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine; +import org.eclipse.jdt.internal.corext.refactoring.ReturnTypeInfo; +import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup; +import org.eclipse.jdt.internal.corext.refactoring.StubTypeContext; +import org.eclipse.jdt.internal.corext.refactoring.TypeContextChecker; +import org.eclipse.jdt.internal.corext.refactoring.base.JavaStringStatusContext; +import org.eclipse.jdt.internal.corext.refactoring.base.RefactoringStatusCodes; +import org.eclipse.jdt.internal.corext.refactoring.base.ReferencesInBinaryContext; +import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange; +import org.eclipse.jdt.internal.corext.refactoring.code.Invocations; +import org.eclipse.jdt.internal.corext.refactoring.delegates.DelegateMethodCreator; +import org.eclipse.jdt.internal.corext.refactoring.participants.JavaProcessors; +import org.eclipse.jdt.internal.corext.refactoring.rename.MethodChecks; +import org.eclipse.jdt.internal.corext.refactoring.rename.RefactoringAnalyzeUtil; +import org.eclipse.jdt.internal.corext.refactoring.rename.RippleMethodFinder2; +import org.eclipse.jdt.internal.corext.refactoring.rename.TempOccurrenceAnalyzer; +import org.eclipse.jdt.internal.corext.refactoring.tagging.IDelegateUpdating; +import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil; +import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext; +import org.eclipse.jdt.internal.corext.refactoring.util.JavadocUtil; +import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; +import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil; +import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager; +import org.eclipse.jdt.internal.corext.refactoring.util.TightSourceRangeComputer; +import org.eclipse.jdt.internal.corext.util.JdtFlags; +import org.eclipse.jdt.internal.corext.util.Messages; +import org.eclipse.jdt.internal.corext.util.SearchUtils; +import org.eclipse.jdt.ls.core.internal.corext.refactoring.RefactoringAvailabilityTester; + + +public class ChangeSignatureProcessor extends RefactoringProcessor implements IDelegateUpdating { + + private static final String ATTRIBUTE_RETURN= "return"; //$NON-NLS-1$ + private static final String ATTRIBUTE_VISIBILITY= "visibility"; //$NON-NLS-1$ + private static final String ATTRIBUTE_PARAMETER= "parameter"; //$NON-NLS-1$ + private static final String ATTRIBUTE_DEFAULT= "default"; //$NON-NLS-1$ + private static final String ATTRIBUTE_KIND= "kind"; //$NON-NLS-1$ + private static final String ATTRIBUTE_DELEGATE= "delegate"; //$NON-NLS-1$ + private static final String ATTRIBUTE_DEPRECATE= "deprecate"; //$NON-NLS-1$ + + private List fParameterInfos; + + private CompilationUnitRewrite fBaseCuRewrite; + private List fExceptionInfos; + private TextChangeManager fChangeManager; + + private IMethod fMethod; + private IMethod fTopMethod; + private IMethod[] fRippleMethods; + private SearchResultGroup[] fOccurrences; + private ReturnTypeInfo fReturnTypeInfo; + private String fMethodName; + private int fVisibility; + private static final String CONST_CLASS_DECL = "class A{";//$NON-NLS-1$ + private static final String CONST_ASSIGN = " i="; //$NON-NLS-1$ + private static final String CONST_CLOSE = ";}"; //$NON-NLS-1$ + + private StubTypeContext fContextCuStartEnd; + private int fOldVarargIndex; // initialized in checkVarargs() + + private BodyUpdater fBodyUpdater; + private IDefaultValueAdvisor fDefaultValueAdvisor; + + private ITypeHierarchy fCachedTypeHierarchy= null; + private boolean fDelegateUpdating; + private boolean fDelegateDeprecation; + + public ChangeSignatureProcessor(JavaRefactoringArguments arguments, RefactoringStatus status) throws JavaModelException { + this((IMethod) null); + status.merge(initialize(arguments)); + } + + /** + * Creates a new change signature refactoring. + * @param method the method, or null if invoked by scripting framework + * @throws JavaModelException if something's wrong with the given method + */ + public ChangeSignatureProcessor(IMethod method) throws JavaModelException { + fMethod= method; + fOldVarargIndex= -1; + fDelegateUpdating= false; + fDelegateDeprecation= true; + if (fMethod != null) { + fParameterInfos= createParameterInfoList(method); + // fExceptionInfos is created in checkInitialConditions + fReturnTypeInfo= new ReturnTypeInfo(Signature.toString(Signature.getReturnType(fMethod.getSignature()))); + fMethodName= fMethod.getElementName(); + fVisibility= JdtFlags.getVisibilityCode(fMethod); + } + } + + private static List createParameterInfoList(IMethod method) { + try { + String[] typeNames= method.getParameterTypes(); + String[] oldNames= method.getParameterNames(); + List result= new ArrayList<>(typeNames.length); + for (int i= 0; i < oldNames.length; i++){ + ParameterInfo parameterInfo; + if (i == oldNames.length - 1 && Flags.isVarargs(method.getFlags())) { + String varargSignature= typeNames[i]; + int arrayCount= Signature.getArrayCount(varargSignature); + String baseSignature= Signature.getElementType(varargSignature); + if (arrayCount > 1) + baseSignature= Signature.createArraySignature(baseSignature, arrayCount - 1); + parameterInfo= new ParameterInfo(Signature.toString(baseSignature) + ParameterInfo.ELLIPSIS, oldNames[i], i); + } else { + parameterInfo= new ParameterInfo(Signature.toString(typeNames[i]), oldNames[i], i); + } + result.add(parameterInfo); + } + return result; + } catch(JavaModelException e) { + JavaManipulationPlugin.log(e); + return new ArrayList<>(0); + } + } + + @Override + public String getProcessorName() { + return RefactoringCoreMessages.ChangeSignatureRefactoring_modify_Parameters; + } + + public IMethod getMethod() { + return fMethod; + } + + public String getMethodName() { + return fMethodName; + } + + public String getReturnTypeString() { + return fReturnTypeInfo.getNewTypeName(); + } + + public void setNewMethodName(String newMethodName){ + Assert.isNotNull(newMethodName); + fMethodName= newMethodName; + } + + public void setNewReturnTypeName(String newReturnTypeName){ + Assert.isNotNull(newReturnTypeName); + fReturnTypeInfo.setNewTypeName(newReturnTypeName); + } + + public boolean canChangeNameAndReturnType(){ + try { + return ! fMethod.isConstructor(); + } catch (JavaModelException e) { + JavaManipulationPlugin.log(e); + return false; + } + } + + /** + * @return visibility + * @see org.eclipse.jdt.core.dom.Modifier + */ + public int getVisibility(){ + return fVisibility; + } + + /** + * @param visibility new visibility + * @see org.eclipse.jdt.core.dom.Modifier + */ + public void setVisibility(int visibility){ + Assert.isTrue( visibility == Modifier.PUBLIC || + visibility == Modifier.PROTECTED || + visibility == Modifier.NONE || + visibility == Modifier.PRIVATE); + fVisibility= visibility; + } + + /* + * @see JdtFlags + */ + public int[] getAvailableVisibilities() throws JavaModelException{ + if (fTopMethod.getDeclaringType().isInterface()) + return new int[]{Modifier.PUBLIC}; + else if (fTopMethod.getDeclaringType().isEnum() && fTopMethod.isConstructor()) + return new int[]{ Modifier.NONE, + Modifier.PRIVATE}; + else + return new int[]{ Modifier.PUBLIC, + Modifier.PROTECTED, + Modifier.NONE, + Modifier.PRIVATE}; + } + + /** + * + * @return List of ParameterInfo objects. + */ + public List getParameterInfos(){ + return fParameterInfos; + } + + /** + * @return List of ExceptionInfo objects. + */ + public List getExceptionInfos(){ + return fExceptionInfos; + } + + public void setBodyUpdater(BodyUpdater bodyUpdater) { + fBodyUpdater= bodyUpdater; + } + + public CompilationUnitRewrite getBaseCuRewrite() { + return fBaseCuRewrite; + } + + //------------------- IDelegateUpdating ---------------------- + + @Override + public boolean canEnableDelegateUpdating() { + return true; + } + + @Override + public boolean getDelegateUpdating() { + return fDelegateUpdating; + } + + @Override + public void setDelegateUpdating(boolean updating) { + fDelegateUpdating= updating; + } + + @Override + public void setDeprecateDelegates(boolean deprecate) { + fDelegateDeprecation= deprecate; + } + + @Override + public boolean getDeprecateDelegates() { + return fDelegateDeprecation; + } + + @Override + public String getDelegateUpdatingTitle(boolean plural) { + if (plural) + return RefactoringCoreMessages.DelegateCreator_keep_original_changed_plural; + else + return RefactoringCoreMessages.DelegateCreator_keep_original_changed_singular; + } + + //------------------- /IDelegateUpdating --------------------- + + public RefactoringStatus checkSignature() { + return checkSignature(false); + } + + private RefactoringStatus checkSignature(boolean resolveBindings) { + RefactoringStatus result= new RefactoringStatus(); + checkMethodName(result); + if (result.hasFatalError()) + return result; + + checkParameterNamesAndValues(result); + if (result.hasFatalError()) + return result; + + checkForDuplicateParameterNames(result); + if (result.hasFatalError()) + return result; + + try { + RefactoringStatus[] typeStati; + if (resolveBindings) + typeStati= TypeContextChecker.checkAndResolveMethodTypes(fMethod, getStubTypeContext(), getNotDeletedInfos(), fReturnTypeInfo); + else + typeStati= TypeContextChecker.checkMethodTypesSyntax(fMethod, getNotDeletedInfos(), fReturnTypeInfo); + for (RefactoringStatus status : typeStati) { + result.merge(status); + } + + result.merge(checkVarargs()); + } catch (CoreException e) { + //cannot do anything here + throw new RuntimeException(e); + } + + //checkExceptions() unnecessary (IType always ok) + return result; + } + + public boolean isSignatureSameAsInitial() throws JavaModelException { + if (! isVisibilitySameAsInitial()) + return false; + if (! isMethodNameSameAsInitial()) + return false; + if (! isReturnTypeSameAsInitial()) + return false; + if (! areExceptionsSameAsInitial()) + return false; + + if (fMethod.getNumberOfParameters() == 0 && fParameterInfos.isEmpty()) + return true; + + if (areNamesSameAsInitial() && isOrderSameAsInitial() && areParameterTypesSameAsInitial()) + return true; + + return false; + } + + /** + * @return true if the new method cannot coexist with the old method since + * the signatures are too much alike + */ + public boolean isSignatureClashWithInitial() { + + if (!isMethodNameSameAsInitial()) + return false; // name has changed. + + if (fMethod.getNumberOfParameters() == 0 && fParameterInfos.isEmpty()) + return true; // name is equal and both parameter lists are empty + + // name is equal and there are some parameters. + // check if there are more or less parameters than before + + int no= getNotDeletedInfos().size(); + + if (fMethod.getNumberOfParameters() != no) + return false; + + // name is equal and parameter count is equal. + // check whether types remained the same + + if (isOrderSameAsInitial()) + return areParameterTypesSameAsInitial(); + else + return false; // could be more specific here + } + + private boolean areParameterTypesSameAsInitial() { + for (ParameterInfo info : fParameterInfos) { + if (! info.isAdded() && ! info.isDeleted() && info.isTypeNameChanged()) + return false; + } + return true; + } + + private boolean isReturnTypeSameAsInitial() { + return ! fReturnTypeInfo.isTypeNameChanged(); + } + + private boolean isMethodNameSameAsInitial() { + return fMethodName.equals(fMethod.getElementName()); + } + + private boolean areExceptionsSameAsInitial() { + for (ExceptionInfo info : fExceptionInfos) { + if (! info.isOld()) + return false; + } + return true; + } + + private void checkParameterNamesAndValues(RefactoringStatus result) { + int i= 1; + for (Iterator iter= fParameterInfos.iterator(); iter.hasNext(); i++) { + ParameterInfo info= iter.next(); + if (info.isDeleted()) + continue; + checkParameterName(result, info, i); + if (result.hasFatalError()) + return; + if (info.isAdded()) { + checkParameterDefaultValue(result, info); + if (result.hasFatalError()) + return; + } + } + } + + private void checkParameterName(RefactoringStatus result, ParameterInfo info, int position) { + if (info.getNewName().trim().length() == 0) { + result.addFatalError(Messages.format( + RefactoringCoreMessages.ChangeSignatureRefactoring_param_name_not_empty, Integer.toString(position))); + } else { + result.merge(Checks.checkTempName(info.getNewName(), fMethod)); + } + } + + private void checkMethodName(RefactoringStatus result) { + if (isMethodNameSameAsInitial() || ! canChangeNameAndReturnType()) + return; + if ("".equals(fMethodName.trim())) { //$NON-NLS-1$ + String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_method_name_not_empty; + result.addFatalError(msg); + return; + } + if (fMethodName.equals(fMethod.getDeclaringType().getElementName())) { + String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_constructor_name; + result.addWarning(msg); + } + result.merge(Checks.checkMethodName(fMethodName, fMethod)); + } + + private void checkParameterDefaultValue(RefactoringStatus result, ParameterInfo info) { + if (fDefaultValueAdvisor != null) + return; + if (info.isNewVarargs()) { + if (! isValidVarargsExpression(info.getDefaultValue())){ + String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_invalid_expression, new String[]{info.getDefaultValue()}); + result.addFatalError(msg); + } + return; + } + + if (info.getDefaultValue().trim().isEmpty()){ + String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_default_value, BasicElementLabels.getJavaElementName(info.getNewName())); + result.addFatalError(msg); + return; + } + if (! isValidExpression(info.getDefaultValue())){ + String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_invalid_expression, new String[]{info.getDefaultValue()}); + result.addFatalError(msg); + } + } + + private RefactoringStatus checkVarargs() throws JavaModelException { + RefactoringStatus result= checkOriginalVarargs(); + if (result != null) + return result; + + if (fRippleMethods != null) { + for (IMethod rippleMethod : fRippleMethods) { + if (! JdtFlags.isVarargs(rippleMethod)) + continue; + + // Vararg method can override method that takes an array as last argument + fOldVarargIndex= rippleMethod.getNumberOfParameters() - 1; + List notDeletedInfos= getNotDeletedInfos(); + for (ParameterInfo info : notDeletedInfos) { + if (fOldVarargIndex != -1 && info.getOldIndex() == fOldVarargIndex && ! info.isNewVarargs()) { + String rippleMethodType= rippleMethod.getDeclaringType().getFullyQualifiedName('.'); + String message= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_ripple_cannot_convert_vararg, new Object[] { BasicElementLabels.getJavaElementName(info.getNewName()), BasicElementLabels.getJavaElementName(rippleMethodType)}); + return RefactoringStatus.createFatalErrorStatus(message, JavaStatusContext.create(rippleMethod)); + } + } + } + } + + return null; + } + + private RefactoringStatus checkOriginalVarargs() throws JavaModelException { + if (JdtFlags.isVarargs(fMethod)) + fOldVarargIndex= fMethod.getNumberOfParameters() - 1; + List notDeletedInfos= getNotDeletedInfos(); + for (int i= 0; i < notDeletedInfos.size(); i++) { + ParameterInfo info= notDeletedInfos.get(i); + if (info.isOldVarargs() && ! info.isNewVarargs()) + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_cannot_convert_vararg, BasicElementLabels.getJavaElementName(info.getNewName()))); + if (i != notDeletedInfos.size() - 1) { + // not the last parameter + if (info.isNewVarargs()) + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_vararg_must_be_last, BasicElementLabels.getJavaElementName(info.getNewName()))); + } + } + return null; + } + + private RefactoringStatus checkTypeVariables() { + if (fRippleMethods.length == 1) + return null; + + RefactoringStatus result= new RefactoringStatus(); + if (fReturnTypeInfo.isTypeNameChanged() && fReturnTypeInfo.getNewTypeBinding() != null) { + HashSet typeVariablesCollector= new HashSet<>(); + collectTypeVariables(fReturnTypeInfo.getNewTypeBinding(), typeVariablesCollector); + if (!typeVariablesCollector.isEmpty()) { + ITypeBinding first= typeVariablesCollector.iterator().next(); + String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_return_type_contains_type_variable, new String[] {BasicElementLabels.getJavaElementName(fReturnTypeInfo.getNewTypeName()), BasicElementLabels.getJavaElementName(first.getName())}); + result.addError(msg); + } + } + + for (ParameterInfo info : getNotDeletedInfos()) { + if (info.isTypeNameChanged() && info.getNewTypeBinding() != null) { + HashSet typeVariablesCollector= new HashSet<>(); + collectTypeVariables(info.getNewTypeBinding(), typeVariablesCollector); + if (!typeVariablesCollector.isEmpty()) { + ITypeBinding first= typeVariablesCollector.iterator().next(); + String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_parameter_type_contains_type_variable, new String[] {BasicElementLabels.getJavaElementName(info.getNewTypeName()), BasicElementLabels.getJavaElementName(info.getNewName()), BasicElementLabels.getJavaElementName(first.getName())}); + result.addError(msg); + } + } + } + return result; + } + + private void collectTypeVariables(ITypeBinding typeBinding, Set typeVariablesCollector) { + if (typeBinding.isTypeVariable()) { + typeVariablesCollector.add(typeBinding); + for (ITypeBinding typeBound : typeBinding.getTypeBounds()) { + collectTypeVariables(typeBound, typeVariablesCollector); + } + + } else if (typeBinding.isArray()) { + collectTypeVariables(typeBinding.getElementType(), typeVariablesCollector); + + } else if (typeBinding.isParameterizedType()) { + for (ITypeBinding typeArgument : typeBinding.getTypeArguments()) { + collectTypeVariables(typeArgument, typeVariablesCollector); + } + + } else if (typeBinding.isWildcardType()) { + ITypeBinding bound= typeBinding.getBound(); + if (bound != null) { + collectTypeVariables(bound, typeVariablesCollector); + } + } + } + + public static boolean isValidExpression(String string){ + String trimmed= string.trim(); + if ("".equals(trimmed)) //speed up for a common case //$NON-NLS-1$ + return false; + StringBuilder cuBuff= new StringBuilder(); + cuBuff.append(CONST_CLASS_DECL) + .append("Object") //$NON-NLS-1$ + .append(CONST_ASSIGN); + int offset= cuBuff.length(); + cuBuff.append(trimmed) + .append(CONST_CLOSE); + ASTParser p= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL); + p.setSource(cuBuff.toString().toCharArray()); + CompilationUnit cu= (CompilationUnit) p.createAST(null); + Selection selection= Selection.createFromStartLength(offset, trimmed.length()); + SelectionAnalyzer analyzer= new SelectionAnalyzer(selection, false); + cu.accept(analyzer); + ASTNode selected= analyzer.getFirstSelectedNode(); + return (selected instanceof Expression) && + trimmed.equals(cuBuff.substring(cu.getExtendedStartPosition(selected), cu.getExtendedStartPosition(selected) + cu.getExtendedLength(selected))); + } + + public static boolean isValidVarargsExpression(String string) { + String trimmed= string.trim(); + if ("".equals(trimmed)) //speed up for a common case //$NON-NLS-1$ + return true; + StringBuilder cuBuff= new StringBuilder(); + cuBuff.append("class A{ {m("); //$NON-NLS-1$ + int offset= cuBuff.length(); + cuBuff.append(trimmed) + .append(");}}"); //$NON-NLS-1$ + ASTParser p= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL); + p.setSource(cuBuff.toString().toCharArray()); + CompilationUnit cu= (CompilationUnit) p.createAST(null); + Selection selection= Selection.createFromStartLength(offset, trimmed.length()); + SelectionAnalyzer analyzer= new SelectionAnalyzer(selection, false); + cu.accept(analyzer); + ASTNode[] selectedNodes= analyzer.getSelectedNodes(); + if (selectedNodes.length == 0) + return false; + for (ASTNode selectedNode : selectedNodes) { + if (!(selectedNode instanceof Expression)) { + return false; + } + } + return true; + } + + public StubTypeContext getStubTypeContext() { + try { + if (fContextCuStartEnd == null) + fContextCuStartEnd= TypeContextChecker.createStubTypeContext(getCu(), fBaseCuRewrite.getRoot(), fMethod.getSourceRange().getOffset()); + } catch (CoreException e) { + //cannot do anything here + throw new RuntimeException(e); + } + return fContextCuStartEnd; + } + + private ITypeHierarchy getCachedTypeHierarchy(IProgressMonitor monitor) throws JavaModelException { + if (fCachedTypeHierarchy == null) + fCachedTypeHierarchy= fMethod.getDeclaringType().newTypeHierarchy(new SubProgressMonitor(monitor, 1)); + return fCachedTypeHierarchy; + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor monitor) throws CoreException { + try { + monitor.beginTask("", 5); //$NON-NLS-1$ + RefactoringStatus result= Checks.checkIfCuBroken(fMethod); + if (result.hasFatalError()) + return result; + if (fMethod == null || !fMethod.exists()) { + String message= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_method_deleted, BasicElementLabels.getFileName(getCu())); + return RefactoringStatus.createFatalErrorStatus(message); + } + if (fMethod.getDeclaringType().isInterface()) { + fTopMethod= MethodChecks.overridesAnotherMethod(fMethod, fMethod.getDeclaringType().newSupertypeHierarchy(new SubProgressMonitor(monitor, 1))); + monitor.worked(1); + } else if (MethodChecks.isVirtual(fMethod)) { + ITypeHierarchy hierarchy= getCachedTypeHierarchy(new SubProgressMonitor(monitor, 1)); + fTopMethod= MethodChecks.isDeclaredInInterface(fMethod, hierarchy, new SubProgressMonitor(monitor, 1)); + if (fTopMethod == null) + fTopMethod= MethodChecks.overridesAnotherMethod(fMethod, hierarchy); + } + if (fTopMethod == null) + fTopMethod= fMethod; + if (! fTopMethod.equals(fMethod)) { + if (fTopMethod.getDeclaringType().isInterface()) { + RefactoringStatusContext context= JavaStatusContext.create(fTopMethod); + String message= Messages.format(RefactoringCoreMessages.MethodChecks_implements, + new String[]{JavaElementUtil.createMethodSignature(fTopMethod), BasicElementLabels.getJavaElementName(fTopMethod.getDeclaringType().getFullyQualifiedName('.'))}); + return RefactoringStatus.createStatus(RefactoringStatus.FATAL, message, context, CorextCore.getPluginId(), RefactoringStatusCodes.METHOD_DECLARED_IN_INTERFACE, fTopMethod); + } else { + RefactoringStatusContext context= JavaStatusContext.create(fTopMethod); + String message= Messages.format(RefactoringCoreMessages.MethodChecks_overrides, + new String[]{JavaElementUtil.createMethodSignature(fTopMethod), BasicElementLabels.getJavaElementName(fTopMethod.getDeclaringType().getFullyQualifiedName('.'))}); + return RefactoringStatus.createStatus(RefactoringStatus.FATAL, message, context, CorextCore.getPluginId(), RefactoringStatusCodes.OVERRIDES_ANOTHER_METHOD, fTopMethod); + } + } + + if (monitor.isCanceled()) + throw new OperationCanceledException(); + + if (fBaseCuRewrite == null || !fBaseCuRewrite.getCu().equals(getCu())) { + fBaseCuRewrite= new CompilationUnitRewrite(getCu()); + fBaseCuRewrite.getASTRewrite().setTargetSourceRangeComputer(new TightSourceRangeComputer()); + } + for (RefactoringStatus status : TypeContextChecker.checkMethodTypesSyntax(fMethod, getParameterInfos(), fReturnTypeInfo)) { + result.merge(status); + } + monitor.worked(1); + result.merge(createExceptionInfoList()); + monitor.worked(1); + return result; + } finally { + monitor.done(); + } + } + + private RefactoringStatus createExceptionInfoList() { + if (fExceptionInfos == null || fExceptionInfos.isEmpty()) { + fExceptionInfos= new ArrayList<>(0); + try { + ASTNode nameNode= NodeFinder.perform(fBaseCuRewrite.getRoot(), fMethod.getNameRange()); + if (nameNode == null || !(nameNode instanceof Name) || !(nameNode.getParent() instanceof MethodDeclaration)) + return null; + MethodDeclaration methodDeclaration= (MethodDeclaration) nameNode.getParent(); + List exceptions= methodDeclaration.thrownExceptionTypes(); + List result= new ArrayList<>(exceptions.size()); + for (Type type : exceptions) { + ITypeBinding typeBinding= type.resolveBinding(); + if (typeBinding == null) + return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ChangeSignatureRefactoring_no_exception_binding); + IJavaElement element= typeBinding.getJavaElement(); + result.add(ExceptionInfo.createInfoForOldException(element, typeBinding)); + } + fExceptionInfos= result; + } catch (JavaModelException e) { + JavaManipulationPlugin.log(e); + } + } + return null; + } + + @Override + public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException, OperationCanceledException { + try { + pm.beginTask(RefactoringCoreMessages.ChangeSignatureRefactoring_checking_preconditions, 8); + RefactoringStatus result= new RefactoringStatus(); + clearManagers(); + fBaseCuRewrite.clearASTAndImportRewrites(); + fBaseCuRewrite.getASTRewrite().setTargetSourceRangeComputer(new TightSourceRangeComputer()); + + if (isSignatureSameAsInitial()) + return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ChangeSignatureRefactoring_unchanged); + result.merge(checkSignature(true)); + if (result.hasFatalError()) + return result; + + if (fDelegateUpdating && isSignatureClashWithInitial()) + result.merge(RefactoringStatus.createErrorStatus(RefactoringCoreMessages.ChangeSignatureRefactoring_old_and_new_signatures_not_sufficiently_different )); + + String binaryRefsDescription= Messages.format(RefactoringCoreMessages.ReferencesInBinaryContext_ref_in_binaries_description , BasicElementLabels.getJavaElementName(getMethodName())); + ReferencesInBinaryContext binaryRefs= new ReferencesInBinaryContext(binaryRefsDescription); + + fRippleMethods= RippleMethodFinder2.getRelatedMethods(fMethod, binaryRefs, new SubProgressMonitor(pm, 1), null); + result.merge(checkVarargs()); + if (result.hasFatalError()) + return result; + + fOccurrences= findOccurrences(new SubProgressMonitor(pm, 1), binaryRefs, result); + binaryRefs.addErrorIfNecessary(result); + + result.merge(checkVisibilityChanges()); + result.merge(checkTypeVariables()); + + //TODO: + // We need a common way of dealing with possible compilation errors for all occurrences, + // including visibility problems, shadowing and missing throws declarations. + + if (! isOrderSameAsInitial()) + result.merge(checkReorderings(new SubProgressMonitor(pm, 1))); + else + pm.worked(1); + + //TODO (bug 58616): check whether changed signature already exists somewhere in the ripple, + // - error if exists + // - warn if exists with different parameter types (may cause overloading) + + if (! areNamesSameAsInitial()) + result.merge(checkRenamings(new SubProgressMonitor(pm, 1))); + else + pm.worked(1); + if (result.hasFatalError()) + return result; + +// resolveTypesWithoutBindings(new SubProgressMonitor(pm, 1)); // already done in checkSignature(true) + + createChangeManager(new SubProgressMonitor(pm, 1), result); + fCachedTypeHierarchy= null; + + if (mustAnalyzeAstOfDeclaringCu()) + result.merge(checkCompilationofDeclaringCu()); //TODO: should also check in ripple methods (move into createChangeManager) + if (result.hasFatalError()) + return result; + + Checks.addModifiedFilesToChecker(getAllFilesToModify(), context); + return result; + } finally { + pm.done(); + } + } + + protected void clearManagers() { + fChangeManager= null; + } + + private RefactoringStatus checkVisibilityChanges() throws JavaModelException { + if (isVisibilitySameAsInitial()) + return null; + if (fRippleMethods.length == 1) + return null; + Assert.isTrue(JdtFlags.getVisibilityCode(fMethod) != Modifier.PRIVATE); + if (fVisibility == Modifier.PRIVATE) + return RefactoringStatus.createWarningStatus(RefactoringCoreMessages.ChangeSignatureRefactoring_non_virtual); + return null; + } + + public String getOldMethodSignature() throws JavaModelException{ + StringBuilder buff= new StringBuilder(); + + int flags= getMethod().getFlags(); + buff.append(getVisibilityString(flags)); + if (Flags.isStatic(flags)) { + buff.append("static "); //$NON-NLS-1$ + } else if (Flags.isDefaultMethod(flags)) { + buff.append("default "); //$NON-NLS-1$ + } + if (! getMethod().isConstructor()) + buff.append(fReturnTypeInfo.getOldTypeName()) + .append(' '); + + buff.append(JavaElementLabelsCore.getElementLabel(fMethod.getParent(), JavaElementLabelsCore.ALL_FULLY_QUALIFIED)); + buff.append('.'); + buff.append(fMethod.getElementName()) + .append(Signature.C_PARAM_START) + .append(getOldMethodParameters()) + .append(Signature.C_PARAM_END); + + buff.append(getOldMethodThrows()); + + return BasicElementLabels.getJavaCodeString(buff.toString()); + } + + public String getNewMethodSignature() throws JavaModelException{ + StringBuilder buff= new StringBuilder(); + + buff.append(getVisibilityString(fVisibility)); + int flags= getMethod().getFlags(); + if (Flags.isStatic(flags)) { + buff.append("static "); //$NON-NLS-1$ + } else if (Flags.isDefaultMethod(flags)) { + buff.append("default "); //$NON-NLS-1$ + } + if (! getMethod().isConstructor()) + buff.append(getReturnTypeString()) + .append(' '); + + buff.append(getMethodName()) + .append(Signature.C_PARAM_START) + .append(getMethodParameters()) + .append(Signature.C_PARAM_END); + + buff.append(getMethodThrows()); + + return BasicElementLabels.getJavaCodeString(buff.toString()); + } + + private String getVisibilityString(int visibility) { + String visibilityString= JdtFlags.getVisibilityString(visibility); + if ("".equals(visibilityString)) //$NON-NLS-1$ + return visibilityString; + return visibilityString + ' '; + } + + private String getMethodThrows() { + final String throwsString= " throws "; //$NON-NLS-1$ + StringBuilder buff= new StringBuilder(throwsString); + for (ExceptionInfo info : fExceptionInfos) { + if (! info.isDeleted()) { + buff.append(info.getElement().getElementName()); + buff.append(", "); //$NON-NLS-1$ + } + } + if (buff.length() == throwsString.length()) + return ""; //$NON-NLS-1$ + buff.delete(buff.length() - 2, buff.length()); + return buff.toString(); + } + + private String getOldMethodThrows() { + final String throwsString= " throws "; //$NON-NLS-1$ + StringBuilder buff= new StringBuilder(throwsString); + for (ExceptionInfo info : fExceptionInfos) { + if (! info.isAdded()) { + buff.append(info.getElement().getElementName()); + buff.append(", "); //$NON-NLS-1$ + } + } + if (buff.length() == throwsString.length()) + return ""; //$NON-NLS-1$ + buff.delete(buff.length() - 2, buff.length()); + return buff.toString(); + } + + private void checkForDuplicateParameterNames(RefactoringStatus result){ + Set found= new HashSet<>(); + Set doubled= new HashSet<>(); + for (ParameterInfo info : getNotDeletedInfos()) { + String newName= info.getNewName(); + if (found.contains(newName) && !doubled.contains(newName)){ + result.addFatalError(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_duplicate_name, BasicElementLabels.getJavaElementName(newName))); + doubled.add(newName); + } else { + found.add(newName); + } + } + } + + private ICompilationUnit getCu() { + return fMethod.getCompilationUnit(); + } + + private boolean mustAnalyzeAstOfDeclaringCu() throws JavaModelException{ + return !JdtFlags.isAbstract(getMethod()) && !JdtFlags.isNative(getMethod()) && !getMethod().getDeclaringType().isInterface(); + } + + private RefactoringStatus checkCompilationofDeclaringCu() throws CoreException { + ICompilationUnit cu= getCu(); + TextChange change= fChangeManager.get(cu); + String newCuSource= change.getPreviewContent(new NullProgressMonitor()); + CompilationUnit newCUNode= new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL).parse(newCuSource, cu, true, false, null); + RefactoringStatus result= new RefactoringStatus(); + for (IProblem problem : RefactoringAnalyzeUtil.getIntroducedCompileProblems(newCUNode, fBaseCuRewrite.getRoot())) { + if (shouldReport(problem, newCUNode)) + result.addEntry(new RefactoringStatusEntry((problem.isError() ? RefactoringStatus.ERROR : RefactoringStatus.WARNING), problem.getMessage(), new JavaStringStatusContext(newCuSource, SourceRangeFactory.create(problem)))); + } + return result; + } + + /** + * Evaluates if a problem needs to be reported. + * @param problem the problem + * @param cu the AST containing the new source + * @return return true if the problem needs to be reported + */ + protected boolean shouldReport(IProblem problem, CompilationUnit cu) { + if (! problem.isError()) + return false; + if (problem.getID() == IProblem.UndefinedType) //reported when trying to import + return false; + return true; + } + + private String getOldMethodParameters() { + StringBuilder buff= new StringBuilder(); + int i= 0; + for (Iterator iter= getNotAddedInfos().iterator(); iter.hasNext(); i++) { + ParameterInfo info= iter.next(); + if (i != 0 ) + buff.append(", "); //$NON-NLS-1$ + buff.append(createDeclarationString(info)); + } + return buff.toString(); + } + + private String getMethodParameters() { + StringBuilder buff= new StringBuilder(); + int i= 0; + for (Iterator iter= getNotDeletedInfos().iterator(); iter.hasNext(); i++) { + ParameterInfo info= iter.next(); + if (i != 0 ) + buff.append(", "); //$NON-NLS-1$ + buff.append(createDeclarationString(info)); + } + return buff.toString(); + } + + private List getAddedInfos(){ + List result= new ArrayList<>(1); + for (ParameterInfo info : fParameterInfos) { + if (info.isAdded()) + result.add(info); + } + return result; + } + + private List getDeletedInfos(){ + List result= new ArrayList<>(1); + for (ParameterInfo info : fParameterInfos) { + if (info.isDeleted()) + result.add(info); + } + return result; + } + + private List getNotAddedInfos(){ + List all= new ArrayList<>(fParameterInfos); + all.removeAll(getAddedInfos()); + return all; + } + + private List getNotDeletedInfos(){ + List all= new ArrayList<>(fParameterInfos); + all.removeAll(getDeletedInfos()); + return all; + } + + private boolean areNamesSameAsInitial() { + for (ParameterInfo info : fParameterInfos) { + if (info.isRenamed()) + return false; + } + return true; + } + + private boolean isOrderSameAsInitial(){ + int i= 0; + for (Iterator iter= fParameterInfos.iterator(); iter.hasNext(); i++) { + ParameterInfo info= iter.next(); + if (info.getOldIndex() != i) // includes info.isAdded() + return false; + if (info.isDeleted()) + return false; + } + return true; + } + + private RefactoringStatus checkReorderings(IProgressMonitor pm) throws JavaModelException { + try{ + pm.beginTask(RefactoringCoreMessages.ChangeSignatureRefactoring_checking_preconditions, 1); + return checkNativeMethods(); + } finally{ + pm.done(); + } + } + + private RefactoringStatus checkRenamings(IProgressMonitor pm) throws JavaModelException { + try{ + pm.beginTask(RefactoringCoreMessages.ChangeSignatureRefactoring_checking_preconditions, 1); + return checkParameterNamesInRippleMethods(); + } finally{ + pm.done(); + } + } + + private RefactoringStatus checkParameterNamesInRippleMethods() throws JavaModelException { + RefactoringStatus result= new RefactoringStatus(); + Set newParameterNames= getNewParameterNamesList(); + for (IMethod rippleMethod : fRippleMethods) { + for (String paramName : rippleMethod.getParameterNames()) { + if (newParameterNames.contains(paramName)) { + String[] args= new String[]{JavaElementUtil.createMethodSignature(rippleMethod), BasicElementLabels.getJavaElementName(paramName)}; + String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_already_has, args); + RefactoringStatusContext context= JavaStatusContext.create(rippleMethod.getCompilationUnit(), rippleMethod.getNameRange()); + result.addError(msg, context); + } + } + } + return result; + } + + private Set getNewParameterNamesList() { + Set oldNames= getOriginalParameterNames(); + Set currentNames= getNamesOfNotDeletedParameters(); + currentNames.removeAll(oldNames); + return currentNames; + } + + private Set getNamesOfNotDeletedParameters() { + Set result= new HashSet<>(); + for (ParameterInfo info : getNotDeletedInfos()) { + result.add(info.getNewName()); + } + return result; + } + + private Set getOriginalParameterNames() { + Set result= new HashSet<>(); + for (ParameterInfo info : fParameterInfos) { + if (! info.isAdded()) + result.add(info.getOldName()); + } + return result; + } + + private RefactoringStatus checkNativeMethods() throws JavaModelException{ + RefactoringStatus result= new RefactoringStatus(); + for (IMethod rippleMethod : fRippleMethods) { + if (JdtFlags.isNative(rippleMethod)) { + String message= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_native, new String[]{JavaElementUtil.createMethodSignature(rippleMethod), BasicElementLabels.getJavaElementName(rippleMethod.getDeclaringType().getFullyQualifiedName('.'))}); + result.addError(message, JavaStatusContext.create(rippleMethod)); + } + } + return result; + } + + private IFile[] getAllFilesToModify(){ + return ResourceUtil.getFiles(fChangeManager.getAllCompilationUnits()); + } + + public Change[] getAllChanges() { + return fChangeManager.getAllChanges(); + } + + @Override + public Change createChange(IProgressMonitor pm) { + pm.beginTask("", 1); //$NON-NLS-1$ + try { + return new DynamicValidationRefactoringChange(createDescriptor(), doGetRefactoringChangeName(), getAllChanges()); + } finally { + clearManagers(); + pm.done(); + } + } + + private ChangeMethodSignatureArguments getParticipantArguments() { + ArrayList parameterList= new ArrayList<>(); + String[] originalParameterTypeSigs= fMethod.getParameterTypes(); + + for (ParameterInfo pi : getParameterInfos()) { + if (!pi.isDeleted()) { + int oldIndex= pi.isAdded() ? -1 : pi.getOldIndex(); + String newName= pi.getNewName(); + String typeSig; + if (pi.isTypeNameChanged()) { + String newType= pi.getNewTypeName(); + if (pi.isNewVarargs()) { + newType= ParameterInfo.stripEllipsis(newType) + "[]"; //$NON-NLS-1$ + } + typeSig= Signature.createTypeSignature(newType, false); + } else { + typeSig= originalParameterTypeSigs[pi.getOldIndex()]; + } + String defaultValue= pi.getDefaultValue(); + parameterList.add(new Parameter(oldIndex, newName, typeSig, defaultValue)); + } + } + Parameter[] parameters= parameterList.toArray(new Parameter[parameterList.size()]); + + ArrayList exceptionList= new ArrayList<>(); + List exceptionInfos= getExceptionInfos(); + for (int i= 0; i < exceptionInfos.size(); i++) { + ExceptionInfo ei= exceptionInfos.get(i); + if (!ei.isDeleted()) { + int oldIndex= ei.isAdded() ? -1 : i; + String qualifiedTypeName= ei.getFullyQualifiedName(); + String newTypeSig= Signature.createTypeSignature(qualifiedTypeName, true); + exceptionList.add(new ThrownException(oldIndex, newTypeSig)); + } + } + ThrownException[] exceptions= exceptionList.toArray(new ThrownException[exceptionList.size()]); + String returnTypeSig; + if (fReturnTypeInfo.isTypeNameChanged()) { + returnTypeSig= Signature.createTypeSignature(fReturnTypeInfo.getNewTypeName(), false); + } else { + try { + returnTypeSig= fMethod.getReturnType(); + } catch (JavaModelException e) { + returnTypeSig= Signature.createTypeSignature(fReturnTypeInfo.getNewTypeName(), false); + } + } + return new ChangeMethodSignatureArguments(fMethodName, returnTypeSig, fVisibility, parameters, exceptions, fDelegateUpdating); + } + + + public JavaRefactoringDescriptor createDescriptor() { + final Map arguments= new HashMap<>(); + String project= null; + IJavaProject javaProject= fMethod.getJavaProject(); + if (javaProject != null) + project= javaProject.getElementName(); + ChangeMethodSignatureDescriptor descriptor= null; + try { + final String description= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_descriptor_description_short, BasicElementLabels.getJavaElementName(fMethod.getElementName())); + final String header= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_descriptor_description, new String[] { getOldMethodSignature(), getNewMethodSignature()}); + final JDTRefactoringDescriptorComment comment= createComment(project, header); + descriptor= RefactoringSignatureDescriptorFactory.createChangeMethodSignatureDescriptor(project, description, comment.asString(), arguments, getDescriptorFlags()); + arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT, JavaRefactoringDescriptorUtil.elementToHandle(project,fMethod)); + arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME, fMethodName); + arguments.put(ATTRIBUTE_DELEGATE, Boolean.toString(fDelegateUpdating)); + arguments.put(ATTRIBUTE_DEPRECATE, Boolean.toString(fDelegateDeprecation)); + if (fReturnTypeInfo.isTypeNameChanged()) + arguments.put(ATTRIBUTE_RETURN, fReturnTypeInfo.getNewTypeName()); + try { + if (!isVisibilitySameAsInitial()) + arguments.put(ATTRIBUTE_VISIBILITY, Integer.toString(fVisibility)); + } catch (JavaModelException exception) { + JavaManipulationPlugin.log(exception); + } + int count= 1; + for (ParameterInfo info : fParameterInfos) { + final StringBuilder buffer= new StringBuilder(64); + if (info.isAdded()) + buffer.append("{added}"); //$NON-NLS-1$ + else + buffer.append(info.getOldTypeName()); + buffer.append(" "); //$NON-NLS-1$ + if (info.isAdded()) + buffer.append("{added}"); //$NON-NLS-1$ + else + buffer.append(info.getOldName()); + buffer.append(" "); //$NON-NLS-1$ + buffer.append(info.getOldIndex()); + buffer.append(" "); //$NON-NLS-1$ + if (info.isDeleted()) + buffer.append("{deleted}"); //$NON-NLS-1$ + else + buffer.append(info.getNewTypeName().replace(" ", "")); //$NON-NLS-1$//$NON-NLS-2$ + buffer.append(" "); //$NON-NLS-1$ + if (info.isDeleted()) + buffer.append("{deleted}"); //$NON-NLS-1$ + else + buffer.append(info.getNewName()); + buffer.append(" "); //$NON-NLS-1$ + buffer.append(info.isDeleted()); + arguments.put(ATTRIBUTE_PARAMETER + count, buffer.toString()); + final String value= info.getDefaultValue(); + if (value != null && !"".equals(value)) //$NON-NLS-1$ + arguments.put(ATTRIBUTE_DEFAULT + count, value); + count++; + } + count= 1; + for (ExceptionInfo info : fExceptionInfos) { + arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + count, JavaRefactoringDescriptorUtil.elementToHandle(project,info.getElement())); + arguments.put(ATTRIBUTE_KIND + count, Integer.toString(info.getKind())); + count++; + } + } catch (JavaModelException exception) { + JavaManipulationPlugin.log(exception); + return null; + } + return descriptor; + } + + protected int getDescriptorFlags() { + int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE; + try { + if (!Flags.isPrivate(fMethod.getFlags())) + flags|= RefactoringDescriptor.MULTI_CHANGE; + final IType declaring= fMethod.getDeclaringType(); + if (declaring.isAnonymous() || declaring.isLocal()) + flags|= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT; + } catch (JavaModelException exception) { + JavaManipulationPlugin.log(exception); + } + return flags; + } + + private JDTRefactoringDescriptorComment createComment(String project, final String header) throws JavaModelException { + final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header); + if (!fMethod.getElementName().equals(fMethodName)) + comment.addSetting(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_new_name_pattern, BasicElementLabels.getJavaElementName(fMethodName))); + if (!isVisibilitySameAsInitial()) { + String visibility= JdtFlags.getVisibilityString(fVisibility); + if ("".equals(visibility)) //$NON-NLS-1$ + visibility= RefactoringCoreMessages.ChangeSignatureRefactoring_default_visibility; + comment.addSetting(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_new_visibility_pattern, visibility)); + } + if (fReturnTypeInfo.isTypeNameChanged()) + comment.addSetting(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_new_return_type_pattern, BasicElementLabels.getJavaElementName(fReturnTypeInfo.getNewTypeName()))); + List deleted= new ArrayList<>(); + List added= new ArrayList<>(); + List changed= new ArrayList<>(); + for (ParameterInfo info : fParameterInfos) { + if (info.isDeleted()) + deleted.add(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_deleted_parameter_pattern, new String[] { BasicElementLabels.getJavaElementName(info.getOldTypeName()), BasicElementLabels.getJavaElementName(info.getOldName())})); + else if (info.isAdded()) + added.add(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_added_parameter_pattern, new String[] { BasicElementLabels.getJavaElementName(info.getNewTypeName()), BasicElementLabels.getJavaElementName(info.getNewName())})); + else if (info.isRenamed() || info.isTypeNameChanged() || info.isVarargChanged()) + changed.add(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_changed_parameter_pattern, new String[] { BasicElementLabels.getJavaElementName(info.getOldTypeName()), BasicElementLabels.getJavaElementName(info.getOldName())})); + } + if (!added.isEmpty()) + comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_added_parameters, added.toArray(new String[added.size()]))); + if (!deleted.isEmpty()) + comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_removed_parameters, deleted.toArray(new String[deleted.size()]))); + if (!changed.isEmpty()) + comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_changed_parameters, changed.toArray(new String[changed.size()]))); + added.clear(); + deleted.clear(); + changed.clear(); + for (ExceptionInfo info : fExceptionInfos) { + if (info.isAdded()) + added.add(info.getElement().getElementName()); + else if (info.isDeleted()) + deleted.add(info.getElement().getElementName()); + } + if (!added.isEmpty()) + comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_added_exceptions, added.toArray(new String[added.size()]))); + if (!deleted.isEmpty()) + comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_removed_exceptions, deleted.toArray(new String[deleted.size()]))); + return comment; + } + + protected String doGetRefactoringChangeName() { + return RefactoringCoreMessages.ChangeSignatureRefactoring_restructure_parameters; + } + + private TextChangeManager createChangeManager(IProgressMonitor pm, RefactoringStatus result) throws CoreException { + pm.beginTask(RefactoringCoreMessages.ChangeSignatureRefactoring_preview, 2); + fChangeManager= new TextChangeManager(); + boolean isNoArgConstructor= isNoArgConstructor(); + Map> namedSubclassMapping= null; + if (isNoArgConstructor){ + //create only when needed; + namedSubclassMapping= createNamedSubclassMapping(new SubProgressMonitor(pm, 1)); + }else{ + pm.worked(1); + } + for (SearchResultGroup occurrence : fOccurrences) { + if (pm.isCanceled()) + throw new OperationCanceledException(); + SearchResultGroup group= occurrence; + ICompilationUnit cu= group.getCompilationUnit(); + if (cu == null) + continue; + CompilationUnitRewrite cuRewrite; + if (cu.equals(getCu())) { + cuRewrite= fBaseCuRewrite; + } else { + cuRewrite= new CompilationUnitRewrite(cu); + cuRewrite.getASTRewrite().setTargetSourceRangeComputer(new TightSourceRangeComputer()); + } + //IntroduceParameterObjectRefactoring needs to update declarations first: + List> deferredUpdates= new ArrayList<>(); + for (ASTNode node : ASTNodeSearchUtil.findNodes(group.getSearchResults(), cuRewrite.getRoot())) { + OccurrenceUpdate update= createOccurrenceUpdate(node, cuRewrite, result); + if (update instanceof DeclarationUpdate) { + update.updateNode(); + } else { + deferredUpdates.add(update); + } + } + for (OccurrenceUpdate occurrenceUpdate : deferredUpdates) { + occurrenceUpdate.updateNode(); + } + + if (isNoArgConstructor && namedSubclassMapping.containsKey(cu)){ + //only non-anonymous subclasses may have noArgConstructors to modify - see bug 43444 + for (IType subtype : namedSubclassMapping.get(cu)) { + AbstractTypeDeclaration subtypeNode= ASTNodeSearchUtil.getAbstractTypeDeclarationNode(subtype, cuRewrite.getRoot()); + if (subtypeNode != null) + modifyImplicitCallsToNoArgConstructor(subtypeNode, cuRewrite); + } + } + TextChange change= cuRewrite.createChange(true); + if (change != null) + fChangeManager.manage(cu, change); + } + + pm.done(); + return fChangeManager; + } + + private Map> createNamedSubclassMapping(IProgressMonitor pm) throws JavaModelException{ + Map> result= new HashMap<>(); + for (IType subclass : getCachedTypeHierarchy(new SubProgressMonitor(pm, 1)).getSubclasses(fMethod.getDeclaringType())) { + if (subclass.isAnonymous()) + continue; + ICompilationUnit cu= subclass.getCompilationUnit(); + if (! result.containsKey(cu)) + result.put(cu, new HashSet()); + result.get(cu).add(subclass); + } + return result; + } + + private void modifyImplicitCallsToNoArgConstructor(AbstractTypeDeclaration subclass, CompilationUnitRewrite cuRewrite) { + MethodDeclaration[] constructors= getAllConstructors(subclass); + if (constructors.length == 0){ + addNewConstructorToSubclass(subclass, cuRewrite); + } else { + for (MethodDeclaration constructor : constructors) { + if (!containsImplicitCallToSuperConstructor(constructor)) { + continue; + } + addExplicitSuperConstructorCall(constructor, cuRewrite); + } + } + } + + private void addExplicitSuperConstructorCall(MethodDeclaration constructor, CompilationUnitRewrite cuRewrite) { + SuperConstructorInvocation superCall= constructor.getAST().newSuperConstructorInvocation(); + addArgumentsToNewSuperConstructorCall(superCall, cuRewrite); + String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_add_super_call; + TextEditGroup description= cuRewrite.createGroupDescription(msg); + cuRewrite.getASTRewrite().getListRewrite(constructor.getBody(), Block.STATEMENTS_PROPERTY).insertFirst(superCall, description); + } + + private void addArgumentsToNewSuperConstructorCall(SuperConstructorInvocation superCall, CompilationUnitRewrite cuRewrite) { + Iterator iter= getNotDeletedInfos().iterator(); + while (iter.hasNext()) { + ParameterInfo info= iter.next(); + Expression newExpression= createNewExpression(info, getParameterInfos(), superCall.arguments(), cuRewrite, ASTNodes.getParent(superCall, MethodDeclaration.class)); + if (newExpression != null) + superCall.arguments().add(newExpression); + } + } + + private static boolean containsImplicitCallToSuperConstructor(MethodDeclaration constructor) { + Assert.isTrue(constructor.isConstructor()); + Block body= constructor.getBody(); + if (body == null) + return false; + if (body.statements().size() == 0) + return true; + if (body.statements().get(0) instanceof ConstructorInvocation) + return false; + if (body.statements().get(0) instanceof SuperConstructorInvocation) + return false; + return true; + } + + private void addNewConstructorToSubclass(AbstractTypeDeclaration subclass, CompilationUnitRewrite cuRewrite) { + AST ast= subclass.getAST(); + MethodDeclaration newConstructor= ast.newMethodDeclaration(); + newConstructor.setName(ast.newSimpleName(subclass.getName().getIdentifier())); + newConstructor.setConstructor(true); + newConstructor.setJavadoc(null); + newConstructor.modifiers().addAll(ASTNodeFactory.newModifiers(ast, getAccessModifier(subclass))); + newConstructor.setReturnType2(ast.newPrimitiveType(PrimitiveType.VOID)); + Block body= ast.newBlock(); + newConstructor.setBody(body); + SuperConstructorInvocation superCall= ast.newSuperConstructorInvocation(); + addArgumentsToNewSuperConstructorCall(superCall, cuRewrite); + body.statements().add(superCall); + + String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_add_constructor; + TextEditGroup description= cuRewrite.createGroupDescription(msg); + cuRewrite.getASTRewrite().getListRewrite(subclass, subclass.getBodyDeclarationsProperty()).insertFirst(newConstructor, description); + + // TODO use AbstractTypeDeclaration + } + + private static int getAccessModifier(AbstractTypeDeclaration subclass) { + int modifiers= subclass.getModifiers(); + if (Modifier.isPublic(modifiers)) + return Modifier.PUBLIC; + else if (Modifier.isProtected(modifiers)) + return Modifier.PROTECTED; + else if (Modifier.isPrivate(modifiers)) + return Modifier.PRIVATE; + else + return Modifier.NONE; + } + + private MethodDeclaration[] getAllConstructors(AbstractTypeDeclaration typeDeclaration) { + BodyDeclaration decl; + List result= new ArrayList<>(1); + for (Iterator it = typeDeclaration.bodyDeclarations().listIterator(); it.hasNext(); ) { + decl= it.next(); + if (decl instanceof MethodDeclaration && ((MethodDeclaration) decl).isConstructor()) + result.add(decl); + } + return result.toArray(new MethodDeclaration[result.size()]); + } + + private boolean isNoArgConstructor() throws JavaModelException { + return fMethod.isConstructor() && fMethod.getNumberOfParameters() == 0; + } + + private Expression createNewExpression(ParameterInfo info, List parameterInfos, List nodes, CompilationUnitRewrite cuRewrite, MethodDeclaration method) { + if (info.isNewVarargs() && info.getDefaultValue().trim().length() == 0) + return null; + else { + if (fDefaultValueAdvisor == null) + return (Expression) cuRewrite.getASTRewrite().createStringPlaceholder(info.getDefaultValue(), ASTNode.METHOD_INVOCATION); + else + return fDefaultValueAdvisor.createDefaultExpression(nodes, info, parameterInfos, method, false, cuRewrite); + } + } + + private boolean isVisibilitySameAsInitial() throws JavaModelException { + return fVisibility == JdtFlags.getVisibilityCode(fMethod); + } + + private IJavaSearchScope createRefactoringScope() throws JavaModelException{ + return RefactoringScopeFactory.create(fMethod, true, false); + } + + private SearchResultGroup[] findOccurrences(IProgressMonitor pm, ReferencesInBinaryContext binaryRefs, RefactoringStatus status) throws JavaModelException{ + final boolean isConstructor= fMethod.isConstructor(); + CuCollectingSearchRequestor requestor= new CuCollectingSearchRequestor(binaryRefs) { + @Override + protected void acceptSearchMatch(ICompilationUnit unit, SearchMatch match) throws CoreException { + // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=27236 : + if (isConstructor && match instanceof MethodReferenceMatch) { + MethodReferenceMatch mrm= (MethodReferenceMatch) match; + if (mrm.isSynthetic()) { + return; + } + } + collectMatch(match); + } + }; + + SearchPattern pattern; + if (isConstructor) { + +// // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=226151 : don't find binary refs for constructors for now +// return ConstructorReferenceFinder.getConstructorOccurrences(fMethod, pm, status); + +// SearchPattern occPattern= SearchPattern.createPattern(fMethod, IJavaSearchConstants.ALL_OCCURRENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); + SearchPattern declPattern= SearchPattern.createPattern(fMethod, IJavaSearchConstants.DECLARATIONS, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); + if (declPattern == null) { + return new SearchResultGroup[0]; + } + SearchPattern refPattern= SearchPattern.createPattern(fMethod, IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); + if (refPattern == null) { + return new SearchResultGroup[0]; + } +// pattern= SearchPattern.createOrPattern(declPattern, refPattern); +// pattern= occPattern; + + // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=226151 : do two searches + try { + SearchEngine engine= new SearchEngine(); + engine.search(declPattern, SearchUtils.getDefaultSearchParticipants(), createRefactoringScope(), requestor, new NullProgressMonitor()); + engine.search(refPattern, SearchUtils.getDefaultSearchParticipants(), createRefactoringScope(), requestor, pm); + } catch (CoreException e) { + throw new JavaModelException(e); + } + return RefactoringSearchEngine.groupByCu(requestor.getResults(), status); + + } else { + pattern= RefactoringSearchEngine.createOrPattern(fRippleMethods, IJavaSearchConstants.ALL_OCCURRENCES); + } + return RefactoringSearchEngine.search(pattern, createRefactoringScope(), requestor, pm, status); + } + + private static String createDeclarationString(ParameterInfo info) { + String newTypeName= info.getNewTypeName(); + int varargsIndex= newTypeName.lastIndexOf("..."); //$NON-NLS-1$ + if (varargsIndex != -1) { + newTypeName= newTypeName.substring(0, varargsIndex); + } + int index= newTypeName.lastIndexOf('.'); + if (index != -1) { + newTypeName= newTypeName.substring(index+1); + } + return newTypeName + (varargsIndex != -1 ? "..." : "") + " " + info.getNewName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + private static final boolean BUG_89686= true; //see bug 83693: Search for References to methods/constructors: do ranges include parameter lists? + + private OccurrenceUpdate createOccurrenceUpdate(ASTNode node, CompilationUnitRewrite cuRewrite, RefactoringStatus result) { + if (BUG_89686 && node instanceof SimpleName && node.getParent() instanceof EnumConstantDeclaration) + node= node.getParent(); + + if (Invocations.isInvocationWithArguments(node)) + return new ReferenceUpdate(node, cuRewrite, result); + + else if (node instanceof SimpleName && node.getParent() instanceof MethodDeclaration) + return new DeclarationUpdate((MethodDeclaration) node.getParent(), cuRewrite, result); + + else if (node instanceof MemberRef || node instanceof MethodRef) + return new DocReferenceUpdate(node, cuRewrite, result); + + else if (ASTNodes.getParent(node, ImportDeclaration.class) != null) + return new StaticImportUpdate(ASTNodes.getParent(node, ImportDeclaration.class), cuRewrite, result); + + else if (node instanceof LambdaExpression) + return new LambdaExpressionUpdate((LambdaExpression) node, cuRewrite, result); + + else if (node.getLocationInParent() == ExpressionMethodReference.NAME_PROPERTY) + return new ExpressionMethodRefUpdate((ExpressionMethodReference) node.getParent(), cuRewrite, result); + + else + return new NullOccurrenceUpdate(node, cuRewrite, result); + } + + abstract class OccurrenceUpdate { + protected final CompilationUnitRewrite fCuRewrite; + protected final TextEditGroup fDescription; + protected RefactoringStatus fResult; + + protected OccurrenceUpdate(CompilationUnitRewrite cuRewrite, TextEditGroup description, RefactoringStatus result) { + fCuRewrite= cuRewrite; + fDescription= description; + fResult= result; + } + + protected final ASTRewrite getASTRewrite() { + return fCuRewrite.getASTRewrite(); + } + + protected final ImportRewrite getImportRewrite() { + return fCuRewrite.getImportRewrite(); + } + + protected final ImportRemover getImportRemover() { + return fCuRewrite.getImportRemover(); + } + + protected final CompilationUnitRewrite getCompilationUnitRewrite() { + return fCuRewrite; + } + + protected int getStartPosition() { + return getMethodNameNode().getStartPosition(); + } + + public abstract void updateNode() throws CoreException; + + protected void registerImportRemoveNode(ASTNode node) { + getImportRemover().registerRemovedNode(node); + } + + protected final void reshuffleElements() { + if (isOrderSameAsInitial()) + return; + + //varargs; method(p1, p2, .., pn), call(a1, a2, .., ax) : + // if (method_was_vararg) { + // assert fOldVarargIndex != -1 + // if (vararg_retained) { + // assert vararg_is_last_non_deleted (pn) + // assert no_other_varargs + // => reshuffle [1..n-1] then append remaining nodes [n..x], possibly none + // + // } else (vararg_deleted) { + // assert all_are_non_vararg + // => reshuffle [1..n-1], drop all remaining nodes [n..x], possibly none + // } + // + // } else if (method_became_vararg) { + // assert n == x + // assert fOldVarargIndex == -1 + // => reshuffle [1..n] + // + // } else (JLS2_case) { + // assert n == x + // assert fOldVarargIndex == -1 + // => reshuffle [1..n] + // } + + ListRewrite listRewrite= getParamgumentsRewrite(); + Map newOldMap= new LinkedHashMap<>(); + List nodes= listRewrite.getRewrittenList(); + Iterator rewriteIter= nodes.iterator(); + List original= listRewrite.getOriginalList(); + for (N n : original) { + newOldMap.put(rewriteIter.next(),n); + } + List newNodes= new ArrayList<>(); + // register removed nodes, and collect nodes in new sequence: + for (ParameterInfo info : fParameterInfos) { + int oldIndex= info.getOldIndex(); + + if (info.isDeleted()) { + if (oldIndex != fOldVarargIndex) { + registerImportRemoveNode(nodes.get(oldIndex)); + } else { + //vararg deleted -> remove all remaining nodes: + for (int n= oldIndex; n < nodes.size(); n++) { + registerImportRemoveNode(nodes.get(n)); + } + } + + } else if (info.isAdded()) { + N newParamgument= createNewParamgument(info, fParameterInfos, nodes); + if (newParamgument != null) + newNodes.add(newParamgument); + + } else /* parameter stays */ { + if (oldIndex != fOldVarargIndex) { + N oldNode= nodes.get(oldIndex); + N movedNode= moveNode(oldNode, getASTRewrite()); + newNodes.add(movedNode); + } else { + //vararg stays and is last parameter -> copy all remaining nodes: + for (int n= oldIndex; n < nodes.size(); n++) { + N oldNode= nodes.get(n); + N movedNode= moveNode(oldNode, getASTRewrite()); + newNodes.add(movedNode); + } + } + } + } + + Iterator nodesIter= nodes.iterator(); + Iterator newIter= newNodes.iterator(); + //replace existing nodes with new ones: + while (nodesIter.hasNext() && newIter.hasNext()) { + ASTNode node= nodesIter.next(); + ASTNode newNode= newIter.next(); + if (!ASTNodes.isExistingNode(node)) //XXX:should better be addressed in ListRewriteEvent.replaceEntry(ASTNode, ASTNode) + listRewrite.replace(newOldMap.get(node), newNode, fDescription); + else + listRewrite.replace(node, newNode, fDescription); + } + //remove remaining existing nodes: + while (nodesIter.hasNext()) { + ASTNode node= nodesIter.next(); + if (!ASTNodes.isExistingNode(node)) + listRewrite.remove(newOldMap.get(node), fDescription); + else + listRewrite.remove(node, fDescription); + } + //add additional new nodes: + while (newIter.hasNext()) { + ASTNode node= newIter.next(); + listRewrite.insertLast(node, fDescription); + } + } + + /** + * @return ListRewrite of parameters or arguments + */ + protected abstract ListRewrite getParamgumentsRewrite(); + + protected final void changeParamguments() { + for (ParameterInfo info : getParameterInfos()) { + if (info.isAdded() || info.isDeleted()) + continue; + + if (info.isRenamed()) + changeParamgumentName(info); + + if (info.isTypeNameChanged()) + changeParamgumentType(info); + } + } + + /** + * @param info the parameter info + */ + protected void changeParamgumentName(ParameterInfo info) { + // no-op + } + + /** + * @param info the parameter info + */ + protected void changeParamgumentType(ParameterInfo info) { + // no-op + } + + protected final void replaceTypeNode(Type typeNode, String newTypeName, ITypeBinding newTypeBinding){ + Type newTypeNode= createNewTypeNode(newTypeName, newTypeBinding); + getASTRewrite().replace(typeNode, newTypeNode, fDescription); + registerImportRemoveNode(typeNode); + getTightSourceRangeComputer().addTightSourceNode(typeNode); + } + + /** + * @param info TODO + * @param parameterInfos TODO + * @param nodes TODO + * @return a new method parameter or argument, or null for an empty vararg argument + */ + protected abstract N createNewParamgument(ParameterInfo info, List parameterInfos, List nodes); + + protected abstract SimpleName getMethodNameNode(); + + protected final void changeMethodName() { + if (! isMethodNameSameAsInitial()) { + SimpleName nameNode= getMethodNameNode(); + if (nameNode != null) { + SimpleName newNameNode= nameNode.getAST().newSimpleName(fMethodName); + getASTRewrite().replace(nameNode, newNameNode, fDescription); + registerImportRemoveNode(nameNode); + getTightSourceRangeComputer().addTightSourceNode(nameNode); + } + } + } + + protected final Type createNewTypeNode(String newTypeName, ITypeBinding newTypeBinding) { + Type newTypeNode; + if (newTypeBinding == null) { + if (fDefaultValueAdvisor != null) + newTypeNode= fDefaultValueAdvisor.createType(newTypeName, getStartPosition(), getCompilationUnitRewrite()); + else + newTypeNode= (Type) getASTRewrite().createStringPlaceholder(newTypeName, ASTNode.SIMPLE_TYPE); + //Don't import if not resolved. + } else { + ImportRewriteContext importRewriteContext= new ContextSensitiveImportRewriteContext(fCuRewrite.getRoot(), getStartPosition(), getImportRewrite()); + newTypeNode= getImportRewrite().addImport(newTypeBinding, fCuRewrite.getAST(), importRewriteContext); + getImportRemover().registerAddedImports(newTypeNode); + } + return newTypeNode; + } + + protected final TightSourceRangeComputer getTightSourceRangeComputer() { + return (TightSourceRangeComputer) fCuRewrite.getASTRewrite().getExtendedSourceRangeComputer(); + } + } + + class ReferenceUpdate extends OccurrenceUpdate { + /** isReferenceNode(fNode) */ + private ASTNode fNode; + + protected ReferenceUpdate(ASTNode node, CompilationUnitRewrite cuRewrite, RefactoringStatus result) { + super(cuRewrite, cuRewrite.createGroupDescription(RefactoringCoreMessages.ChangeSignatureRefactoring_update_reference), result); + fNode= node; //holds: Assert.isTrue(isReferenceNode(node)); + } + + @Override + public void updateNode() { + reshuffleElements(); + changeMethodName(); + } + + /** @return {@inheritDoc} (element type: Expression) */ + @Override + protected ListRewrite getParamgumentsRewrite() { + return getASTRewrite().getListRewrite(fNode, Invocations.getArgumentsProperty(fNode)); + } + + @Override + protected Expression createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) { + CompilationUnitRewrite cuRewrite= getCompilationUnitRewrite(); + MethodDeclaration declaration= ASTNodes.getParent(fNode, MethodDeclaration.class); + if (isRecursiveReference()) { + return createNewExpressionRecursive(info, parameterInfos, nodes, cuRewrite, declaration); + } else + return createNewExpression(info, parameterInfos, nodes, cuRewrite, declaration); + } + + private Expression createNewExpressionRecursive(ParameterInfo info, List parameterInfos, List nodes, CompilationUnitRewrite cuRewrite, MethodDeclaration methodDeclaration) { + if (fDefaultValueAdvisor != null && info.isAdded()) { + return fDefaultValueAdvisor.createDefaultExpression(nodes, info, parameterInfos, methodDeclaration, true, cuRewrite); + } + return (Expression) getASTRewrite().createStringPlaceholder(info.getNewName(), ASTNode.METHOD_INVOCATION); + } + + @Override + protected SimpleName getMethodNameNode() { + if (fNode instanceof MethodInvocation) + return ((MethodInvocation)fNode).getName(); + + if (fNode instanceof SuperMethodInvocation) + return ((SuperMethodInvocation)fNode).getName(); + + return null; + } + + private boolean isRecursiveReference() { + MethodDeclaration enclosingMethodDeclaration= ASTNodes.getParent(fNode, MethodDeclaration.class); + if (enclosingMethodDeclaration == null) + return false; + + IMethodBinding enclosingMethodBinding= enclosingMethodDeclaration.resolveBinding(); + if (enclosingMethodBinding == null) + return false; + + if (fNode instanceof MethodInvocation) + return enclosingMethodBinding == ((MethodInvocation)fNode).resolveMethodBinding(); + + if (fNode instanceof SuperMethodInvocation) { + IMethodBinding methodBinding= ((SuperMethodInvocation)fNode).resolveMethodBinding(); + return isSameMethod(methodBinding, enclosingMethodBinding); + } + + if (fNode instanceof ClassInstanceCreation) + return enclosingMethodBinding == ((ClassInstanceCreation)fNode).resolveConstructorBinding(); + + if (fNode instanceof ConstructorInvocation) + return enclosingMethodBinding == ((ConstructorInvocation)fNode).resolveConstructorBinding(); + + if (fNode instanceof SuperConstructorInvocation) { + return false; //Constructors don't override -> enclosing has not been changed -> no recursion + } + + if (fNode instanceof EnumConstantDeclaration) { + return false; //cannot define enum constant inside enum constructor + } + + Assert.isTrue(false); + return false; + } + + /** + * @param m1 method 1 + * @param m2 method 2 + * @return true iff + *
  • the methods are both constructors with same argument types, or
  • + *
  • the methods have the same name and the same argument types
+ */ + private boolean isSameMethod(IMethodBinding m1, IMethodBinding m2) { + if (m1.isConstructor()) { + if (! m2.isConstructor()) + return false; + } else { + if (! m1.getName().equals(m2.getName())) + return false; + } + + ITypeBinding[] m1Parameters= m1.getParameterTypes(); + ITypeBinding[] m2Parameters= m2.getParameterTypes(); + if (m1Parameters.length != m2Parameters.length) + return false; + for (int i= 0; i < m1Parameters.length; i++) { + if (m1Parameters[i].getErasure() != m2Parameters[i].getErasure()) + return false; + } + return true; + } + + } + + + class ExpressionMethodRefUpdate extends OccurrenceUpdate { + private ExpressionMethodReference fMethodRef; + + protected ExpressionMethodRefUpdate(ExpressionMethodReference decl, CompilationUnitRewrite cuRewrite, RefactoringStatus result) { + super(cuRewrite, cuRewrite.createGroupDescription(RefactoringCoreMessages.ChangeSignatureRefactoring_change_signature), result); + fMethodRef= decl; + } + + @Override + public void updateNode() throws CoreException { + if (canChangeNameAndReturnType()) + changeMethodName(); + } + + @Override + protected SimpleName getMethodNameNode() { + return fMethodRef.getName(); + } + + @Override + protected ListRewrite getParamgumentsRewrite() { + return null; + } + + @Override + protected VariableDeclaration createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) { + return null; + } + + } + + /** + * Abstraction for handling MethodDeclaration and LambdaExpression updates. + * @param type of the parameter nodes + */ + abstract class AbstractDeclarationUpdate extends OccurrenceUpdate { + + protected AbstractDeclarationUpdate(CompilationUnitRewrite cuRewrite, TextEditGroup description, RefactoringStatus result) { + super(cuRewrite, description, result); + } + + protected abstract ASTNode getNode(); + + protected abstract VariableDeclaration getParameter(int index); + + @Override + protected void changeParamgumentName(ParameterInfo info) { + VariableDeclaration param= getParameter(info.getOldIndex()); + if (!info.getOldName().equals(param.getName().getIdentifier())) + return; //don't change if original parameter name != name in rippleMethod + + String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_update_parameter_references; + TextEditGroup description= fCuRewrite.createGroupDescription(msg); + TempOccurrenceAnalyzer analyzer= new TempOccurrenceAnalyzer(param, false); + analyzer.perform(); + // @param tags are updated in changeJavaDocTags() + for (SimpleName occurrence : analyzer.getReferenceAndDeclarationNodes()) { + getASTRewrite().set(occurrence, SimpleName.IDENTIFIER_PROPERTY, info.getNewName(), description); + } + } + + @Override + protected void changeParamgumentType(ParameterInfo info) { + VariableDeclaration oldParam= getParameter(info.getOldIndex()); + if (oldParam instanceof SingleVariableDeclaration) { + getASTRewrite().set(oldParam, SingleVariableDeclaration.VARARGS_PROPERTY, Boolean.valueOf(info.isNewVarargs()), fDescription); + SingleVariableDeclaration oldSVDParam= (SingleVariableDeclaration) oldParam; + replaceTypeNode(oldSVDParam.getType(), ParameterInfo.stripEllipsis(info.getNewTypeName()), info.getNewTypeBinding()); + removeExtraDimensions(oldSVDParam); + } + } + + private void removeExtraDimensions(SingleVariableDeclaration oldParam) { + ListRewrite listRewrite= getASTRewrite().getListRewrite(oldParam, SingleVariableDeclaration.EXTRA_DIMENSIONS2_PROPERTY); + for (Dimension dimension : (List) oldParam.extraDimensions()) { + listRewrite.remove(dimension, fDescription); + } + } + + //TODO: already reported as compilation error -> don't report there? + protected void checkIfDeletedParametersUsed() { + for (ParameterInfo info : getDeletedInfos()) { + VariableDeclaration paramDecl= getParameter(info.getOldIndex()); + TempOccurrenceAnalyzer analyzer= new TempOccurrenceAnalyzer(paramDecl, false); + analyzer.perform(); + SimpleName[] paramRefs= analyzer.getReferenceNodes(); + + if (paramRefs.length > 0) { + RefactoringStatusContext context= JavaStatusContext.create(fCuRewrite.getCu(), paramRefs[0]); + String typeName= getFullTypeName(); + Object[] keys= new String[] { BasicElementLabels.getJavaElementName(paramDecl.getName().getIdentifier()), + BasicElementLabels.getJavaElementName(getMethod().getElementName()), + BasicElementLabels.getJavaElementName(typeName) }; + String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_parameter_used, keys); + fResult.addError(msg, context); + } + } + } + + protected String getFullTypeName() { + ASTNode node= getNode(); + while (true) { + node= node.getParent(); + if (node instanceof AbstractTypeDeclaration) { + String typeName= ((AbstractTypeDeclaration) node).getName().getIdentifier(); + if (getNode() instanceof LambdaExpression) { + return Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_lambda_expression, typeName); + } + return typeName; + } else if (node instanceof ClassInstanceCreation) { + ClassInstanceCreation cic= (ClassInstanceCreation) node; + return Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_anonymous_subclass, BasicElementLabels.getJavaElementName(ASTNodes.asString(cic.getType()))); + } else if (node instanceof EnumConstantDeclaration) { + EnumDeclaration ed= (EnumDeclaration) node.getParent(); + return Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_anonymous_subclass, BasicElementLabels.getJavaElementName(ASTNodes.asString(ed.getName()))); + } + } + } + + protected SingleVariableDeclaration createNewSingleVariableDeclaration(ParameterInfo info) { + SingleVariableDeclaration newP= getASTRewrite().getAST().newSingleVariableDeclaration(); + newP.setName(getASTRewrite().getAST().newSimpleName(info.getNewName())); + newP.setType(createNewTypeNode(ParameterInfo.stripEllipsis(info.getNewTypeName()), info.getNewTypeBinding())); + newP.setVarargs(info.isNewVarargs()); + return newP; + } + } + + class LambdaExpressionUpdate extends AbstractDeclarationUpdate { + private LambdaExpression fLambdaDecl; + + protected LambdaExpressionUpdate(LambdaExpression decl, CompilationUnitRewrite cuRewrite, RefactoringStatus result) { + super(cuRewrite, cuRewrite.createGroupDescription(RefactoringCoreMessages.ChangeSignatureRefactoring_change_signature), result); + fLambdaDecl= decl; + } + + @Override + public void updateNode() throws CoreException { + changeParamguments(); + + reshuffleElements(); + + if (fBodyUpdater == null || fBodyUpdater.needsParameterUsedCheck()) + checkIfDeletedParametersUsed(); + } + + /** @return {@inheritDoc} (element type: VariableDeclaration) */ + @Override + protected ListRewrite getParamgumentsRewrite() { + return getASTRewrite().getListRewrite(fLambdaDecl, LambdaExpression.PARAMETERS_PROPERTY); + } + + @Override + protected VariableDeclaration createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) { + List parameters= fLambdaDecl.parameters(); + if (!parameters.isEmpty() && parameters.get(0) instanceof SingleVariableDeclaration) { + return createNewSingleVariableDeclaration(info); + } + VariableDeclarationFragment newP= getASTRewrite().getAST().newVariableDeclarationFragment(); + newP.setName(getASTRewrite().getAST().newSimpleName(info.getNewName())); + return newP; + } + + @Override + protected int getStartPosition() { + return fLambdaDecl.getStartPosition(); + } + + @Override + protected ASTNode getNode() { + return fLambdaDecl; + } + + @Override + protected VariableDeclaration getParameter(int index) { + return (VariableDeclaration) fLambdaDecl.parameters().get(index); + } + + @Override + protected SimpleName getMethodNameNode() { + return null; + } + } + + class DeclarationUpdate extends AbstractDeclarationUpdate { + private MethodDeclaration fMethDecl; + + protected DeclarationUpdate(MethodDeclaration decl, CompilationUnitRewrite cuRewrite, RefactoringStatus result) { + super(cuRewrite, cuRewrite.createGroupDescription(RefactoringCoreMessages.ChangeSignatureRefactoring_change_signature), result); + fMethDecl= decl; + } + + // Prevent import removing if delegate is created. + @Override + protected void registerImportRemoveNode(ASTNode node) { + if (!fDelegateUpdating) + super.registerImportRemoveNode(node); + } + + @Override + public void updateNode() throws CoreException { + changeParamguments(); + + if (canChangeNameAndReturnType()) { + changeMethodName(); + changeReturnType(); + } + + if (needsVisibilityUpdate()) + changeVisibility(); + reshuffleElements(); + changeExceptions(); + + changeJavadocTags(); + + if (fBodyUpdater == null || fBodyUpdater.needsParameterUsedCheck()) + checkIfDeletedParametersUsed(); + + if (fBodyUpdater != null) + fBodyUpdater.updateBody(fMethDecl, fCuRewrite, fResult); + + if (fDelegateUpdating) + addDelegate(); + } + + private void addDelegate() throws JavaModelException { + + DelegateMethodCreator creator= new DelegateMethodCreator(); + creator.setDeclaration(fMethDecl); + creator.setDeclareDeprecated(fDelegateDeprecation); + creator.setSourceRewrite(fCuRewrite); + creator.prepareDelegate(); + + /* + * The delegate now contains a call and a javadoc reference to the + * old method (i.e., to itself). + * + * Use ReferenceUpdate() / DocReferenceUpdate() to update these + * references like any other reference. + */ + final ASTNode delegateInvocation= creator.getDelegateInvocation(); + if (delegateInvocation != null) + // may be null if the delegate is an interface method or + // abstract -> no body + new ReferenceUpdate(delegateInvocation, creator.getDelegateRewrite(), fResult).updateNode(); + MethodRef javadocReference= creator.getJavadocReference(); + if (javadocReference != null) + new DocReferenceUpdate(javadocReference, creator.getDelegateRewrite(), fResult).updateNode(); + + creator.createEdit(); + } + + /** @return {@inheritDoc} (element type: SingleVariableDeclaration) */ + @Override + protected ListRewrite getParamgumentsRewrite() { + return getASTRewrite().getListRewrite(fMethDecl, MethodDeclaration.PARAMETERS_PROPERTY); + } + + private void changeReturnType() { + if (isReturnTypeSameAsInitial()) + return; + replaceTypeNode(fMethDecl.getReturnType2(), fReturnTypeInfo.getNewTypeName(), fReturnTypeInfo.getNewTypeBinding()); + removeExtraDimensions(fMethDecl); + //Remove expression from return statement when changed to void? No, would lose information! + //Could add return statement with default value and add todo comment, but compile error is better. + } + + private void removeExtraDimensions(MethodDeclaration methDecl) { + ListRewrite listRewrite= getASTRewrite().getListRewrite(methDecl, MethodDeclaration.EXTRA_DIMENSIONS2_PROPERTY); + for (Dimension dimension : (List) methDecl.extraDimensions()) { + listRewrite.remove(dimension, fDescription); + } + } + + private boolean needsVisibilityUpdate() throws JavaModelException { + if (isVisibilitySameAsInitial()) + return false; + if (isIncreasingVisibility()) + return JdtFlags.isHigherVisibility(fVisibility, JdtFlags.getVisibilityCode(fMethDecl)); + else + return JdtFlags.isHigherVisibility(JdtFlags.getVisibilityCode(fMethDecl), fVisibility); + } + + private boolean isIncreasingVisibility() throws JavaModelException{ + return JdtFlags.isHigherVisibility(fVisibility, JdtFlags.getVisibilityCode(fMethod)); + } + + private void changeVisibility() { + ModifierRewrite.create(getASTRewrite(), fMethDecl).setVisibility(fVisibility, fDescription); + } + + private void changeExceptions() { + for (ExceptionInfo info : fExceptionInfos) { + if (info.isOld()) + continue; + if (info.isDeleted()) + removeExceptionFromNodeList(info, fMethDecl.thrownExceptionTypes()); + else + addExceptionToNodeList(info, getASTRewrite().getListRewrite(fMethDecl, MethodDeclaration.THROWN_EXCEPTION_TYPES_PROPERTY)); + } + } + + private void removeExceptionFromNodeList(ExceptionInfo toRemove, List list) { + ITypeBinding typeToRemove= toRemove.getTypeBinding(); + for (Type currentExcType : list) { + ITypeBinding currentType= currentExcType.resolveBinding(); + /* Maybe remove all subclasses of typeToRemove too. + * Problem: + * - B extends A; + * - A.m() throws IOException, Exception; + * - B.m() throws IOException, AWTException; + * Removing Exception should remove AWTException, + * but NOT remove IOException (or a subclass of JavaModelException). */ + // if (Bindings.isSuperType(typeToRemove, currentType)) + if (currentType == null) + continue; // newly added or unresolvable type + if (Bindings.equals(currentType, typeToRemove) || toRemove.getElement().getElementName().equals(currentType.getName())) { + getASTRewrite().remove(currentExcType, fDescription); + registerImportRemoveNode(currentExcType); + } + } + } + + private void addExceptionToNodeList(ExceptionInfo exceptionInfo, ListRewrite exceptionListRewrite) { + String fullyQualified= exceptionInfo.getFullyQualifiedName(); + for (Iterator iter= exceptionListRewrite.getOriginalList().iterator(); iter.hasNext(); ) { + Type exType= (Type) iter.next(); + //XXX: existing superclasses of the added exception are redundant and could be removed + ITypeBinding typeBinding= exType.resolveBinding(); + if (typeBinding == null) + continue; // newly added or unresolvable type + if (typeBinding.getQualifiedName().equals(fullyQualified)) + return; // don't add it again + } + String importedType= getImportRewrite().addImport(exceptionInfo.getFullyQualifiedName()); + getImportRemover().registerAddedImport(importedType); + ASTNode exNode= getASTRewrite().createStringPlaceholder(importedType, ASTNode.SIMPLE_TYPE); + exceptionListRewrite.insertLast(exNode, fDescription); + } + + private void changeJavadocTags() throws JavaModelException { + //update tags in javadoc: @param, @return, @exception, @throws, ... + Javadoc javadoc= fMethDecl.getJavadoc(); + if (javadoc == null) + return; + + ITypeBinding typeBinding= Bindings.getBindingOfParentType(fMethDecl); + if (typeBinding == null) + return; + IMethodBinding methodBinding= fMethDecl.resolveBinding(); + if (methodBinding == null) + return; + + boolean isTopOfRipple= (Bindings.findOverriddenMethod(methodBinding, false) == null); + //add tags: only iff top of ripple; change and remove: always. + //TODO: should have preference for adding tags in (overriding) methods (with template: todo, inheritDoc, ...) + + List tags= javadoc.tags(); + ListRewrite tagsRewrite= getASTRewrite().getListRewrite(javadoc, Javadoc.TAGS_PROPERTY); + + if (! isReturnTypeSameAsInitial()) { + if (PrimitiveType.VOID.toString().equals(fReturnTypeInfo.getNewTypeName())) { + for (TagElement tag : tags) { + if (TagElement.TAG_RETURN.equals(tag.getTagName())) { + getASTRewrite().remove(tag, fDescription); + registerImportRemoveNode(tag); + } + } + } else if (isTopOfRipple && Signature.SIG_VOID.equals(fMethod.getReturnType())){ + TagElement returnNode= createReturnTag(); + TagElement previousTag= findTagElementToInsertAfter(tags, TagElement.TAG_RETURN); + insertTag(returnNode, previousTag, tagsRewrite); + tags= tagsRewrite.getRewrittenList(); + } + } + + if (!areNamesSameAsInitial() || !isOrderSameAsInitial()) { + ArrayList paramTags= new ArrayList<>(); // , only not deleted tags with simpleName + // delete & rename: + for (TagElement tag : tags) { + String tagName= tag.getTagName(); + List fragments= tag.fragments(); + if (!TagElement.TAG_PARAM.equals(tagName) + || fragments.isEmpty() + || !(fragments.get(0) instanceof SimpleName)) + continue; + SimpleName simpleName= (SimpleName) fragments.get(0); + String identifier= simpleName.getIdentifier(); + boolean removed= false; + for (ParameterInfo info : fParameterInfos) { + if (identifier.equals(info.getOldName())) { + if (info.isDeleted()) { + getASTRewrite().remove(tag, fDescription); + registerImportRemoveNode(tag); + removed= true; + } else if (info.isRenamed()) { + SimpleName newName= simpleName.getAST().newSimpleName(info.getNewName()); + getASTRewrite().replace(simpleName, newName, fDescription); + registerImportRemoveNode(tag); + } + break; + } + } + if (! removed) + paramTags.add(tag); + } + tags= tagsRewrite.getRewrittenList(); + + if (! isOrderSameAsInitial()) { + // reshuffle (sort in declaration sequence) & add (only add to top of ripple): + TagElement previousTag= findTagElementToInsertAfter(tags, TagElement.TAG_PARAM); + boolean first= true; // workaround for bug 92111: preserve first tag if possible + // reshuffle: + for (ParameterInfo info : fParameterInfos) { + String oldName= info.getOldName(); + String newName= info.getNewName(); + if (info.isAdded()) { + first= false; + if (! isTopOfRipple) + continue; + TagElement paramNode= JavadocUtil.createParamTag(newName, fCuRewrite.getRoot().getAST(), fCuRewrite.getCu().getJavaProject()); + insertTag(paramNode, previousTag, tagsRewrite); + previousTag= paramNode; + } else { + for (Iterator tagIter= paramTags.iterator(); tagIter.hasNext();) { + TagElement tag= tagIter.next(); + SimpleName tagName= (SimpleName) tag.fragments().get(0); + if (oldName.equals(tagName.getIdentifier())) { + tagIter.remove(); + if (first) { + previousTag= tag; + } else { + TagElement movedTag= (TagElement) getASTRewrite().createMoveTarget(tag); + getASTRewrite().remove(tag, fDescription); + insertTag(movedTag, previousTag, tagsRewrite); + previousTag= movedTag; + } + } + first= false; + } + } + } + // params with bad names: + for (TagElement tag : paramTags) { + TagElement movedTag= (TagElement) getASTRewrite().createMoveTarget(tag); + getASTRewrite().remove(tag, fDescription); + insertTag(movedTag, previousTag, tagsRewrite); + previousTag= movedTag; + } + } + tags= tagsRewrite.getRewrittenList(); + } + + if (! areExceptionsSameAsInitial()) { + // collect exceptionTags and remove deleted: + ArrayList exceptionTags= new ArrayList<>(); // , only not deleted tags with name + for (TagElement tag : tags) { + if (! TagElement.TAG_THROWS.equals(tag.getTagName()) && ! TagElement.TAG_EXCEPTION.equals(tag.getTagName())) + continue; + if (tag.fragments().isEmpty() || !(tag.fragments().get(0) instanceof Name)) + continue; + boolean tagDeleted= false; + Name name= (Name) tag.fragments().get(0); + for (ExceptionInfo info : fExceptionInfos) { + if (info.isDeleted()) { + boolean remove= false; + final ITypeBinding nameBinding= name.resolveTypeBinding(); + if (nameBinding != null) { + final ITypeBinding infoBinding= info.getTypeBinding(); + if ((infoBinding != null && Bindings.equals(infoBinding, nameBinding)) + || info.getElement().getElementName().equals(nameBinding.getName())) { + remove= true; + } + if (remove) { + getASTRewrite().remove(tag, fDescription); + registerImportRemoveNode(tag); + tagDeleted= true; + break; + } + } + } + } + if (! tagDeleted) + exceptionTags.add(tag); + } + // reshuffle: + tags= tagsRewrite.getRewrittenList(); + TagElement previousTag= findTagElementToInsertAfter(tags, TagElement.TAG_THROWS); + for (ExceptionInfo info : fExceptionInfos) { + if (info.isAdded()) { + if (!isTopOfRipple) + continue; + TagElement excptNode= createExceptionTag(info.getElement().getElementName()); + insertTag(excptNode, previousTag, tagsRewrite); + previousTag= excptNode; + } else { + for (Iterator tagIter= exceptionTags.iterator(); tagIter.hasNext();) { + TagElement tag= tagIter.next(); + Name tagName= (Name) tag.fragments().get(0); + final ITypeBinding nameBinding= tagName.resolveTypeBinding(); + if (nameBinding != null) { + boolean process= false; + final ITypeBinding infoBinding= info.getTypeBinding(); + if ((infoBinding != null && Bindings.equals(infoBinding, nameBinding)) + || info.getElement().getElementName().equals(nameBinding.getName())) { + process= true; + } + if (process) { + tagIter.remove(); + TagElement movedTag= (TagElement) getASTRewrite().createMoveTarget(tag); + getASTRewrite().remove(tag, fDescription); + insertTag(movedTag, previousTag, tagsRewrite); + previousTag= movedTag; + } + } + } + } + } + // exceptions with bad names: + for (TagElement tag : exceptionTags) { + TagElement movedTag= (TagElement) getASTRewrite().createMoveTarget(tag); + getASTRewrite().remove(tag, fDescription); + insertTag(movedTag, previousTag, tagsRewrite); + previousTag= movedTag; + } + } + } + + private TagElement createReturnTag() { + TagElement returnNode= getASTRewrite().getAST().newTagElement(); + returnNode.setTagName(TagElement.TAG_RETURN); + + TextElement textElement= getASTRewrite().getAST().newTextElement(); + String text= StubUtility.getTodoTaskTag(fCuRewrite.getCu().getJavaProject()); + if (text != null) + textElement.setText(text); //TODO: use template with {@todo} ... + returnNode.fragments().add(textElement); + + return returnNode; + } + + private TagElement createExceptionTag(String simpleName) { + TagElement excptNode= getASTRewrite().getAST().newTagElement(); + excptNode.setTagName(TagElement.TAG_THROWS); + + SimpleName nameNode= getASTRewrite().getAST().newSimpleName(simpleName); + excptNode.fragments().add(nameNode); + + TextElement textElement= getASTRewrite().getAST().newTextElement(); + String text= StubUtility.getTodoTaskTag(fCuRewrite.getCu().getJavaProject()); + if (text != null) + textElement.setText(text); //TODO: use template with {@todo} ... + excptNode.fragments().add(textElement); + + return excptNode; + } + + private void insertTag(TagElement tag, TagElement previousTag, ListRewrite tagsRewrite) { + if (previousTag == null) + tagsRewrite.insertFirst(tag, fDescription); + else + tagsRewrite.insertAfter(tag, previousTag, fDescription); + } + + /** + * @param tags existing tags + * @param tagName name of tag to add + * @return the TagElement just before a new TagElement with name + * tagName, or null. + */ + private TagElement findTagElementToInsertAfter(List tags, String tagName) { + List tagOrder= Arrays.asList( + TagElement.TAG_AUTHOR, + TagElement.TAG_VERSION, + TagElement.TAG_PARAM, + TagElement.TAG_RETURN, + TagElement.TAG_THROWS, + TagElement.TAG_EXCEPTION, + TagElement.TAG_SEE, + TagElement.TAG_SINCE, + TagElement.TAG_SERIAL, + TagElement.TAG_SERIALFIELD, + TagElement.TAG_SERIALDATA, + TagElement.TAG_DEPRECATED, + TagElement.TAG_VALUE + ); + int goalOrdinal= tagOrder.indexOf(tagName); + if (goalOrdinal == -1) // unknown tag -> to end + return (tags.isEmpty()) ? null : (TagElement) tags.get(tags.size()); + for (int i= 0; i < tags.size(); i++) { + int tagOrdinal= tagOrder.indexOf(tags.get(i).getTagName()); + if (tagOrdinal >= goalOrdinal) + return (i == 0) ? null : (TagElement) tags.get(i - 1); + } + return (tags.isEmpty()) ? null : (TagElement) tags.get(tags.size() - 1); + } + + + @Override + protected SingleVariableDeclaration createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) { + return createNewSingleVariableDeclaration(info); + } + + @Override + protected ASTNode getNode() { + return fMethDecl; + } + + @Override + protected VariableDeclaration getParameter(int index) { + return (VariableDeclaration) fMethDecl.parameters().get(index); + } + + @Override + protected SimpleName getMethodNameNode() { + return fMethDecl.getName(); + } + + } + + class DocReferenceUpdate extends OccurrenceUpdate { + /** instanceof MemberRef || MethodRef */ + private ASTNode fNode; + + protected DocReferenceUpdate(ASTNode node, CompilationUnitRewrite cuRewrite, RefactoringStatus result) { + super(cuRewrite, cuRewrite.createGroupDescription(RefactoringCoreMessages.ChangeSignatureRefactoring_update_javadoc_reference), result); + fNode= node; + } + + @Override + public void updateNode() { + if (fNode instanceof MethodRef) { + changeParamguments(); + reshuffleElements(); + } + if (canChangeNameAndReturnType()) + changeMethodName(); + } + + @Override + protected MethodRefParameter createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) { + return createNewMethodRefParameter(info); + } + + private MethodRefParameter createNewMethodRefParameter(ParameterInfo info) { + MethodRefParameter newP= getASTRewrite().getAST().newMethodRefParameter(); + + // only add name iff first parameter already has a name: + List parameters= getParamgumentsRewrite().getOriginalList(); + if (parameters.size() > 0) + if (((MethodRefParameter) parameters.get(0)).getName() != null) + newP.setName(getASTRewrite().getAST().newSimpleName(info.getNewName())); + + newP.setType(createNewDocRefType(info)); + newP.setVarargs(info.isNewVarargs()); + return newP; + } + + private Type createNewDocRefType(ParameterInfo info) { + String newTypeName= ParameterInfo.stripEllipsis(info.getNewTypeName()); + ITypeBinding newTypeBinding= info.getNewTypeBinding(); + if (newTypeBinding != null) + newTypeBinding= newTypeBinding.getErasure(); //see bug 83127: Javadoc references are raw (erasures) + return createNewTypeNode(newTypeName, newTypeBinding); + } + + @Override + protected SimpleName getMethodNameNode() { + if (fNode instanceof MemberRef) + return ((MemberRef) fNode).getName(); + + if (fNode instanceof MethodRef) + return ((MethodRef) fNode).getName(); + + return null; + } + + /** @return {@inheritDoc} (element type: MethodRefParameter) */ + @Override + protected ListRewrite getParamgumentsRewrite() { + return getASTRewrite().getListRewrite(fNode, MethodRef.PARAMETERS_PROPERTY); + } + + @Override + protected void changeParamgumentName(ParameterInfo info) { + if (! (fNode instanceof MethodRef)) + return; + + MethodRefParameter oldParam= (MethodRefParameter) ((MethodRef) fNode).parameters().get(info.getOldIndex()); + SimpleName oldParamName= oldParam.getName(); + if (oldParamName != null) + getASTRewrite().set(oldParamName, SimpleName.IDENTIFIER_PROPERTY, info.getNewName(), fDescription); + } + + @Override + protected void changeParamgumentType(ParameterInfo info) { + if (! (fNode instanceof MethodRef)) + return; + + MethodRefParameter oldParam= (MethodRefParameter) ((MethodRef) fNode).parameters().get(info.getOldIndex()); + Type oldTypeNode= oldParam.getType(); + Type newTypeNode= createNewDocRefType(info); + if (info.isNewVarargs()) { + if (info.isOldVarargs() && ! oldParam.isVarargs()) { + // leave as array reference if old reference was not vararg + newTypeNode= ASTNodeFactory.newArrayType(newTypeNode); + } else { + getASTRewrite().set(oldParam, MethodRefParameter.VARARGS_PROPERTY, Boolean.TRUE, fDescription); + } + } else { + if (oldParam.isVarargs()) { + getASTRewrite().set(oldParam, MethodRefParameter.VARARGS_PROPERTY, Boolean.FALSE, fDescription); + } + } + + getASTRewrite().replace(oldTypeNode, newTypeNode, fDescription); + registerImportRemoveNode(oldTypeNode); + } + } + + class StaticImportUpdate extends OccurrenceUpdate { + + private final ImportDeclaration fImportDecl; + + public StaticImportUpdate(ImportDeclaration importDecl, CompilationUnitRewrite cuRewrite, RefactoringStatus result) { + super(cuRewrite, null, result); + fImportDecl= importDecl; + } + + @Override + public void updateNode() throws JavaModelException { + ImportRewrite importRewrite= fCuRewrite.getImportRewrite(); + QualifiedName name= (QualifiedName) fImportDecl.getName(); + //will be removed by importRemover if not used elsewhere ... importRewrite.removeStaticImport(name.getFullyQualifiedName()); + importRewrite.addStaticImport(name.getQualifier().getFullyQualifiedName(), fMethodName, false); + } + + @Override + protected ListRewrite getParamgumentsRewrite() { + return null; + } + + @Override + protected ASTNode createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) { + return null; + } + + @Override + protected SimpleName getMethodNameNode() { + return null; + } + } + + class NullOccurrenceUpdate extends OccurrenceUpdate { + private ASTNode fNode; + protected NullOccurrenceUpdate(ASTNode node, CompilationUnitRewrite cuRewrite, RefactoringStatus result) { + super(cuRewrite, null, result); + fNode= node; + } + @Override + public void updateNode() throws JavaModelException { + int start= fNode.getStartPosition(); + int length= fNode.getLength(); + String msg= "Cannot update found node: nodeType=" + fNode.getNodeType() + "; " //$NON-NLS-1$//$NON-NLS-2$ + + fNode.toString() + "[" + start + ", " + length + "] in " + fCuRewrite.getCu(); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + JavaManipulationPlugin.log(new Exception(msg + ":\n" + fCuRewrite.getCu().getSource().substring(start, start + length))); //$NON-NLS-1$ + fResult.addError(msg, JavaStatusContext.create(fCuRewrite.getCu(), fNode)); + } + @Override + protected ListRewrite getParamgumentsRewrite() { + return null; + } + @Override + protected ASTNode createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) { + return null; + } + @Override + protected SimpleName getMethodNameNode() { + return null; + } + } + + private RefactoringStatus initialize(JavaRefactoringArguments arguments) { + final String handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT); + if (handle != null) { + final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false); + if (element == null || !element.exists() || element.getElementType() != IJavaElement.METHOD) + return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getProcessorName(), IJavaRefactorings.CHANGE_METHOD_SIGNATURE); + else { + fMethod= (IMethod) element; + fMethodName= fMethod.getElementName(); + try { + fVisibility= JdtFlags.getVisibilityCode(fMethod); + fReturnTypeInfo= new ReturnTypeInfo(Signature.toString(Signature.getReturnType(fMethod.getSignature()))); + } catch (JavaModelException exception) { + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { Integer.valueOf(fVisibility), + ATTRIBUTE_VISIBILITY })); + } + } + } else + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT)); + final String name= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME); + if (name != null) { + fMethodName= name; + final RefactoringStatus status= Checks.checkMethodName(fMethodName, fMethod); + if (status.hasError()) + return status; + } else + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME)); + final String type= arguments.getAttribute(ATTRIBUTE_RETURN); + if (type != null && !"".equals(type)) //$NON-NLS-1$ + fReturnTypeInfo.setNewTypeName(type); + final String visibility= arguments.getAttribute(ATTRIBUTE_VISIBILITY); + if (visibility != null && !"".equals(visibility)) {//$NON-NLS-1$ + int flag= 0; + try { + flag= Integer.parseInt(visibility); + } catch (NumberFormatException exception) { + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_VISIBILITY)); + } + fVisibility= flag; + } + int count= 1; + String attribute= ATTRIBUTE_PARAMETER + count; + String value= null; + fParameterInfos= new ArrayList<>(3); + while ((value= arguments.getAttribute(attribute)) != null) { + StringTokenizer tokenizer= new StringTokenizer(value, " "); //$NON-NLS-1$ + if (tokenizer.countTokens() < 6) + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { value, ATTRIBUTE_PARAMETER })); + String oldTypeName= tokenizer.nextToken(); + String oldName= tokenizer.nextToken(); + String oldIndex= tokenizer.nextToken(); + String newTypeName= tokenizer.nextToken(); + String newName= tokenizer.nextToken(); + String deleted= tokenizer.nextToken(); + ParameterInfo info= null; + try { + int index= Integer.parseInt(oldIndex); + if (index == -1) { + String result= arguments.getAttribute(ATTRIBUTE_DEFAULT + count); + if (result == null) + result= ""; //$NON-NLS-1$ + info= ParameterInfo.createInfoForAddedParameter(newTypeName, newName, result); + } else { + info= new ParameterInfo(oldTypeName, oldName, index); + info.setNewTypeName(newTypeName); + info.setNewName(newName); + if (Boolean.parseBoolean(deleted)) + info.markAsDeleted(); + } + fParameterInfos.add(info); + } catch (NumberFormatException exception) { + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { value, ATTRIBUTE_PARAMETER })); + } + count++; + attribute= ATTRIBUTE_PARAMETER + count; + } + count= 1; + fExceptionInfos= new ArrayList<>(2); + attribute= JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + count; + while ((value= arguments.getAttribute(attribute)) != null) { + ExceptionInfo info= null; + final String kind= arguments.getAttribute(ATTRIBUTE_KIND + count); + if (kind != null) { + final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), value, false); + if (element == null || !element.exists()) + return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getProcessorName(), IJavaRefactorings.CHANGE_METHOD_SIGNATURE); + else { + try { + info= new ExceptionInfo(element, Integer.parseInt(kind), null); + } catch (NumberFormatException exception) { + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { kind, ATTRIBUTE_KIND })); + } + } + } else + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { kind, ATTRIBUTE_KIND })); + fExceptionInfos.add(info); + count++; + attribute= JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + count; + } + final String deprecate= arguments.getAttribute(ATTRIBUTE_DEPRECATE); + if (deprecate != null) { + fDelegateDeprecation= Boolean.parseBoolean(deprecate); + } else + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_DEPRECATE)); + final String delegate= arguments.getAttribute(ATTRIBUTE_DELEGATE); + if (delegate != null) { + fDelegateUpdating= Boolean.parseBoolean(delegate); + } else + return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_DELEGATE)); + return new RefactoringStatus(); + } + + /** + * If this occurrence update is called from within a declaration update + * (i.e., to update the call inside the newly created delegate), the old + * node does not yet exist and therefore cannot be a move target. + * + * Normally, always use createMoveTarget as this has the advantage of + * being able to add changes inside changed nodes (for example, a method + * call within a method call, see test case #4) and preserving comments + * inside calls. + * @param oldNode original node + * @param rewrite an AST rewrite + * @return the node to insert at the target location + */ + protected T moveNode(T oldNode, ASTRewrite rewrite) { + T movedNode; + if (ASTNodes.isExistingNode(oldNode)) + movedNode= ASTNodes.createMoveTarget(rewrite, oldNode); //node must be one of ast + else + movedNode= ASTNodes.copySubtree(rewrite.getAST(), oldNode); + return movedNode; + } + + public IDefaultValueAdvisor getDefaultValueAdvisor() { + return fDefaultValueAdvisor; + } + + public void setDefaultValueAdvisor(IDefaultValueAdvisor defaultValueAdvisor) { + fDefaultValueAdvisor= defaultValueAdvisor; + } + + @Override + public Object[] getElements() { + return new Object[] { fMethod }; + } + + @Override + public String getIdentifier() { + return IRefactoringProcessorIds.CHANGE_METHOD_SIGNATURE_PROCESSOR; + } + + @Override + public boolean isApplicable() throws CoreException { + return RefactoringAvailabilityTester.isChangeSignatureAvailable(fMethod); + } + + @Override + public RefactoringParticipant[] loadParticipants(RefactoringStatus status, SharableParticipants sharedParticipants) throws CoreException { + String[] affectedNatures= JavaProcessors.computeAffectedNatures(fMethod); + return JavaParticipantManager.loadChangeMethodSignatureParticipants(status, this, fMethod, getParticipantArguments(), null, affectedNatures, sharedParticipants); + } +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/IDefaultValueAdvisor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/IDefaultValueAdvisor.java new file mode 100644 index 0000000000..65d48ac87f --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/IDefaultValueAdvisor.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2007, 2011 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.structure; + +import java.util.List; + +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Type; + +import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo; + +public interface IDefaultValueAdvisor { + + /** + * Creates a default expression for an added parameter for a given method invocation. + * + * @param invocationArguments arguments of the method invocation + * @param addedInfo the added ParamterInfo object + * @param parameterInfos all ParameterInfo objects, including the added ParameterInfo + * @param enclosingMethod the Method that encloses the invocation. Can be null if there is no enclosing method + * @param isRecursive true if called from a recursive invocation + * @param cuRewrite the CompilationUnitRewrite to use for rewrite, imports etc.. + * @return a new Expression to be used as argument for the new parameter + */ + Expression createDefaultExpression(List invocationArguments, ParameterInfo addedInfo, List parameterInfos, MethodDeclaration enclosingMethod, boolean isRecursive, CompilationUnitRewrite cuRewrite); + + /** + * Create a type for the added parameter. + * + * @param newTypeName the fully qualified name of the type + * @param startPosition the position where the type is defined in a compilation unit + * @param cuRewrite the CompilationUnitRewrite to use for rewrite, imports etc.. + * @return the new type to be used in default expressions + */ + Type createType(String newTypeName, int startPosition, CompilationUnitRewrite cuRewrite); + +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/IntroduceParameterObjectProcessor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/IntroduceParameterObjectProcessor.java new file mode 100644 index 0000000000..25e8b10a5a --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/IntroduceParameterObjectProcessor.java @@ -0,0 +1,746 @@ +/******************************************************************************* + * 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.structure; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; +import org.eclipse.ltk.core.refactoring.resource.ResourceChange; + +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.NamingConventions; +import org.eclipse.jdt.core.compiler.IProblem; +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.ArrayCreation; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.ArrayType; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.ExpressionStatement; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.Message; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.NullLiteral; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; +import org.eclipse.jdt.core.dom.SuperFieldAccess; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; +import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; +import org.eclipse.jdt.core.dom.rewrite.ListRewrite; +import org.eclipse.jdt.core.refactoring.descriptors.IntroduceParameterObjectDescriptor; +import org.eclipse.jdt.core.refactoring.descriptors.IntroduceParameterObjectDescriptor.Parameter; +import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor; +import org.eclipse.jdt.core.refactoring.participants.IRefactoringProcessorIds; + +import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin; +import org.eclipse.jdt.internal.core.manipulation.StubUtility; +import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving; +import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; +import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory; +import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; +import org.eclipse.jdt.internal.corext.dom.ASTNodes; +import org.eclipse.jdt.internal.corext.refactoring.Checks; +import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment; +import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo; +import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; +import org.eclipse.jdt.internal.corext.util.JavaModelUtil; +import org.eclipse.jdt.internal.corext.util.Messages; + +public class IntroduceParameterObjectProcessor extends ChangeSignatureProcessor { + + private final class ParameterObjectCreator implements IDefaultValueAdvisor { + @Override + public Expression createDefaultExpression(List invocationArguments, ParameterInfo addedInfo, List parameterInfos, MethodDeclaration enclosingMethod, boolean isRecursive, CompilationUnitRewrite cuRewrite) { + final AST ast= cuRewrite.getAST(); + final ASTRewrite rewrite= cuRewrite.getASTRewrite(); + if (isRecursive && canReuseParameterObject(invocationArguments, addedInfo, parameterInfos, enclosingMethod)) { + return ast.newSimpleName(addedInfo.getNewName()); + } + ClassInstanceCreation classCreation= ast.newClassInstanceCreation(); + + int startPosition= enclosingMethod != null ? enclosingMethod.getStartPosition() : cuRewrite.getRoot().getStartPosition(); + ContextSensitiveImportRewriteContext context= fParameterObjectFactory.createParameterClassAwareContext(fCreateAsTopLevel, cuRewrite, startPosition); + classCreation.setType(fParameterObjectFactory.createType(fCreateAsTopLevel, cuRewrite, startPosition)); + List constructorArguments= classCreation.arguments(); + for (Iterator iter= parameterInfos.iterator(); iter.hasNext();) { + ParameterInfo pi= iter.next(); + if (isValidField(pi)) { + if (pi.isOldVarargs()) { + boolean isLastParameter= !iter.hasNext(); + constructorArguments.addAll(computeVarargs(invocationArguments, pi, isLastParameter, cuRewrite, context)); + } else { + Expression exp= invocationArguments.get(pi.getOldIndex()); + importNodeTypes(exp, cuRewrite, context); + constructorArguments.add(moveNode(exp, rewrite)); + } + } + } + return classCreation; + } + + @Override + public Type createType(String newTypeName, int startPosition, CompilationUnitRewrite cuRewrite) { + return fParameterObjectFactory.createType(fCreateAsTopLevel, cuRewrite, startPosition); + } + + private boolean canReuseParameterObject(List invocationArguments, ParameterInfo addedInfo, List parameterInfos, MethodDeclaration enclosingMethod) { + Assert.isNotNull(enclosingMethod); + List parameters= enclosingMethod.parameters(); + for (ParameterInfo pi : parameterInfos) { + if (isValidField(pi)) { + if (!pi.isInlined()) + return false; + ASTNode node= invocationArguments.get(pi.getOldIndex()); + if (!isParameter(pi, node, parameters, addedInfo.getNewName())) { + return false; + } + } + } + return true; + } + + private List computeVarargs(List invocationArguments, ParameterInfo varArgPI, boolean isLastParameter, CompilationUnitRewrite cuRewrite, ContextSensitiveImportRewriteContext context) { + boolean isEmptyVarArg= varArgPI.getOldIndex() >= invocationArguments.size(); + ASTRewrite rewrite= cuRewrite.getASTRewrite(); + AST ast= cuRewrite.getAST(); + ASTNode lastNode= isEmptyVarArg ? null : invocationArguments.get(varArgPI.getOldIndex()); + List constructorArguments= new ArrayList<>(); + if (lastNode instanceof ArrayCreation) { + ArrayCreation creation= (ArrayCreation) lastNode; + ITypeBinding arrayType= creation.resolveTypeBinding(); + if (arrayType != null && arrayType.isAssignmentCompatible(varArgPI.getNewTypeBinding())) { + constructorArguments.add(moveNode(creation, rewrite)); + return constructorArguments; + } + } + if (isLastParameter) { + // copy all varargs + for (int i= varArgPI.getOldIndex(); i < invocationArguments.size(); i++) { + Expression node= invocationArguments.get(i); + importNodeTypes(node, cuRewrite, context); + constructorArguments.add(moveNode(node, rewrite)); + } + } else { // new signature would be String...args, int + if (lastNode instanceof NullLiteral) { + NullLiteral nullLiteral= (NullLiteral) lastNode; + constructorArguments.add(moveNode(nullLiteral, rewrite)); + } else { + ArrayCreation creation= ast.newArrayCreation(); + creation.setType((ArrayType) importBinding(varArgPI.getNewTypeBinding(), cuRewrite, context)); + ArrayInitializer initializer= ast.newArrayInitializer(); + List expressions= initializer.expressions(); + for (int i= varArgPI.getOldIndex(); i < invocationArguments.size(); i++) { + Expression node= invocationArguments.get(i); + importNodeTypes(node, cuRewrite, context); + expressions.add(moveNode(node, rewrite)); + } + if (expressions.isEmpty()) + creation.dimensions().add(ast.newNumberLiteral("0")); //$NON-NLS-1$ + else + creation.setInitializer(initializer); + constructorArguments.add(creation); + } + } + return constructorArguments; + } + + public Type importBinding(ITypeBinding newTypeBinding, CompilationUnitRewrite cuRewrite, ImportRewriteContext context) { + Type type= cuRewrite.getImportRewrite().addImport(newTypeBinding, cuRewrite.getAST(), context); + cuRewrite.getImportRemover().registerAddedImports(type); + return type; + } + + private void importNodeTypes(ASTNode node, final CompilationUnitRewrite cuRewrite, final ImportRewriteContext context) { + ASTResolving.visitAllBindings(node, nodeBinding -> { + importBinding(nodeBinding, cuRewrite, context); + return false; + }); + } + } + + private boolean isParameter(ParameterInfo pi, ASTNode node, List enclosingMethodParameters, String qualifier) { + if (node instanceof Name) { + Name name= (Name) node; + IVariableBinding binding= ASTNodes.getVariableBinding(name); + if (binding != null && binding.isParameter()) { + return binding.getName().equals(getNameInScope(pi, enclosingMethodParameters)); + } else { + if (node instanceof QualifiedName) { + QualifiedName qn= (QualifiedName) node; + return qn.getFullyQualifiedName().equals(JavaModelUtil.concatenateName(qualifier, getNameInScope(pi, enclosingMethodParameters))); + } + } + } + return false; + } + + private final class RewriteParameterBody extends BodyUpdater { + @Override + public void updateBody(MethodDeclaration methodDeclaration, final CompilationUnitRewrite cuRewrite, RefactoringStatus result) throws CoreException { + // ensure that the parameterObject is imported + fParameterObjectFactory.createType(fCreateAsTopLevel, cuRewrite, methodDeclaration.getStartPosition()); + if (cuRewrite.getCu().equals(getCompilationUnit()) && !fParameterClassCreated) { + createParameterClass(methodDeclaration, cuRewrite); + fParameterClassCreated= true; + } + Block body= methodDeclaration.getBody(); + final List parameters= methodDeclaration.parameters(); + if (body != null) { // abstract methods don't have bodies + final ASTRewrite rewriter= cuRewrite.getASTRewrite(); + ListRewrite bodyStatements= rewriter.getListRewrite(body, Block.STATEMENTS_PROPERTY); + ImportRewriteContext context=new ContextSensitiveImportRewriteContext(body, cuRewrite.getImportRewrite()); + for (ParameterInfo pi : getParameterInfos()) { + if (isValidField(pi)) { + if (isReadOnly(pi, body, parameters, null)) { + body.accept(new ASTVisitor(false) { + + @Override + public boolean visit(SimpleName node) { + updateSimpleName(rewriter, pi, node, parameters, cuRewrite.getCu().getJavaProject()); + return false; + } + + }); + pi.setInlined(true); + } else { + ExpressionStatement initializer= fParameterObjectFactory.createInitializer(pi, getParameterName(), cuRewrite, context); + bodyStatements.insertFirst(initializer, null); + } + } + } + } + + + } + + private void updateSimpleName(ASTRewrite rewriter, ParameterInfo pi, SimpleName node, List enclosingParameters, IJavaProject project) { + AST ast= rewriter.getAST(); + IBinding binding= node.resolveBinding(); + Expression replacementNode= fParameterObjectFactory.createFieldReadAccess(pi, getParameterName(), ast, project, false, null); + if (binding instanceof IVariableBinding) { + IVariableBinding variable= (IVariableBinding) binding; + if (variable.isParameter() && variable.getName().equals(getNameInScope(pi, enclosingParameters))) { + rewriter.replace(node, replacementNode, null); + } + } else { + ASTNode parent= node.getParent(); + if (!(parent instanceof QualifiedName) + && !(parent instanceof FieldAccess) + && !(parent instanceof SuperFieldAccess)) { + if (node.getIdentifier().equals(getNameInScope(pi, enclosingParameters))) { + rewriter.replace(node, replacementNode, null); + } + } + } + } + + private boolean isReadOnly(final ParameterInfo pi, Block block, final List enclosingMethodParameters, final String qualifier) { + class NotWrittenDetector extends ASTVisitor { + boolean notWritten= true; + + @Override + public boolean visit(SimpleName node) { + if (isParameter(pi, node, enclosingMethodParameters, qualifier) && ASTResolving.isWriteAccess(node)) + notWritten= false; + return false; + } + + @Override + public boolean visit(SuperFieldAccess node) { + return false; + } + } + NotWrittenDetector visitor= new NotWrittenDetector(); + block.accept(visitor); + return visitor.notWritten; + } + + @Override + public boolean needsParameterUsedCheck() { + return false; + } + + } + + private static final String PARAMETER_CLASS_APPENDIX= "Parameter"; //$NON-NLS-1$ + + private static final String DEFAULT_PARAMETER_OBJECT_NAME= "parameterObject"; //$NON-NLS-1$ + + private MethodDeclaration fMethodDeclaration; + + private ParameterObjectFactory fParameterObjectFactory; + + private boolean fCreateAsTopLevel= true; + + private ParameterInfo fParameterObjectReference; + + private boolean fParameterClassCreated= false; + + private List fOtherChanges; + + public IntroduceParameterObjectProcessor(IntroduceParameterObjectDescriptor descriptor) throws JavaModelException { + super(descriptor.getMethod()); + IMethod method= descriptor.getMethod(); + Assert.isNotNull(method); + initializeFields(method); + setBodyUpdater(new RewriteParameterBody()); + setDefaultValueAdvisor(new ParameterObjectCreator()); + configureRefactoring(descriptor, this); + } + + private void configureRefactoring(final IntroduceParameterObjectDescriptor parameter, IntroduceParameterObjectProcessor ref) { + ref.setCreateAsTopLevel(parameter.isTopLevel()); + ref.setCreateGetter(parameter.isGetters()); + ref.setCreateSetter(parameter.isSetters()); + ref.setDelegateUpdating(parameter.isDelegate()); + ref.setDeprecateDelegates(parameter.isDeprecateDelegate()); + if (parameter.getClassName() != null) + ref.setClassName(parameter.getClassName()); + if (parameter.getPackageName() != null) + ref.setPackage(parameter.getPackageName()); + if (parameter.getParameterName() != null) + ref.setParameterName(parameter.getParameterName()); + List pis= ref.getParameterInfos(); + Parameter[] parameters= parameter.getParameters(); + if (parameters == null) + parameters= IntroduceParameterObjectDescriptor.createParameters(getMethod()); + Map paramIndex= new HashMap<>(); + for (ParameterInfo pi : pis) { + paramIndex.put(pi.getOldIndex(), pi); + } + paramIndex.put(ParameterInfo.INDEX_FOR_ADDED, fParameterObjectReference); + pis.clear(); + for (Parameter param : parameters) { + ParameterInfo pi= paramIndex.get(Integer.valueOf(param.getIndex())); + pis.add(pi); + if (param != IntroduceParameterObjectDescriptor.PARAMETER_OBJECT) { + pi.setCreateField(param.isCreateField()); + if (pi.isCreateField()) { + String fieldName= param.getFieldName(); + if (fieldName != null) + pi.setNewName(fieldName); + } + } + } + } + + private void initializeFields(IMethod method) { + fParameterObjectFactory= new ParameterObjectFactory(); + String methodName= method.getElementName(); + String className= String.valueOf(Character.toUpperCase(methodName.charAt(0))); + if (methodName.length() > 1) + className+= methodName.substring(1); + className+= PARAMETER_CLASS_APPENDIX; + + fParameterObjectReference= ParameterInfo.createInfoForAddedParameter(className, DEFAULT_PARAMETER_OBJECT_NAME); + fParameterObjectFactory.setClassName(className); + + IType declaringType= method.getDeclaringType(); + Assert.isNotNull(declaringType); + fParameterObjectFactory.setPackage(declaringType.getPackageFragment().getElementName()); + + updateReferenceType(); + } + + @Override + public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException, OperationCanceledException { + RefactoringStatus status= new RefactoringStatus(); + IMethod method= getMethod(); + // TODO: Check for availability + status.merge(Checks.checkTypeName(fParameterObjectFactory.getClassName(), method)); + status.merge(Checks.checkIdentifier(getParameterName(), method)); + if (status.hasFatalError()) + return status; + status.merge(super.checkFinalConditions(pm, context)); + return status; + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { + RefactoringStatus status= new RefactoringStatus(); + status.merge(super.checkInitialConditions(pm)); + if (status.hasFatalError()) + return status; + CompilationUnit astRoot= getBaseCuRewrite().getRoot(); + ISourceRange nameRange= getMethod().getNameRange(); + ASTNode selectedNode= NodeFinder.perform(astRoot, nameRange.getOffset(), nameRange.getLength()); + if (selectedNode == null) { + return mappingErrorFound(status, selectedNode); + } + fMethodDeclaration= ASTNodes.getParent(selectedNode, MethodDeclaration.class); + if (fMethodDeclaration == null) { + return mappingErrorFound(status, selectedNode); + } + IMethodBinding resolveBinding= fMethodDeclaration.resolveBinding(); + if (resolveBinding == null) { + if (!processCompilerError(status, selectedNode)) + status.addFatalError(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_error_cannot_resolve_type); + return status; + } + + ITypeBinding declaringClass= resolveBinding.getDeclaringClass(); + if (fParameterObjectFactory.getPackage() == null) + fParameterObjectFactory.setPackage(declaringClass.getPackage().getName()); + if (fParameterObjectFactory.getEnclosingType() == null) + fParameterObjectFactory.setEnclosingType(declaringClass.getQualifiedName()); + + List parameterInfos= super.getParameterInfos(); + for (ParameterInfo pi : parameterInfos) { + if (!pi.isAdded()) { + if (pi.getOldName().equals(pi.getNewName())) // may have been + // set to + // something + // else after + // creation + pi.setNewName(getFieldName(pi)); + } + } + if (!parameterInfos.contains(fParameterObjectReference)) { + parameterInfos.add(0, fParameterObjectReference); + } + Map bindingMap= new HashMap<>(); + for (Iterator iter= fMethodDeclaration.parameters().iterator(); iter.hasNext();) { + SingleVariableDeclaration sdv= iter.next(); + bindingMap.put(sdv.getName().getIdentifier(), sdv.resolveBinding()); + } + for (ParameterInfo pi : parameterInfos) { + if (pi != fParameterObjectReference) + pi.setOldBinding(bindingMap.get(pi.getOldName())); + } + fParameterObjectFactory.setVariables(parameterInfos); + return status; + } + + @Override + protected boolean shouldReport(IProblem problem, CompilationUnit cu) { + if (!super.shouldReport(problem, cu)) + return false; + ASTNode node= ASTNodeSearchUtil.getAstNode(cu, problem.getSourceStart(), problem.getSourceEnd() - problem.getSourceStart() + 1); + if (node instanceof Type) { + Type type= (Type) node; + if (problem.getID() == IProblem.UndefinedType && getClassName().equals(ASTNodes.getTypeName(type))) { + return false; + } + } + if (node instanceof Name) { + Name name= (Name) node; + if (problem.getID() == IProblem.ImportNotFound && getPackage().indexOf(name.getFullyQualifiedName()) != -1) + return false; + if (problem.getID() == IProblem.MissingTypeInMethod) { + StructuralPropertyDescriptor locationInParent= name.getLocationInParent(); + String[] arguments= problem.getArguments(); + if ((locationInParent == MethodInvocation.NAME_PROPERTY || locationInParent == SuperMethodInvocation.NAME_PROPERTY) + && arguments.length > 3 + && arguments[3].endsWith(getClassName())) + return false; + } + } + return true; + } + + public String getClassName() { + return fParameterObjectFactory.getClassName(); + } + + public ITypeBinding getContainingClass() { + return fMethodDeclaration.resolveBinding().getDeclaringClass(); + } + + private String getMappingErrorMessage() { + return RefactoringCoreMessages.IntroduceParameterObjectRefactoring_cannotalanyzemethod_mappingerror; + } + + public String getFieldName(ParameterInfo element) { + IJavaProject javaProject= getCompilationUnit().getJavaProject(); + String stripped= NamingConventions.getBaseName(NamingConventions.VK_PARAMETER, element.getOldName(), javaProject); + int dim= element.getNewTypeBinding() != null ? element.getNewTypeBinding().getDimensions() : 0; + return StubUtility.getVariableNameSuggestions(NamingConventions.VK_INSTANCE_FIELD, javaProject, stripped, dim, null, true)[0]; + } + + @Override + public Change[] getAllChanges() { + ArrayList changes= new ArrayList<>(Arrays.asList(super.getAllChanges())); + changes.addAll(fOtherChanges); + return changes.toArray(new Change[changes.size()]); + } + + @Override + protected void clearManagers() { + super.clearManagers(); + fOtherChanges= new ArrayList<>(); + fParameterClassCreated= false; + } + + @Override + public String getProcessorName() { + return RefactoringCoreMessages.IntroduceParameterObjectRefactoring_refactoring_name; + } + + @Override + public String getIdentifier() { + return IRefactoringProcessorIds.INTRODUCE_PARAMETER_OBJECT_PROCESSOR; + } + + @Override + public JavaRefactoringDescriptor createDescriptor() { + IntroduceParameterObjectDescriptor ipod= RefactoringSignatureDescriptorFactory.createIntroduceParameterObjectDescriptor(); + ipod.setMethod(getMethod()); + ipod.setClassName(getClassName()); + ipod.setDelegate(getDelegateUpdating()); + ipod.setDeprecateDelegate(getDeprecateDelegates()); + ipod.setGetters(isCreateGetter()); + ipod.setSetters(isCreateSetter()); + ipod.setPackageName(getPackage()); + ipod.setParameterName(getParameterName()); + ipod.setTopLevel(isCreateAsTopLevel()); + + ArrayList parameters= new ArrayList<>(); + List pis= getParameterInfos(); + for (ParameterInfo pi : pis) { + if (pi.isAdded()) { + parameters.add(IntroduceParameterObjectDescriptor.PARAMETER_OBJECT); + } else { + IntroduceParameterObjectDescriptor.Parameter parameter= new IntroduceParameterObjectDescriptor.Parameter(pi.getOldIndex()); + if (pi.isCreateField()) { + parameter.setCreateField(true); + parameter.setFieldName(pi.getNewName()); + } + parameters.add(parameter); + } + } + ipod.setParameters(parameters.toArray(new Parameter[parameters.size()])); + String project= getCompilationUnit().getJavaProject().getElementName(); + try { + ipod.setComment(createComment(project).asString()); + } catch (JavaModelException e) { + JavaManipulationPlugin.log(e); + } + ipod.setProject(project); + ipod.setDescription(getProcessorName()); + ipod.setFlags(getDescriptorFlags()); + return ipod; + } + + private JDTRefactoringDescriptorComment createComment(String project) throws JavaModelException { + String header= Messages.format(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_descriptor_description, getOldMethodSignature()); + JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header); + comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_descriptor_object_class, BasicElementLabels.getJavaElementName(fParameterObjectFactory.getClassName()))); + if (fCreateAsTopLevel) { + comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_descriptor_package, BasicElementLabels.getJavaElementName(fParameterObjectFactory.getPackage()))); + } else { + comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_descriptor_enclosing_type, BasicElementLabels.getJavaElementName(fParameterObjectFactory.getEnclosingType()))); + } + List kept= new ArrayList<>(); + List fields= new ArrayList<>(); + for (ParameterInfo pi : getParameterInfos()) { + if (pi.isCreateField()) { + fields.add(pi.getNewName()); + } else { + if (!pi.isAdded()) { + kept.add(pi.getNewName()); + } + } + } + + comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_descriptor_fields, fields.toArray(new String[0]))); + if (!kept.isEmpty()) + comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_descriptor_keep_parameter, kept.toArray(new String[0]))); + if (fParameterObjectFactory.isCreateGetter()) + comment.addSetting(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_descriptor_create_getter); + if (fParameterObjectFactory.isCreateSetter()) + comment.addSetting(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_descriptor_create_setter); + return comment; + } + + @Override + protected String doGetRefactoringChangeName() { + return getProcessorName(); + } + + public String getParameterName() { + return fParameterObjectReference.getNewName(); + } + + public boolean isCreateGetter() { + return fParameterObjectFactory.isCreateGetter(); + } + + public boolean isCreateSetter() { + return fParameterObjectFactory.isCreateSetter(); + } + + public boolean isCreateAsTopLevel() { + return fCreateAsTopLevel; + } + + /** + * Checks if the given parameter info has been selected for field creation + * + * @param pi parameter info + * @return true if the given parameter info has been selected for field + * creation + */ + private boolean isValidField(ParameterInfo pi) { + return pi.isCreateField() & !pi.isAdded(); + } + + private RefactoringStatus mappingErrorFound(RefactoringStatus result, ASTNode node) { + if (node != null && (node.getFlags() & ASTNode.MALFORMED) != 0 && processCompilerError(result, node)) + return result; + result.addFatalError(getMappingErrorMessage()); + return result; + } + + public void moveFieldDown(ParameterInfo selected) { + fParameterObjectFactory.moveDown(selected); + } + + public void moveFieldUp(ParameterInfo selected) { + fParameterObjectFactory.moveUp(selected); + } + + private boolean processCompilerError(RefactoringStatus result, ASTNode node) { + Message[] messages= ASTNodes.getMessages(node, ASTNodes.INCLUDE_ALL_PARENTS); + if (messages.length == 0) + return false; + result.addFatalError(Messages.format(RefactoringCoreMessages.IntroduceParameterObjectRefactoring_cannotanalysemethod_compilererror, + new String[] { messages[0].getMessage() })); + return true; + } + + public void setClassName(String className) { + fParameterObjectFactory.setClassName(className); + updateReferenceType(); + } + + private void updateReferenceType() { + if (fCreateAsTopLevel) + fParameterObjectReference.setNewTypeName(JavaModelUtil.concatenateName(fParameterObjectFactory.getPackage(), fParameterObjectFactory + .getClassName())); + else + fParameterObjectReference.setNewTypeName(JavaModelUtil.concatenateName(fParameterObjectFactory.getEnclosingType(), + fParameterObjectFactory.getClassName())); + } + + public void setCreateGetter(boolean createGetter) { + fParameterObjectFactory.setCreateGetter(createGetter); + } + + public void setCreateSetter(boolean createSetter) { + fParameterObjectFactory.setCreateSetter(createSetter); + } + + public void setPackageName(String packageName) { + fParameterObjectFactory.setPackage(packageName); + updateReferenceType(); + } + + public void setParameterName(String paramName) { + this.fParameterObjectReference.setNewName(paramName); + } + + public void setCreateAsTopLevel(boolean topLevel) { + this.fCreateAsTopLevel= topLevel; + updateReferenceType(); + } + + public void updateParameterPosition() { + fParameterObjectFactory.updateParameterPosition(fParameterObjectReference); + } + + private void createParameterClass(MethodDeclaration methodDeclaration, CompilationUnitRewrite cuRewrite) throws CoreException { + if (fCreateAsTopLevel) { + IPackageFragmentRoot root= (IPackageFragmentRoot) cuRewrite.getCu().getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); + fOtherChanges.addAll(fParameterObjectFactory.createTopLevelParameterObject(root)); + } else { + ASTRewrite rewriter= cuRewrite.getASTRewrite(); + TypeDeclaration enclosingType= (TypeDeclaration) methodDeclaration.getParent(); + ContextSensitiveImportRewriteContext context=new ContextSensitiveImportRewriteContext(enclosingType, cuRewrite.getImportRewrite()); + ListRewrite bodyRewrite= rewriter.getListRewrite(enclosingType, TypeDeclaration.BODY_DECLARATIONS_PROPERTY); + String fqn= enclosingType.getName().getFullyQualifiedName(); + TypeDeclaration classDeclaration= fParameterObjectFactory.createClassDeclaration(fqn, cuRewrite, null, context); + classDeclaration.modifiers().add(rewriter.getAST().newModifier(ModifierKeyword.PUBLIC_KEYWORD)); + classDeclaration.modifiers().add(rewriter.getAST().newModifier(ModifierKeyword.STATIC_KEYWORD)); + bodyRewrite.insertBefore(classDeclaration, methodDeclaration, null); + } + } + + public String getPackage() { + return fParameterObjectFactory.getPackage(); + } + + public void setPackage(String typeQualifier) { + fParameterObjectFactory.setPackage(typeQualifier); + } + + private String getNameInScope(ParameterInfo pi, List enclosingMethodParameters) { + Assert.isNotNull(enclosingMethodParameters); + boolean emptyVararg= pi.getOldIndex() >= enclosingMethodParameters.size(); + if (!emptyVararg) { + SingleVariableDeclaration svd= enclosingMethodParameters.get(pi.getOldIndex()); + return svd.getName().getIdentifier(); + } + return null; + } + + public String getNewTypeName() { + return fParameterObjectReference.getNewTypeName(); + } + + public ICompilationUnit getCompilationUnit() { + return getBaseCuRewrite().getCu(); + } + + @Override + protected int getDescriptorFlags() { + return super.getDescriptorFlags() | JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT; + } + +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/ParameterObjectFactory.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/ParameterObjectFactory.java new file mode 100644 index 0000000000..cd781a1d01 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/corext/refactoring/structure/ParameterObjectFactory.java @@ -0,0 +1,728 @@ +/******************************************************************************* + * Copyright (c) 2007, 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 + * Samrat Dhillon samrat.dhillon@gmail.com https://bugs.eclipse.org/bugs/show_bug.cgi?id=395558 , https://bugs.eclipse.org/bugs/show_bug.cgi?id=395561 and https://bugs.eclipse.org/bugs/show_bug.cgi?id=394548 + *******************************************************************************/ +package org.eclipse.jdt.internal.corext.refactoring.structure; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +import org.eclipse.text.edits.TextEdit; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; + +import org.eclipse.ltk.core.refactoring.resource.ResourceChange; + +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.NamingConventions; +import org.eclipse.jdt.core.ToolFactory; +import org.eclipse.jdt.core.compiler.IScanner; +import org.eclipse.jdt.core.compiler.ITerminalSymbols; +import org.eclipse.jdt.core.compiler.InvalidInputException; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Assignment; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.BodyDeclaration; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.ExpressionStatement; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.Statement; +import org.eclipse.jdt.core.dom.SuperFieldAccess; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeParameter; +import org.eclipse.jdt.core.dom.VariableDeclarationExpression; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; +import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; +import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; +import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.TypeLocation; +import org.eclipse.jdt.core.dom.rewrite.ListRewrite; +import org.eclipse.jdt.core.manipulation.CodeGeneration; +import org.eclipse.jdt.core.manipulation.JavaManipulation; +import org.eclipse.jdt.internal.core.manipulation.StubUtility; +import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; +import org.eclipse.jdt.internal.corext.dom.ASTNodes; +import org.eclipse.jdt.internal.corext.dom.TokenScanner; +import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo; +import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; +import org.eclipse.jdt.internal.corext.refactoring.changes.CreateCompilationUnitChange; +import org.eclipse.jdt.internal.corext.refactoring.changes.CreatePackageChange; +import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil; +import org.eclipse.jdt.internal.corext.util.JavaModelUtil; + +public class ParameterObjectFactory { + + private String fClassName; + private boolean fCreateGetter; + private boolean fCreateSetter; + private String fEnclosingType; + private String fPackage; + private List fVariables; + + public ParameterObjectFactory() { + super(); + } + + public static class CreationListener { + /** + * Notifies that a getter has been created + * @param cuRewrite the rewriter + * @param getter the new getter + * @param pi the parameter info + */ + public void getterCreated(CompilationUnitRewrite cuRewrite, MethodDeclaration getter, ParameterInfo pi){} + /** + * Notifies that a setter has been created + * @param cuRewrite the rewriter + * @param setter the new setter + * @param pi the parameter info + */ + public void setterCreated(CompilationUnitRewrite cuRewrite, MethodDeclaration setter, ParameterInfo pi){} + /** + * Notifies that a field has been created + * @param cuRewrite the rewriter + * @param field the new field + * @param pi the parameter info + */ + public void fieldCreated(CompilationUnitRewrite cuRewrite, FieldDeclaration field, ParameterInfo pi){} + /** + * Notifies that a constructor has been created + * @param cuRewrite the rewriter + * @param constructor the new constructor + */ + public void constructorCreated(CompilationUnitRewrite cuRewrite, MethodDeclaration constructor){} + /** + * Notifies that a type declaration has been created + * @param cuRewrite the rewriter + * @param declaration the new declaration + */ + public void typeCreated(CompilationUnitRewrite cuRewrite, TypeDeclaration declaration) {} + + protected static ASTNode moveNode(CompilationUnitRewrite cuRewrite, ASTNode node) { + ASTRewrite rewrite= cuRewrite.getASTRewrite(); + if (rewrite.getAST() != node.getAST()) { + String str= ASTNodes.getNodeSource(node, true, true); + if (str != null) { + return rewrite.createStringPlaceholder(str, node.getNodeType()); + } + return ASTNode.copySubtree(rewrite.getAST(), node); + } + return rewrite.createMoveTarget(node); + } + /** + * Return whether the setter should be created for this field. This method is only called when + * the global createSetters is set and the parameterInfo is marked for field creation. + * @param pi the parameter info + * @return true if a setter should be created + */ + public boolean isCreateSetter(ParameterInfo pi) { + return !Modifier.isFinal(pi.getOldBinding().getModifiers()); + } + /** + * Return whether the getter should be created for this field. This method is only called when + * the global createGetters is set and the parameterInfo is marked for field creation. + * @param pi the parameter info + * @return true if a getter should be created + */ + public boolean isCreateGetter(ParameterInfo pi) { + return true; + } + /** + * Return whether the field should appear in the constructor + * @param pi the parameter info + * @return true if the field should appear + */ + public boolean isUseInConstructor(ParameterInfo pi) { + return true; + } + } + + /** + * Creates a new TypeDeclaration for the parameterInfo objects. + * + * @param declaringType the fully qualified name of the type + * @param cuRewrite the {@link CompilationUnitRewrite} that will be used for creation + * @param listener the creation listener or null + * @param context the import rewrite context or null + * @return the new declaration + * @throws CoreException if creation failed + */ + public TypeDeclaration createClassDeclaration(String declaringType, CompilationUnitRewrite cuRewrite, CreationListener listener, ImportRewriteContext context) throws CoreException { + AST ast= cuRewrite.getAST(); + if (listener == null) + listener= new CreationListener(); + TypeDeclaration typeDeclaration= ast.newTypeDeclaration(); + typeDeclaration.setName(ast.newSimpleName(fClassName)); + List body= typeDeclaration.bodyDeclarations(); + for (ParameterInfo pi : fVariables) { + if (isValidField(pi)) { + FieldDeclaration declaration= createField(pi, cuRewrite, context); + listener.fieldCreated(cuRewrite, declaration, pi); + body.add(declaration); + ITypeBinding oldTypeBinding= pi.getOldTypeBinding(); + if(oldTypeBinding != null && oldTypeBinding.isTypeVariable()){ + TypeParameter param= ast.newTypeParameter(); + param.setName(ast.newSimpleName(pi.getNewTypeName())); + typeDeclaration.typeParameters().add(param); + } + } + } + MethodDeclaration constructor= createConstructor(declaringType, cuRewrite, listener, context); + listener.constructorCreated(cuRewrite, constructor); + body.add(constructor); + for (ParameterInfo pi : fVariables) { + if (fCreateGetter && isValidField(pi) && listener.isCreateGetter(pi)) { + MethodDeclaration getter= createGetter(pi, declaringType, cuRewrite, context); + listener.getterCreated(cuRewrite, getter, pi); + body.add(getter); + } + if (fCreateSetter && isValidField(pi) && listener.isCreateSetter(pi)) { + MethodDeclaration setter= createSetter(pi, declaringType, cuRewrite, context); + listener.setterCreated(cuRewrite, setter, pi); + body.add(setter); + } + } + listener.typeCreated(cuRewrite, typeDeclaration); + return typeDeclaration; + } + + private MethodDeclaration createConstructor(String declaringTypeName, CompilationUnitRewrite cuRewrite, CreationListener listener, ImportRewriteContext context) throws CoreException { + AST ast= cuRewrite.getAST(); + ICompilationUnit unit= cuRewrite.getCu(); + IJavaProject project= unit.getJavaProject(); + + MethodDeclaration methodDeclaration= ast.newMethodDeclaration(); + methodDeclaration.setName(ast.newSimpleName(fClassName)); + methodDeclaration.setConstructor(true); + methodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD)); + String lineDelimiter= StubUtility.getLineDelimiterUsed(unit); + if (createComments(project)) { + String comment= CodeGeneration.getMethodComment(unit, declaringTypeName, methodDeclaration, null, lineDelimiter); + if (comment != null) { + Javadoc doc= (Javadoc) cuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC); + methodDeclaration.setJavadoc(doc); + } + } + List parameters= methodDeclaration.parameters(); + Block block= ast.newBlock(); + methodDeclaration.setBody(block); + List statements= block.statements(); + List validParameter= new ArrayList<>(); + for (ParameterInfo pi : fVariables) { + if (isValidField(pi) && listener.isUseInConstructor(pi)) { + validParameter.add(pi); + } + } + + ArrayList usedParameter= new ArrayList<>(); + for (Iterator iter= validParameter.iterator(); iter.hasNext();) { + ParameterInfo pi= iter.next(); + SingleVariableDeclaration svd= ast.newSingleVariableDeclaration(); + ITypeBinding typeBinding= pi.getNewTypeBinding(); + if (!iter.hasNext() && typeBinding.isArray() && pi.isOldVarargs()) { + int dimensions= typeBinding.getDimensions(); + if (dimensions == 1) { + typeBinding= typeBinding.getComponentType(); + } else { + typeBinding= typeBinding.createArrayType(dimensions - 1); + } + svd.setVarargs(true); + } + + String paramName= getParameterName(pi, project, usedParameter); + usedParameter.add(paramName); + + Type fieldType= importBinding(typeBinding, cuRewrite, context, TypeLocation.PARAMETER); + svd.setType(fieldType); + svd.setName(ast.newSimpleName(paramName)); + parameters.add(svd); + Expression leftHandSide; + if (paramName.equals(pi.getNewName()) || StubUtility.useThisForFieldAccess(project)) { + FieldAccess fieldAccess= ast.newFieldAccess(); + fieldAccess.setName(ast.newSimpleName(pi.getNewName())); + fieldAccess.setExpression(ast.newThisExpression()); + leftHandSide= fieldAccess; + } else { + leftHandSide= ast.newSimpleName(pi.getNewName()); + } + Assignment assignment= ast.newAssignment(); + assignment.setLeftHandSide(leftHandSide); + assignment.setRightHandSide(ast.newSimpleName(paramName)); + statements.add(ast.newExpressionStatement(assignment)); + } + return methodDeclaration; + } + + private String getParameterName(ParameterInfo pi, IJavaProject project, ArrayList usedParameter) { + String fieldName= pi.getNewName(); + String strippedName= NamingConventions.getBaseName(NamingConventions.VK_INSTANCE_FIELD, fieldName, project); + String[] suggestions= StubUtility.getVariableNameSuggestions(NamingConventions.VK_PARAMETER, project, strippedName, 0, usedParameter, true); + return suggestions[0]; + } + + + public static Type importBinding(ITypeBinding typeBinding, CompilationUnitRewrite cuRewrite, ImportRewriteContext context, TypeLocation typeLocation) { + int declaredModifiers= typeBinding.getModifiers(); + AST ast= cuRewrite.getAST(); + if (Modifier.isPrivate(declaredModifiers) || Modifier.isProtected(declaredModifiers)) { + return ast.newSimpleType(ast.newSimpleName(typeBinding.getName())); + } + Type type= cuRewrite.getImportRewrite().addImport(typeBinding, cuRewrite.getAST(), context, typeLocation); + cuRewrite.getImportRemover().registerAddedImports(type); + return type; + } + + private FieldDeclaration createField(ParameterInfo pi, CompilationUnitRewrite cuRewrite, ImportRewriteContext context) throws CoreException { + AST ast= cuRewrite.getAST(); + ICompilationUnit unit= cuRewrite.getCu(); + + VariableDeclarationFragment fragment= ast.newVariableDeclarationFragment(); + String lineDelim= StubUtility.getLineDelimiterUsed(unit); + SimpleName fieldName= ast.newSimpleName(pi.getNewName()); + fragment.setName(fieldName); + FieldDeclaration declaration= ast.newFieldDeclaration(fragment); + if (createComments(unit.getJavaProject())) { + String comment= StubUtility.getFieldComment(unit, pi.getNewTypeName(), pi.getNewName(), lineDelim); + if (comment != null) { + Javadoc doc= (Javadoc) cuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC); + declaration.setJavadoc(doc); + } + } + List modifiers= new ArrayList<>(); + if (fCreateGetter) { + modifiers.add(ast.newModifier(ModifierKeyword.PRIVATE_KEYWORD)); + } else { + modifiers.add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD)); + } + declaration.modifiers().addAll(modifiers); + declaration.setType(importBinding(pi.getNewTypeBinding(), cuRewrite, context, TypeLocation.FIELD)); + return declaration; + } + + public Expression createFieldReadAccess(ParameterInfo pi, String paramName, AST ast, IJavaProject project, boolean useSuper, Expression qualifier) { + Expression completeQualifier= generateQualifier(paramName, ast, useSuper, qualifier); + if (fCreateGetter) { + MethodInvocation mi= ast.newMethodInvocation(); + mi.setName(ast.newSimpleName(getGetterName(pi, ast, project))); + mi.setExpression(completeQualifier); + return mi; + } + return createFieldAccess(pi, ast, completeQualifier); + } + + public Expression createFieldWriteAccess(ParameterInfo pi, String paramName, AST ast, IJavaProject project, Expression assignedValue, boolean useSuper, Expression qualifier) { + Expression completeQualifier= generateQualifier(paramName, ast, useSuper, qualifier); + if (fCreateSetter) { + MethodInvocation mi= ast.newMethodInvocation(); + mi.setName(ast.newSimpleName(getSetterName(pi, ast, project))); + mi.setExpression(completeQualifier); + mi.arguments().add(assignedValue); + return mi; + } + return createFieldAccess(pi, ast, completeQualifier); + } + + private Expression generateQualifier(String paramName, AST ast, boolean useSuper, Expression qualifier) { + SimpleName paramSimpleName= ast.newSimpleName(paramName); + if (useSuper) { + SuperFieldAccess sf= ast.newSuperFieldAccess(); + sf.setName(paramSimpleName); + if (qualifier instanceof Name) { + sf.setQualifier((Name) qualifier); + } + return sf; + } + if (qualifier != null) { + FieldAccess parameterAccess= ast.newFieldAccess(); + parameterAccess.setExpression(qualifier); + parameterAccess.setName(paramSimpleName); + return parameterAccess; + } + return paramSimpleName; + } + + + + private Expression createFieldAccess(ParameterInfo pi, AST ast, Expression qualifier) { + if (qualifier instanceof Name) { + Name name= (Name) qualifier; //create FQN for IPOR + return ast.newName(JavaModelUtil.concatenateName(name.getFullyQualifiedName(), pi.getNewName())); + } + FieldAccess fa= ast.newFieldAccess(); + fa.setName(ast.newSimpleName(pi.getNewName())); + fa.setExpression(qualifier); + return fa; + } + + private MethodDeclaration createGetter(ParameterInfo pi, String declaringType, CompilationUnitRewrite cuRewrite, ImportRewriteContext context) throws CoreException { + AST ast= cuRewrite.getAST(); + ICompilationUnit cu= cuRewrite.getCu(); + IJavaProject project= cu.getJavaProject(); + + MethodDeclaration methodDeclaration= ast.newMethodDeclaration(); + String fieldName= pi.getNewName(); + String getterName= getGetterName(pi, ast, project); + String lineDelim= StubUtility.getLineDelimiterUsed(cu); + String bareFieldname= NamingConventions.getBaseName(NamingConventions.VK_INSTANCE_FIELD, fieldName, project); + if (createComments(project)) { + String comment= CodeGeneration.getGetterComment(cu, declaringType, getterName, fieldName, pi.getNewTypeName(), bareFieldname, lineDelim); + if (comment != null) + methodDeclaration.setJavadoc((Javadoc) cuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC)); + } + methodDeclaration.setName(ast.newSimpleName(getterName)); + methodDeclaration.setReturnType2(importBinding(pi.getNewTypeBinding(), cuRewrite, context, TypeLocation.RETURN_TYPE)); + methodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD)); + Block block= ast.newBlock(); + methodDeclaration.setBody(block); + boolean useThis= StubUtility.useThisForFieldAccess(project); + if (useThis) { + fieldName= "this." + fieldName; //$NON-NLS-1$ + } + String bodyContent= CodeGeneration.getGetterMethodBodyContent(cu, declaringType, getterName, fieldName, lineDelim); + ASTNode getterBody= cuRewrite.getASTRewrite().createStringPlaceholder(bodyContent, ASTNode.EXPRESSION_STATEMENT); + block.statements().add(getterBody); + return methodDeclaration; + } + + public ExpressionStatement createInitializer(ParameterInfo pi, String paramName, CompilationUnitRewrite cuRewrite, ImportRewriteContext context) { + AST ast= cuRewrite.getAST(); + + VariableDeclarationFragment fragment= ast.newVariableDeclarationFragment(); + fragment.setName(ast.newSimpleName(pi.getOldName())); + fragment.setInitializer(createFieldReadAccess(pi, paramName, ast, cuRewrite.getCu().getJavaProject(), false, null)); + VariableDeclarationExpression declaration= ast.newVariableDeclarationExpression(fragment); + IVariableBinding variable= pi.getOldBinding(); + declaration.setType(importBinding(pi.getNewTypeBinding(), cuRewrite, context, TypeLocation.LOCAL_VARIABLE)); + int modifiers= variable.getModifiers(); + List newModifiers= ast.newModifiers(modifiers); + declaration.modifiers().addAll(newModifiers); + return ast.newExpressionStatement(declaration); + } + + private MethodDeclaration createSetter(ParameterInfo pi, String declaringType, CompilationUnitRewrite cuRewrite, ImportRewriteContext context) throws CoreException { + AST ast= cuRewrite.getAST(); + ICompilationUnit cu= cuRewrite.getCu(); + IJavaProject project= cu.getJavaProject(); + + MethodDeclaration methodDeclaration= ast.newMethodDeclaration(); + String fieldName= pi.getNewName(); + String setterName= getSetterName(pi, ast, project); + String lineDelim= StubUtility.getLineDelimiterUsed(cu); + String bareFieldname= NamingConventions.getBaseName(NamingConventions.VK_INSTANCE_FIELD, fieldName, project); + String paramName= StubUtility.suggestArgumentName(project, bareFieldname, null); + if (createComments(project)) { + String comment= CodeGeneration.getSetterComment(cu, declaringType, setterName, fieldName, pi.getNewTypeName(), paramName, bareFieldname, lineDelim); + if (comment != null) + methodDeclaration.setJavadoc((Javadoc) cuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC)); + } + methodDeclaration.setName(ast.newSimpleName(setterName)); + methodDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD)); + SingleVariableDeclaration variable= ast.newSingleVariableDeclaration(); + variable.setType(importBinding(pi.getNewTypeBinding(), cuRewrite, context, TypeLocation.PARAMETER)); + variable.setName(ast.newSimpleName(paramName)); + methodDeclaration.parameters().add(variable); + Block block= ast.newBlock(); + methodDeclaration.setBody(block); + boolean useThis= StubUtility.useThisForFieldAccess(project); + if (useThis || fieldName.equals(paramName)) { + fieldName= "this." + fieldName; //$NON-NLS-1$ + } + String bodyContent= CodeGeneration.getSetterMethodBodyContent(cu, declaringType, setterName, fieldName, paramName, lineDelim); + ASTNode setterBody= cuRewrite.getASTRewrite().createStringPlaceholder(bodyContent, ASTNode.EXPRESSION_STATEMENT); + block.statements().add(setterBody); + return methodDeclaration; + } + + public Type createType(boolean asTopLevelClass, CompilationUnitRewrite cuRewrite, int position) { + String qualifier= asTopLevelClass ? fPackage : fEnclosingType; + String concatenateName= JavaModelUtil.concatenateName(qualifier, fClassName); + + ImportRewrite importRewrite= cuRewrite.getImportRewrite(); + ContextSensitiveImportRewriteContext context= createParameterClassAwareContext(asTopLevelClass, cuRewrite, position); + String addedImport= importRewrite.addImport(concatenateName, context); + cuRewrite.getImportRemover().registerAddedImport(addedImport); + AST ast= cuRewrite.getAST(); + return ast.newSimpleType(ast.newName(addedImport)); + } + + ContextSensitiveImportRewriteContext createParameterClassAwareContext(final boolean asTopLevelClass, final CompilationUnitRewrite cuRewrite, int position) { + ContextSensitiveImportRewriteContext context= new ContextSensitiveImportRewriteContext(cuRewrite.getRoot(), position, cuRewrite.getImportRewrite()) { + @Override + public int findInContext(String qualifier, String name, int kind) { + String parameterClassName= getClassName(); + if (kind == ImportRewriteContext.KIND_TYPE && parameterClassName.equals(name)) { + String parameterClassQualifier= asTopLevelClass ? getPackage() : getEnclosingType(); + if (super.findInContext(qualifier, "", kind) == ImportRewriteContext.RES_NAME_FOUND) { //$NON-NLS-1$ // TODO: should be "*", not " "! + if (parameterClassQualifier.equals(qualifier)) { + return ImportRewriteContext.RES_NAME_FOUND; + } else { + return ImportRewriteContext.RES_NAME_CONFLICT; + } + } + } + return super.findInContext(qualifier, name, kind); + } + }; + return context; + } + + public String getClassName() { + return fClassName; + } + + public String getEnclosingType() { + return fEnclosingType; + } + + private String getGetterName(ParameterInfo pi, AST ast, IJavaProject project) { + ITypeBinding type= pi.getNewTypeBinding(); + boolean isBoolean= ast.resolveWellKnownType("boolean").isEqualTo(type) || ast.resolveWellKnownType("java.lang.Boolean").isEqualTo(type); //$NON-NLS-1$//$NON-NLS-2$ + return NamingConventions.suggestGetterName(project, pi.getNewName(), Flags.AccPublic, isBoolean, null); + } + + public String getPackage() { + return fPackage; + } + + public ParameterInfo getParameterInfo(String identifier) { + for (ParameterInfo pi : fVariables) { + if (pi.getOldName().equals(identifier)) + return pi; + } + return null; + } + + private String getSetterName(ParameterInfo pi, AST ast, IJavaProject project) { + ITypeBinding type= pi.getNewTypeBinding(); + boolean isBoolean= ast.resolveWellKnownType("boolean").isEqualTo(type) || ast.resolveWellKnownType("java.lang.Boolean").isEqualTo(type); //$NON-NLS-1$//$NON-NLS-2$ + return NamingConventions.suggestSetterName(project, pi.getNewName(), Flags.AccPublic, isBoolean, null); + } + + public boolean isCreateGetter() { + return fCreateGetter; + } + + public boolean isCreateSetter() { + return fCreateSetter; + } + + private boolean isValidField(ParameterInfo pi) { + return pi.isCreateField() && !pi.isAdded(); + } + + public void moveDown(ParameterInfo selected) { + int idx= fVariables.indexOf(selected); + Assert.isTrue(idx >= 0 && idx < fVariables.size() - 1); + int nextIdx= idx + 1; + ParameterInfo next= fVariables.get(nextIdx); + if (next.isAdded()) { + nextIdx++; + Assert.isTrue(nextIdx <= fVariables.size() - 1); + next= fVariables.get(nextIdx); + } + fVariables.set(idx, next); + fVariables.set(nextIdx, selected); + } + + public void moveUp(ParameterInfo selected) { + int idx= fVariables.indexOf(selected); + Assert.isTrue(idx > 0); + int prevIdx= idx - 1; + ParameterInfo prev= fVariables.get(prevIdx); + if (prev.isAdded()) { + prevIdx--; + Assert.isTrue(prevIdx >= 0); + prev= fVariables.get(prevIdx); + } + fVariables.set(idx, prev); + fVariables.set(prevIdx, selected); + } + + public void setClassName(String className) { + fClassName= className; + } + + public void setCreateGetter(boolean createGetter) { + fCreateGetter= createGetter; + } + + public void setCreateSetter(boolean createSetter) { + fCreateSetter= createSetter; + } + + public void setEnclosingType(String enclosingType) { + fEnclosingType= enclosingType; + } + + public void setPackage(String typeQualifier) { + fPackage= typeQualifier; + } + + public void setVariables(List parameters) { + fVariables= parameters; + } + + /** + * Updates the position of the newly inserted parameterObject so that it is + * directly after the first checked parameter + * + * @param parameterObjectReference the inserted parameterObject + */ + public void updateParameterPosition(ParameterInfo parameterObjectReference) { + fVariables.remove(parameterObjectReference); + for (ListIterator iterator= fVariables.listIterator(); iterator.hasNext();) { + ParameterInfo pi= iterator.next(); + if (isValidField(pi)) { + iterator.add(parameterObjectReference); + return; + } + } + } + + private boolean createComments(IJavaProject project) { + return StubUtility.doAddComments(project); + } + + + public List createTopLevelParameterObject(IPackageFragmentRoot packageFragmentRoot, CreationListener listener) throws CoreException { + List changes= new ArrayList<>(); + IPackageFragment packageFragment= packageFragmentRoot.getPackageFragment(getPackage()); + if (!packageFragment.exists()) { + changes.add(new CreatePackageChange(packageFragment)); + } + ICompilationUnit unit= packageFragment.getCompilationUnit(getClassName() + JavaModelUtil.DEFAULT_CU_SUFFIX); + Assert.isTrue(!unit.exists()); + IJavaProject javaProject= unit.getJavaProject(); + ICompilationUnit workingCopy= unit.getWorkingCopy(null); + + try { + // create stub with comments and dummy type + String lineDelimiter= StubUtility.getLineDelimiterUsed(javaProject); + String fileComment= getFileComment(workingCopy, lineDelimiter); + String typeComment= getTypeComment(workingCopy, lineDelimiter); + String content= CodeGeneration.getCompilationUnitContent(workingCopy, fileComment, typeComment, "class " + getClassName() + "{}", lineDelimiter); //$NON-NLS-1$ //$NON-NLS-2$ + workingCopy.getBuffer().setContents(content); + + CompilationUnitRewrite cuRewrite= new CompilationUnitRewrite(workingCopy); + ASTRewrite rewriter= cuRewrite.getASTRewrite(); + CompilationUnit root= cuRewrite.getRoot(); + AST ast= cuRewrite.getAST(); + ImportRewrite importRewrite= cuRewrite.getImportRewrite(); + ContextSensitiveImportRewriteContext context=new ContextSensitiveImportRewriteContext(root, cuRewrite.getImportRewrite()); + + // retrieve&replace dummy type with real class + ListRewrite types= rewriter.getListRewrite(root, CompilationUnit.TYPES_PROPERTY); + ASTNode dummyType= (ASTNode) types.getOriginalList().get(0); + String newTypeName= JavaModelUtil.concatenateName(getPackage(), getClassName()); + TypeDeclaration classDeclaration= createClassDeclaration(newTypeName, cuRewrite, listener, context); + classDeclaration.modifiers().add(ast.newModifier(ModifierKeyword.PUBLIC_KEYWORD)); + Javadoc javadoc= (Javadoc) dummyType.getStructuralProperty(TypeDeclaration.JAVADOC_PROPERTY); + rewriter.set(classDeclaration, TypeDeclaration.JAVADOC_PROPERTY, javadoc, null); + types.replace(dummyType, classDeclaration, null); + + // Apply rewrites and discard workingcopy + // Using CompilationUnitRewrite.createChange() leads to strange + // results + String charset= ResourceUtil.getFile(unit).getCharset(false); + Document document= new Document(content); + try { + rewriter.rewriteAST().apply(document); + TextEdit rewriteImports= importRewrite.rewriteImports(null); + rewriteImports.apply(document); + } catch (BadLocationException e) { + throw new CoreException(new Status(IStatus.ERROR, JavaManipulation.ID_PLUGIN, RefactoringCoreMessages.IntroduceParameterObjectRefactoring_parameter_object_creation_error, e)); + } + String docContent= document.get(); + CreateCompilationUnitChange compilationUnitChange= new CreateCompilationUnitChange(unit, docContent, charset); + changes.add(compilationUnitChange); + } finally { + workingCopy.discardWorkingCopy(); + } + return changes; + } + + public List createTopLevelParameterObject(IPackageFragmentRoot packageFragmentRoot) throws CoreException { + return createTopLevelParameterObject(packageFragmentRoot, null); + } + + protected String getFileComment(ICompilationUnit parentCU, String lineDelimiter) throws CoreException { + if (StubUtility.doAddComments(parentCU.getJavaProject())) { + return CodeGeneration.getFileComment(parentCU, lineDelimiter); + } + return null; + + } + + protected String getTypeComment(ICompilationUnit parentCU, String lineDelimiter) throws CoreException { + IJavaProject javaProject= parentCU.getJavaProject(); + if (StubUtility.doAddComments(javaProject)) { + StringBuilder typeName= new StringBuilder(); + typeName.append(getClassName()); + String[] typeParamNames= new String[0]; + String comment= CodeGeneration.getTypeComment(parentCU, typeName.toString(), typeParamNames, lineDelimiter); + if (comment != null && isValidComment(comment, javaProject)) { + return comment; + } + } + return null; + } + + private boolean isValidComment(String template, IJavaProject javaProject) { + IScanner scanner; + if (javaProject != null) { + String sourceLevel = javaProject.getOption(JavaCore.COMPILER_SOURCE, true); + String complianceLevel = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); + scanner = ToolFactory.createScanner(true, false, false, sourceLevel, complianceLevel); + } else { + scanner= ToolFactory.createScanner(true, false, false, false); + } + scanner.setSource(template.toCharArray()); + try { + int next= scanner.getNextToken(); + while (TokenScanner.isComment(next)) { + next= scanner.getNextToken(); + } + return next == ITerminalSymbols.TokenNameEOF; + } catch (InvalidInputException e) { + } + return false; + } + +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/ui/refactoring/contentassist/JavaTypeCompletionProcessorCore.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/ui/refactoring/contentassist/JavaTypeCompletionProcessorCore.java new file mode 100644 index 0000000000..8e089b43a2 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/internal/ui/refactoring/contentassist/JavaTypeCompletionProcessorCore.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2023 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 + * Microsoft Corporation - based this file on JavaTypeCompletionProcessor + *******************************************************************************/ +package org.eclipse.jdt.internal.ui.refactoring.contentassist; + +import org.eclipse.jdt.internal.corext.util.JavaModelUtil; +/** + * @since 1.18 + */ +public class JavaTypeCompletionProcessorCore { + public static final String DUMMY_CLASS_NAME= "$$__$$"; //$NON-NLS-1$ + /** + * The CU name to be used if no parent ICompilationUnit is available. + * The main type of this class will be filtered out from the proposals list. + */ + public static final String DUMMY_CU_NAME= DUMMY_CLASS_NAME + JavaModelUtil.DEFAULT_CU_SUFFIX; +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JavaCodeActionKind.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JavaCodeActionKind.java index d09b54b8a1..25e0a06ef9 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JavaCodeActionKind.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JavaCodeActionKind.java @@ -113,6 +113,11 @@ public interface JavaCodeActionKind { */ public static final String REFACTOR_INTRODUCE_PARAMETER = CodeActionKind.Refactor + ".introduce.parameter"; + /** + * Change Signature + */ + public static final String REFACTOR_CHANGE_SIGNATURE = CodeActionKind.Refactor + ".change.signature"; + /** * Base kind for "quickassist" code actions */ diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/RefactoringAvailabilityTester.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/RefactoringAvailabilityTester.java index 633aa21d62..266721354e 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/RefactoringAvailabilityTester.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/RefactoringAvailabilityTester.java @@ -167,7 +167,7 @@ public static IType getTopLevelType(final IMember[] members) { } public static boolean isChangeSignatureAvailable(final IMethod method) throws JavaModelException { - return Checks.isAvailable(method) && !Flags.isAnnotation(method.getDeclaringType().getFlags()); + return (method != null) && Checks.isAvailable(method) && !Flags.isAnnotation(method.getDeclaringType().getFlags()); } // public static boolean isChangeSignatureAvailable(final IStructuredSelection selection) throws JavaModelException { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/RefactoringCoreMessages.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/RefactoringCoreMessages.java index 1c8fd489d1..8f7937828d 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/RefactoringCoreMessages.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/RefactoringCoreMessages.java @@ -69,6 +69,8 @@ public final class RefactoringCoreMessages extends NLS { public static String ChangeSignatureRefactoring_change_signature; + public static String ChangeSignatureRefactoring_change_signature_for; + public static String ChangeSignatureRefactoring_changed_parameter_pattern; public static String ChangeSignatureRefactoring_changed_parameters; diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/refactoring.properties b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/refactoring.properties index f90ff05690..70340605a1 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/refactoring.properties +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corext/refactoring/refactoring.properties @@ -814,6 +814,7 @@ ChangeSignatureRefactoring_constructor_name=The method should not have the same ChangeSignatureRefactoring_no_exception_binding=Cannot resolve the type binding of a thrown exception. Compilation errors must be fixed before this refactoring can be performed. ChangeSignatureRefactoring_param_name_not_empty=Enter the name for parameter {0}. ChangeSignatureRefactoring_change_signature=Change signature +ChangeSignatureRefactoring_change_signature_for=Change signature for ''{0}'' ChangeSignatureRefactoring_changed_parameters=Changed parameters: ChangeSignatureRefactoring_update_reference=Update reference ChangeSignatureRefactoring_new_name_pattern=New name: ''{0}'' diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/RefactorProcessor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/RefactorProcessor.java index dc18ef5c88..8dfce6461d 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/RefactorProcessor.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/RefactorProcessor.java @@ -153,6 +153,7 @@ public List getProposals(CodeActionParams params, IInv getAssignToVariableProposals(context, coveringNode, locations, proposals, params); getIntroduceParameterProposals(params, context, coveringNode, locations, proposals); getExtractInterfaceProposal(params, context, proposals); + getChangeSignatureProposal(params, context, proposals); } return proposals; } @@ -1004,4 +1005,19 @@ private boolean getExtractInterfaceProposal(CodeActionParams params, IInvocation proposals.add(proposal); return true; } + + private boolean getChangeSignatureProposal(CodeActionParams params, IInvocationContext context, Collection proposals) { + if (proposals == null) { + return false; + } + + ChangeCorrectionProposal proposal = RefactorProposalUtility.getChangeSignatureProposal(params, context); + + if (proposal == null) { + return false; + } + + proposals.add(proposal); + return true; + } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/ChangeSignatureHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/ChangeSignatureHandler.java new file mode 100644 index 0000000000..797e2f6e3d --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/ChangeSignatureHandler.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2023 Microsoft Corporation and others. + * All rights reserved. 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: + * Microsoft Corporation - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.ls.core.internal.handlers; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.ICompilationUnit; +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.JavaCore; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.TypeNameMatch; +import org.eclipse.jdt.core.search.TypeNameMatchRequestor; +import org.eclipse.jdt.internal.corext.refactoring.ExceptionInfo; +import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo; +import org.eclipse.jdt.internal.corext.refactoring.structure.ChangeSignatureProcessor; +import org.eclipse.jdt.internal.corext.util.JdtFlags; +import org.eclipse.jdt.ls.core.internal.JDTUtils; +import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; +import org.eclipse.lsp4j.CodeActionParams; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.participants.ProcessorBasedRefactoring; + +public class ChangeSignatureHandler { + + public static class MethodParameter { + public String type; + public String name; + public String defaultValue; + public int originalIndex; + + public MethodParameter(String type, String name, String defaultValue, int originalIndex) { + this.type = type; + this.name = name; + this.defaultValue = defaultValue; + this.originalIndex = originalIndex; + } + } + + public static class MethodException { + public String type; + public String typeHandleIdentifier; + + public MethodException(String type, String typeHandleIdentifier) { + this.type = type; + this.typeHandleIdentifier = typeHandleIdentifier; + } + } + + public static Refactoring getChangeSignatureRefactoring(CodeActionParams params, IMethod method, boolean isDelegate, String methodName, String modifier, String returnType, List parameters, + List exceptions) { + ICompilationUnit cu = JDTUtils.resolveCompilationUnit(params.getTextDocument().getUri()); + if (cu == null) { + return null; + } + IType primaryType = cu.findPrimaryType(); + if (primaryType == null) { + return null; + } + try { + ChangeSignatureProcessor processor = new ChangeSignatureProcessor(method); + processor.setNewMethodName(methodName); + processor.setVisibility(JdtFlags.getVisibilityCode(modifier)); + processor.setNewReturnTypeName(returnType); + processor.setDelegateUpdating(isDelegate); + RefactoringStatus status = processor.checkInitialConditions(new NullProgressMonitor()); + if (status.hasFatalError()) { + if (status.hasFatalError()) { + logFatalError(status); + return null; + } + } + List parameterInfos = processor.getParameterInfos(); + List newParameterInfos = new ArrayList<>(); + for (MethodParameter param : parameters) { + if (param.originalIndex != ParameterInfo.INDEX_FOR_ADDED && param.originalIndex < parameterInfos.size()) { + ParameterInfo info = parameterInfos.get(param.originalIndex); + info.setNewTypeName(param.type); + info.setNewName(param.name); + newParameterInfos.add(info); + } else { + newParameterInfos.add(ParameterInfo.createInfoForAddedParameter(param.type, param.name, param.defaultValue)); + } + } + parameterInfos.clear(); + parameterInfos.addAll(newParameterInfos); + List exceptionInfos = processor.getExceptionInfos(); + List newExceptionInfos = new ArrayList<>(); + for (MethodException exception : exceptions) { + if (exception.typeHandleIdentifier != null) { + IJavaElement element = JavaCore.create(exception.typeHandleIdentifier); + if (element instanceof IType type) { + newExceptionInfos.add(ExceptionInfo.createInfoForAddedException(type)); + } + } else { + IType type = cu.getJavaProject().findType(exception.type); + if (type == null) { + // exception.type is not FQN, or it should be found via IJavaProject.findType() + SearchEngine engine = new SearchEngine(); + IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaProject[] { cu.getJavaProject() }, true); + int qualIndex = exception.type.lastIndexOf('.'); + if (qualIndex == -1) { + List foundTypes = new ArrayList<>(); + engine.searchAllTypeNames(null, SearchPattern.R_FULL_MATCH, exception.type.toCharArray(), SearchPattern.R_FULL_MATCH, IJavaSearchConstants.TYPE, scope, new TypeNameMatchRequestor() { + @Override + public void acceptTypeNameMatch(TypeNameMatch match) { + IType type = match.getType(); + if (type.exists()) { + foundTypes.add(type); + } + } + }, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, new NullProgressMonitor()); + if (foundTypes.size() == 1) { + type = foundTypes.get(0); + } + } + } + if (type != null) { + newExceptionInfos.add(ExceptionInfo.createInfoForAddedException(type)); + } + } + } + exceptionInfos.clear(); + exceptionInfos.addAll(newExceptionInfos); + Refactoring refactoring = new ProcessorBasedRefactoring(processor); + refactoring.checkInitialConditions(new NullProgressMonitor()); + status = refactoring.checkFinalConditions(new NullProgressMonitor()); + if (status.hasFatalError()) { + logFatalError(status); + return null; + } + return refactoring; + } catch (CoreException e) { + JavaLanguageServerPlugin.logException(e); + } + return null; + } + + private static void logFatalError(RefactoringStatus status) { + String message = status.getMessageMatchingSeverity(RefactoringStatus.FATAL); + if (message == null) { + message = status.getMessageMatchingSeverity(RefactoringStatus.ERROR); + } + JavaLanguageServerPlugin.getProjectsManager().getConnection().showMessage(new MessageParams(MessageType.Error, message)); + JavaLanguageServerPlugin.logError(status.toString()); + } +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/GetRefactorEditHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/GetRefactorEditHandler.java index 91fa3ff9d8..39f7b08a81 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/GetRefactorEditHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/GetRefactorEditHandler.java @@ -21,6 +21,9 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.internal.corext.fix.LinkedProposalModelCore; import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroupCore; @@ -131,6 +134,32 @@ public static RefactorWorkspaceEdit getEditsForRefactor(GetRefactorEditParams pa WorkspaceEdit edit = ChangeUtil.convertToWorkspaceEdit(change); return new RefactorWorkspaceEdit(edit, null); } + } else if (RefactorProposalUtility.CHANGE_SIGNATURE_COMMAND.equals(params.command)) { + if (params.commandArguments != null && params.commandArguments.size() == 7) { + String handleIdentifier = JSONUtility.toModel(params.commandArguments.get(0), String.class); + Boolean isDelegate = JSONUtility.toModel(params.commandArguments.get(1), Boolean.class); + String methodName = JSONUtility.toModel(params.commandArguments.get(2), String.class); + String modifier = JSONUtility.toModel(params.commandArguments.get(3), String.class); + String returnType = JSONUtility.toModel(params.commandArguments.get(4), String.class); + List parameters = Arrays.asList(JSONUtility.toModel(params.commandArguments.get(5), ChangeSignatureHandler.MethodParameter[].class)); + List exceptions = Arrays.asList(JSONUtility.toModel(params.commandArguments.get(6), ChangeSignatureHandler.MethodException[].class)); + if (handleIdentifier == null) { + return null; + } + IJavaElement element = JavaCore.create(handleIdentifier); + if (element instanceof IMethod method) { + Refactoring refactoring = ChangeSignatureHandler.getChangeSignatureRefactoring(params.context, method, isDelegate, methodName, modifier, returnType, parameters, exceptions); + if (refactoring == null) { + return null; + } + Change change = refactoring.createChange(new NullProgressMonitor()); + if (change == null) { + return null; + } + WorkspaceEdit edit = ChangeUtil.convertToWorkspaceEdit(change); + return new RefactorWorkspaceEdit(edit, null); + } + } } if (proposal == null) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/text/correction/RefactorProposalUtility.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/text/correction/RefactorProposalUtility.java index 86cd52c95f..2d7140e7fb 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/text/correction/RefactorProposalUtility.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/text/correction/RefactorProposalUtility.java @@ -24,6 +24,7 @@ import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaModelMarker; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; @@ -55,8 +56,11 @@ import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.dom.Bindings; import org.eclipse.jdt.internal.corext.fix.LinkedProposalModelCore; +import org.eclipse.jdt.internal.corext.refactoring.ExceptionInfo; +import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo; import org.eclipse.jdt.internal.corext.refactoring.RefactoringAvailabilityTesterCore; import org.eclipse.jdt.internal.corext.refactoring.code.PromoteTempToFieldRefactoring; +import org.eclipse.jdt.internal.corext.refactoring.structure.ChangeSignatureProcessor; import org.eclipse.jdt.internal.corext.refactoring.structure.ExtractInterfaceProcessor; import org.eclipse.jdt.internal.corext.util.JdtFlags; import org.eclipse.jdt.internal.ui.text.correction.IProblemLocationCore; @@ -77,6 +81,8 @@ import org.eclipse.jdt.ls.core.internal.corrections.proposals.ChangeCorrectionProposal; import org.eclipse.jdt.ls.core.internal.corrections.proposals.IProposalRelevance; import org.eclipse.jdt.ls.core.internal.corrections.proposals.RefactoringCorrectionProposal; +import org.eclipse.jdt.ls.core.internal.handlers.ChangeSignatureHandler.MethodException; +import org.eclipse.jdt.ls.core.internal.handlers.ChangeSignatureHandler.MethodParameter; import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; import org.eclipse.lsp4j.CodeActionKind; import org.eclipse.lsp4j.CodeActionParams; @@ -91,6 +97,7 @@ public class RefactorProposalUtility { public static final String EXTRACT_METHOD_COMMAND = "extractMethod"; public static final String EXTRACT_FIELD_COMMAND = "extractField"; public static final String EXTRACT_INTERFACE_COMMAND = "extractInterface"; + public static final String CHANGE_SIGNATURE_COMMAND = "changeSignature"; public static final String ASSIGN_FIELD_COMMAND = "assignField"; public static final String CONVERT_VARIABLE_TO_FIELD_COMMAND = "convertVariableToField"; public static final String MOVE_FILE_COMMAND = "moveFile"; @@ -842,6 +849,60 @@ public static ChangeCorrectionProposal getExtractInterfaceProposal(CodeActionPar return null; } + public static ChangeCorrectionProposal getChangeSignatureProposal(CodeActionParams params, IInvocationContext context) { + ICompilationUnit cu = context.getCompilationUnit(); + if (cu == null) { + return null; + } + ASTNode methodNode = CodeActionUtility.inferASTNode(context.getCoveringNode(), MethodDeclaration.class); + if (methodNode != null) { + IMethodBinding methodBinding = ((MethodDeclaration) methodNode).resolveBinding(); + if (methodBinding != null) { + IJavaElement element = methodBinding.getJavaElement(); + if (element instanceof IMethod method) { + try { + ChangeSignatureProcessor processor = new ChangeSignatureProcessor(method); + if (RefactoringAvailabilityTester.isChangeSignatureAvailable(method) && processor.checkInitialConditions(new NullProgressMonitor()).isOK()) { + List parameters = new ArrayList<>(); + for (ParameterInfo info : processor.getParameterInfos()) { + parameters.add(new MethodParameter(info.getOldTypeName(), info.getOldName(), info.getDefaultValue() == null ? "null" : info.getDefaultValue(), info.getOldIndex())); + } + List exceptions = new ArrayList<>(); + for (ExceptionInfo info : processor.getExceptionInfos()) { + exceptions.add(new MethodException(info.getFullyQualifiedName(), info.getElement().getHandleIdentifier())); + } + ChangeSignatureInfo info = new ChangeSignatureInfo(method.getHandleIdentifier(), JdtFlags.getVisibilityString(processor.getVisibility()), processor.getReturnTypeString(), method.getElementName(), parameters.toArray(MethodParameter[]::new), exceptions.toArray(MethodException[]::new)); + String label = Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_change_signature_for, new String[]{ method.getElementName() }); + return new CUCorrectionCommandProposal(label, JavaCodeActionKind.REFACTOR_CHANGE_SIGNATURE, cu, IProposalRelevance.CHANGE_METHOD_SIGNATURE, RefactorProposalUtility.APPLY_REFACTORING_COMMAND_ID, Arrays.asList(RefactorProposalUtility.CHANGE_SIGNATURE_COMMAND, params, info)); + } + } catch (CoreException e) { + // Do nothing + } + } + } + } + return null; + } + + public static class ChangeSignatureInfo { + + public String methodIdentifier; + public String accessType; + public String returnType; + public String methodName; + public MethodParameter[] parameters; + public MethodException[] exceptions; + + public ChangeSignatureInfo(String methodIdentifier, String accessType, String returnType, String methodName, MethodParameter[] parameters, MethodException[] exceptions) { + this.methodIdentifier = methodIdentifier; + this.accessType = accessType; + this.returnType = returnType; + this.methodName = methodName; + this.parameters = parameters; + this.exceptions = exceptions; + } + } + public static String getUniqueMethodName(ASTNode astNode, String suggestedName) throws JavaModelException { while (astNode != null && !(astNode instanceof TypeDeclaration || astNode instanceof AnonymousClassDeclaration)) { astNode = astNode.getParent(); diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ChangeSignatureHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ChangeSignatureHandlerTest.java new file mode 100644 index 0000000000..0c9ff9423d --- /dev/null +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/ChangeSignatureHandlerTest.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2023 Microsoft Corporation and others. + * All rights reserved. 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: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.jdt.ls.core.internal.handlers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.corext.util.JdtFlags; +import org.eclipse.jdt.ls.core.internal.CodeActionUtil; +import org.eclipse.jdt.ls.core.internal.JavaClientConnection; +import org.eclipse.jdt.ls.core.internal.JavaCodeActionKind; +import org.eclipse.jdt.ls.core.internal.LanguageServerWorkingCopyOwner; +import org.eclipse.jdt.ls.core.internal.corext.refactoring.ParameterInfo; +import org.eclipse.jdt.ls.core.internal.handlers.ChangeSignatureHandler.MethodException; +import org.eclipse.jdt.ls.core.internal.handlers.ChangeSignatureHandler.MethodParameter; +import org.eclipse.jdt.ls.core.internal.text.correction.RefactorProposalUtility; +import org.eclipse.jdt.ls.core.internal.text.correction.RefactorProposalUtility.ChangeSignatureInfo; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionParams; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ChangeSignatureHandlerTest extends AbstractCompilationUnitBasedTest { + + @Mock + private JavaClientConnection connection; + private IJavaProject fJavaProject; + private IPackageFragmentRoot fRoot; + private IPackageFragment fPackageP; + + @Override + @Before + public void setup() throws Exception { + fJavaProject = newEmptyProject(); + fRoot = fJavaProject.findPackageFragmentRoot(fJavaProject.getPath().append("src")); + assertNotNull(fRoot); + fPackageP = fRoot.createPackageFragment("p", true, null); + wcOwner = new LanguageServerWorkingCopyOwner(connection); + server = new JDTLanguageServer(projectsManager, this.preferenceManager); + } + + @Test + public void testChangeSignatureRefactoringExists() throws JavaModelException { + //@formatter:off + ICompilationUnit unit = fPackageP.createCompilationUnit("A.java", "package p;\r\n" + + "\r\n" + + "public class A {\r\n" + + " public void getName(String input) {\r\n" + + " }\r\n" + + "}" + , true, null); + //@formatter:on + CodeActionParams params = CodeActionUtil.constructCodeActionParams(unit, "getName"); + List> codeActions = server.codeAction(params).join(); + Assert.assertNotNull(codeActions); + Either changeSignatureAction = CodeActionHandlerTest.findAction(codeActions, JavaCodeActionKind.REFACTOR_CHANGE_SIGNATURE); + Assert.assertNotNull(changeSignatureAction); + Command changeSignatureCommand = CodeActionHandlerTest.getCommand(changeSignatureAction); + Assert.assertNotNull(changeSignatureCommand); + Assert.assertEquals(RefactorProposalUtility.APPLY_REFACTORING_COMMAND_ID, changeSignatureCommand.getCommand()); + List arguments = changeSignatureCommand.getArguments(); + Assert.assertEquals(3, arguments.size()); + Object arg0 = arguments.get(0); + assertEquals(true, arg0 instanceof String); + assertEquals("changeSignature", (String) arg0); + Object arg1 = arguments.get(1); + assertEquals(true, arg1 instanceof CodeActionParams); + Object arg2 = arguments.get(2); + assertEquals(true, arg2 instanceof ChangeSignatureInfo); + ChangeSignatureInfo info = (ChangeSignatureInfo) arg2; + assertEquals("public", info.accessType); + assertEquals(0, info.exceptions.length); + assertEquals("=TestProject/src> codeActions = server.codeAction(params).join(); + Assert.assertNotNull(codeActions); + Either changeSignatureAction = CodeActionHandlerTest.findAction(codeActions, JavaCodeActionKind.REFACTOR_CHANGE_SIGNATURE); + Assert.assertNotNull(changeSignatureAction); + Command changeSignatureCommand = CodeActionHandlerTest.getCommand(changeSignatureAction); + Assert.assertNotNull(changeSignatureCommand); + Assert.assertEquals(RefactorProposalUtility.APPLY_REFACTORING_COMMAND_ID, changeSignatureCommand.getCommand()); + List arguments = changeSignatureCommand.getArguments(); + Assert.assertEquals(3, arguments.size()); + Object arg1 = arguments.get(1); + assertEquals(true, arg1 instanceof CodeActionParams); + Object arg2 = arguments.get(2); + assertEquals(true, arg2 instanceof ChangeSignatureInfo); + ChangeSignatureInfo info = (ChangeSignatureInfo) arg2; + IJavaElement element = JavaCore.create(info.methodIdentifier); + assertEquals(true, element instanceof IMethod); + List parameters = List.of(info.parameters[0], new MethodParameter("String", "input1", "null", ParameterInfo.INDEX_FOR_ADDED)); + List exceptions = List.of(new MethodException("IOException", null)); + Refactoring refactoring = ChangeSignatureHandler.getChangeSignatureRefactoring((CodeActionParams) arg1, (IMethod) element, false, "getName1", JdtFlags.VISIBILITY_STRING_PRIVATE, "String", parameters, exceptions); + Change change = refactoring.createChange(new NullProgressMonitor()); + change.perform(new NullProgressMonitor()); + //@formatter:off + String expected = "package p;\r\n" + + "\r\n" + + "import java.io.IOException;\r\n" + + "\r\n" + + "public class A {\r\n" + + " private String getName1(String input, String input1) throws IOException {\r\n" + + " }\r\n" + + "}"; + //@formatter:on + assertEquals(expected, unit.getSource()); + } +}