blob: a98bf1d9e64465f870489b4993d36e83bab6d9dc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.refactoring.java.plugins;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.source.*;
import org.netbeans.api.java.source.ClasspathInfo.PathKind;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.modules.refactoring.api.AbstractRefactoring;
import org.netbeans.modules.refactoring.api.MoveRefactoring;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.api.ProgressEvent;
import org.netbeans.modules.refactoring.java.RefactoringUtils;
import org.netbeans.modules.refactoring.java.api.JavaMoveMembersProperties;
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
import org.netbeans.modules.refactoring.java.spi.JavaRefactoringPlugin;
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.URLMapper;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
/**
* Implemented abilities: <ul> <li>Move field(s)</li> <li>Move method(s)</li>
* </ul>
*
* @author Ralph Ruijs
*/
@NbBundle.Messages({"ERR_NothingSelected=Nothing selected to move",
"ERR_MoveToLibrary=Cannot move to a library",
"ERR_MoveFromLibrary=Cannot move from a library",
"ERR_MoveFromClass=Can only move members of a class",
"ERR_MoveToSameClass=Target can not be the same as the source class",
"ERR_MoveToSuperClass=Cannot move to a superclass, maybe you need the Pull Up Refactoring?",
"ERR_MoveToSubClass=Cannot move to a subclass, maybe you need the Push Down Refactoring?",
"ERR_MoveGenericField=Cannot move a generic field",
"# {0} - Method name",
"ERR_MoveAbstractMember=Cannot move abstract method \"{0}\"",
"# {0} - Method name",
"ERR_MoveMethodPolymorphic=Cannot move polymorphic method \"{0}\"",
"WRN_InitNoAccess=Field initializer uses local accessors which will not be accessible",
"# {0} - File displayname : line number",
"WRN_NoAccessor=No accessor found to invoke the method from: {0}",
"TXT_DelegatingMethod=Delegating method"})
public class MoveMembersRefactoringPlugin extends JavaRefactoringPlugin {
private final MoveRefactoring refactoring;
private final JavaMoveMembersProperties properties;
public MoveMembersRefactoringPlugin(MoveRefactoring moveRefactoring) {
this.refactoring = moveRefactoring;
this.properties = moveRefactoring.getContext().lookup(JavaMoveMembersProperties.class);
}
@Override
protected JavaSource getJavaSource(Phase p) {
TreePathHandle source;
source = properties.getPreSelectedMembers()[0];
if(source != null && source.getFileObject() != null) {
switch(p) {
case CHECKPARAMETERS:
case FASTCHECKPARAMETERS:
case PRECHECK:
case PREPARE:
ClasspathInfo cpInfo = getClasspathInfo(refactoring);
return JavaSource.create(cpInfo, source.getFileObject());
}
}
return null;
}
@Override
protected ClasspathInfo getClasspathInfo(AbstractRefactoring refactoring) {
List<TreePathHandle> handles = new ArrayList<TreePathHandle>(refactoring.getRefactoringSource().lookupAll(TreePathHandle.class));
Lookup targetLookup = this.refactoring.getTarget();
if(targetLookup != null) {
TreePathHandle target = targetLookup.lookup(TreePathHandle.class);
if(target != null) {
handles.add(target);
}
}
ClasspathInfo cpInfo;
if (!handles.isEmpty()) {
cpInfo = RefactoringUtils.getClasspathInfoFor(handles.toArray(new TreePathHandle[handles.size()]));
} else {
cpInfo = JavaRefactoringUtils.getClasspathInfoFor((FileObject)properties.getPreSelectedMembers()[0].getFileObject());
}
refactoring.getContext().add(cpInfo);
return cpInfo;
}
@Override
protected Problem preCheck(CompilationController info) throws IOException {
info.toPhase(JavaSource.Phase.RESOLVED);
Problem preCheckProblem = isElementAvail(properties.getPreSelectedMembers()[0], info);
if (preCheckProblem != null) {
return preCheckProblem;
}
Element element = properties.getPreSelectedMembers()[0].resolveElement(info);
TreePath path = info.getTrees().getPath(element);
if (path != null) {
TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(info, path, true, true, false, true, false);
if (enclosingClassPath != null) {
Element typeElement = info.getTrees().getElement(enclosingClassPath);
if (typeElement == null || !typeElement.getKind().isClass() ||
enclosingClassPath.getLeaf().getKind() == Tree.Kind.INTERFACE ||
typeElement.getKind() == ElementKind.ENUM) {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveFromClass"));
}
} else {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveFromClass"));
}
} else {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveFromClass"));
}
return preCheckProblem;
}
@Override
protected Problem checkParameters(CompilationController javac) throws IOException {
javac.toPhase(JavaSource.Phase.RESOLVED);
// TODO source method is using something not available at target
// TODO source using generics not available at target
// TODO Check if member is static but target is non static inner
// TODO Check if target is in <default> package but source is not
return null;
}
@Override
protected Problem fastCheckParameters(CompilationController javac) throws IOException {
javac.toPhase(JavaSource.Phase.RESOLVED);
Collection<? extends TreePathHandle> source = refactoring.getRefactoringSource().lookupAll(TreePathHandle.class);
if (source.isEmpty()) { // [f] nothing is selected
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_NothingSelected")); //NOI18N
}
Lookup targetLookup = refactoring.getTarget();
TreePathHandle target;
if(targetLookup == null || (target = targetLookup.lookup(TreePathHandle.class)) == null) {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_NoTarget")); //NOI18N
}
if (target.getFileObject() == null || !JavaRefactoringUtils.isOnSourceClasspath(target.getFileObject())) { // [f] target is not on source classpath
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveToLibrary")); //NOI18N
}
TreePathHandle sourceTph = source.iterator().next();
if (sourceTph.getFileObject() == null || !JavaRefactoringUtils.isOnSourceClasspath(sourceTph.getFileObject())) { // [f] source is not on source classpath
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveFromLibrary")); //NOI18N
}
for (TreePathHandle treePathHandle : source) {
Element element = treePathHandle.resolveElement(javac);
if(element.getKind() == ElementKind.FIELD) {
VariableElement var = (VariableElement) element;
if(var.asType().getKind() == TypeKind.TYPEVAR) {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveGenericField"));
}
}
if(element.getKind() == ElementKind.METHOD) {
ExecutableElement method = (ExecutableElement) element;
if(method.getModifiers().contains(Modifier.ABSTRACT)) {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveAbstractMember", element.getSimpleName()));
}
// Method can not be polymorphic
Collection<ExecutableElement> overridenMethods = JavaRefactoringUtils.getOverriddenMethods(method, javac);
Collection<ExecutableElement> overridingMethods = JavaRefactoringUtils.getOverridingMethods(method, javac, cancelRequested);
if (overridenMethods.size() > 0 || overridingMethods.size() > 0) {
return new Problem(true, NbBundle.getMessage(InlineRefactoringPlugin.class, "ERR_MoveMethodPolymorphic", method.getSimpleName())); //NOI18N
}
}
}
Element targetElement = target.resolveElement(javac);
Element targetClass = targetElement;
if(targetClass == null) {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_TargetNotResolved"));
}
while (targetClass != null && !targetClass.getKind().isClass() && !targetClass.getKind().isInterface()) {
targetClass = targetClass.getEnclosingElement();
}
if(targetClass == null) {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_TargetNotResolved"));
}
TypeMirror targetType = targetClass.asType();
if(targetType == null) {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_TargetNotResolved"));
}
Problem p = checkProjectDeps(sourceTph.getFileObject(), target.getFileObject());
if(p != null) {
return p;
}
TreePath sourceClass = JavaRefactoringUtils.findEnclosingClass(javac, sourceTph.resolve(javac), true, true, false, false, false);
TypeMirror sourceType = javac.getTrees().getTypeMirror(sourceClass);
if (sourceType.equals(targetType)) { // [f] target is the same as source
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveToSameClass")); //NOI18N
}
if (javac.getTypes().isSubtype(sourceType, targetType)) { // [f] target is a superclass of source
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveToSuperClass")); //NOI18N
}
if (javac.getTypes().isSubtype(targetType, sourceType)) { // [f] target is a subclass of source
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MoveToSubClass")); //NOI18N
}
PackageElement targetPackage = (PackageElement) javac.getElementUtilities().outermostTypeElement(targetElement).getEnclosingElement();
Element sourceElement = sourceTph.resolveElement(javac);
PackageElement sourcePackage = (PackageElement) javac.getElementUtilities().outermostTypeElement(sourceElement).getEnclosingElement();
if(targetPackage.isUnnamed() && !sourcePackage.isUnnamed()) {
return new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MovingMemberToDefaultPackage")); //NOI18N
}
for (TreePathHandle treePathHandle : source) {
Element element = treePathHandle.resolveElement(javac);
List<? extends Element> enclosedElements = targetElement.getEnclosedElements();
switch(element.getKind()) {
case FIELD:
enclosedElements = ElementFilter.fieldsIn(enclosedElements);
break;
case METHOD:
enclosedElements = ElementFilter.methodsIn(enclosedElements);
break;
case CONSTRUCTOR:
enclosedElements = ElementFilter.constructorsIn(enclosedElements);
break;
default:
enclosedElements = ElementFilter.typesIn(enclosedElements);
break;
}
for (Element member : enclosedElements) {
if(element.getSimpleName().contentEquals(member.getSimpleName())) {
Problem p2;
if(member.getKind() != ElementKind.METHOD) {
p = JavaPluginUtils.chainProblems(p, new Problem(true, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_PullUp_MemberAlreadyExists", element.getSimpleName())));
} else if((p2 = compareMethodSignatures((ExecutableElement)element, (ExecutableElement)member, targetElement, javac)) != null) {
p = JavaPluginUtils.chainProblems(p, p2);
}
}
}
}
return p;
}
private Set<FileObject> getRelevantFiles() {
final Set<FileObject> set = new LinkedHashSet<FileObject>();
ClasspathInfo cpInfo = getClasspathInfo(refactoring);
final ClassIndex idx = cpInfo.getClassIndex();
final Collection<? extends TreePathHandle> tphs = refactoring.getRefactoringSource().lookupAll(TreePathHandle.class);
TreePathHandle target = refactoring.getTarget().lookup(TreePathHandle.class);
FileObject file = target.getFileObject();
JavaSource source = JavaPluginUtils.createSource(file, cpInfo, target);
CancellableTask<CompilationController> task = new CancellableTask<CompilationController>() {
public void cancel() {
}
public void run(CompilationController info) throws Exception {
info.toPhase(JavaSource.Phase.RESOLVED);
Set<ClassIndex.SearchScopeType> searchScopeType = new HashSet<ClassIndex.SearchScopeType>(1);
searchScopeType.add(ClassIndex.SearchScope.SOURCE);
for (TreePathHandle tph : tphs) {
set.add(tph.getFileObject());
final Element el = tph.resolveElement(info);
if (el == null) {
// Similar to #145291 from JavaWhereUsedQueryPlugin
throw new NullPointerException(String.format("#222979: Cannot resolve handle: %s\n%s", tph, info.getClasspathInfo())); // NOI18N
}
if (el.getKind() == ElementKind.METHOD) {
// get method references from index
set.addAll(idx.getResources(ElementHandle.create((TypeElement) el.getEnclosingElement()), EnumSet.of(ClassIndex.SearchKind.METHOD_REFERENCES), searchScopeType)); //?????
}
if (el.getKind().isField()) {
// get field references from index
set.addAll(idx.getResources(ElementHandle.create((TypeElement) el.getEnclosingElement()), EnumSet.of(ClassIndex.SearchKind.FIELD_REFERENCES), searchScopeType));
}
}
}
};
try {
source.runUserActionTask(task, true);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
// Make sure the target is added last. Needed for escalating visibility.
set.remove(file);
set.add(file);
return set;
}
@Override
public Problem prepare(RefactoringElementsBag refactoringElements) {
fireProgressListenerStart(AbstractRefactoring.PREPARE, -1);
Set<FileObject> relevantFiles = getRelevantFiles();
Problem p = null;
TreePathHandle targetHandle = refactoring.getTarget().lookup(TreePathHandle.class);
fireProgressListenerStep(relevantFiles.size());
MoveMembersTransformer transformer = new MoveMembersTransformer(refactoring);
TransformTask task = new TransformTask(transformer, targetHandle);
Problem prob = createAndAddElements(relevantFiles, task, refactoringElements, refactoring, getClasspathInfo(refactoring));
prob = JavaPluginUtils.chainProblems(prob, transformer.getProblem());
fireProgressListenerStop();
return prob != null ? prob : JavaPluginUtils.chainProblems(transformer.getProblem(), p);
}
@SuppressWarnings("CollectionContainsUrl")
private Problem checkProjectDeps(FileObject sourceFile, FileObject targetFile) {
Set<FileObject> sourceRoots = new HashSet<FileObject>();
ClassPath cp = ClassPath.getClassPath(sourceFile, ClassPath.SOURCE);
if (cp != null) {
FileObject root = cp.findOwnerRoot(sourceFile);
sourceRoots.add(root);
}
FileObject targetRoot = null;
ClassPath targetCp = ClassPath.getClassPath(targetFile, ClassPath.SOURCE);
if(targetCp != null) {
targetRoot = targetCp.findOwnerRoot(targetFile);
}
if(!sourceRoots.isEmpty() && targetRoot != null) {
URL targetUrl = URLMapper.findURL(targetRoot, URLMapper.EXTERNAL);
Project targetProject = FileOwnerQuery.getOwner(targetRoot);
Set<URL> deps = SourceUtils.getDependentRoots(targetUrl);
for (FileObject sourceRoot : sourceRoots) {
URL sourceUrl = URLMapper.findURL(sourceRoot, URLMapper.INTERNAL);
if (!deps.contains(sourceUrl)) {
Project sourceProject = FileOwnerQuery.getOwner(sourceRoot);
for (FileObject affected : getRelevantFiles()) {
if (FileOwnerQuery.getOwner(affected).equals(sourceProject) && !sourceProject.equals(targetProject)) {
assert sourceProject != null;
assert targetProject != null;
String sourceName = ProjectUtils.getInformation(sourceProject).getDisplayName();
String targetName = ProjectUtils.getInformation(targetProject).getDisplayName();
return new Problem(false, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "ERR_MemberMissingProjectDeps", sourceName, targetName));
}
}
}
}
}
return null;
}
private Problem compareMethodSignatures(ExecutableElement method, ExecutableElement exMethod, Element targetElement, CompilationInfo javac) {
Problem p = null;
if (!exMethod.equals(method)) {
if (exMethod.getSimpleName().equals(method.getSimpleName())
&& exMethod.getParameters().size() == method.getParameters().size()) {
boolean sameParameters = true;
boolean wideningConversion = true;
for (int j = 0; j < exMethod.getParameters().size(); j++) {
TypeMirror exType = ((VariableElement) exMethod.getParameters().get(j)).asType();
TypeMirror paramType = method.getParameters().get(j).asType();
if (!javac.getTypes().isSameType(exType, paramType)) {
sameParameters = false;
if (exType.getKind().isPrimitive() && paramType.getKind().isPrimitive()) {
/*
* byte to short, int, long, float, or double
* short to int, long, float, or double
* char to int, long, float, or double
* int to long, float, or double
* long to float or double
* float to double
*/
switch (exType.getKind()) {
case DOUBLE:
if (paramType.getKind().equals(TypeKind.FLOAT)) {
break;
}
case FLOAT:
if (paramType.getKind().equals(TypeKind.LONG)) {
break;
}
case LONG:
if (paramType.getKind().equals(TypeKind.INT)) {
break;
}
case INT:
if (paramType.getKind().equals(TypeKind.SHORT)) {
break;
}
case SHORT:
if (paramType.getKind().equals(TypeKind.BYTE)) {
break;
}
case BYTE:
wideningConversion = false;
break;
}
} else {
wideningConversion = false;
}
}
}
if (sameParameters) {
p = createProblem(p, true, NbBundle.getMessage(ChangeParametersPlugin.class, "ERR_existingMethod", exMethod.toString(), ((TypeElement)targetElement).getQualifiedName())); // NOI18N
} else if (wideningConversion) {
p = createProblem(p, false, NbBundle.getMessage(ChangeParametersPlugin.class, "WRN_wideningConversion", exMethod.toString(), ((TypeElement)targetElement).getQualifiedName())); // NOI18N
}
}
}
return p;
}
}