| /* |
| * 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.doctree.DocTree; |
| import com.sun.source.doctree.ReferenceTree; |
| import com.sun.source.tree.*; |
| import com.sun.source.util.DocTreePath; |
| import com.sun.source.util.DocTrees; |
| import com.sun.source.util.TreePath; |
| import java.io.IOException; |
| import java.util.*; |
| import javax.lang.model.element.*; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.WorkingCopy; |
| import org.netbeans.modules.refactoring.api.Problem; |
| import org.netbeans.modules.refactoring.java.RefactoringUtils; |
| import org.netbeans.modules.refactoring.java.SourceUtilsEx; |
| import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils; |
| import org.netbeans.modules.refactoring.java.spi.RefactoringVisitor; |
| import org.netbeans.modules.refactoring.java.spi.ToPhaseException; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| |
| /** |
| * |
| * @author Jan Becicka |
| */ |
| public class MoveTransformer extends RefactoringVisitor { |
| |
| private FileObject originalFolder; |
| private MoveFileRefactoringPlugin move; |
| private Set<Element> elementsToImport; |
| private Set<ImportTree> importToRemove; |
| private Set<String> importToAdd; |
| private boolean isThisFileMoving; |
| private boolean isThisFileReferencingOldPackage = false; |
| private Problem problem; |
| private boolean moveToDefaulPackageProblem = false; |
| private String originalPackage; |
| private SourceUtilsEx.Cache cacheOfSrcFiles = new SourceUtilsEx.Cache(); |
| private final Set<ElementHandle<TypeElement>> classes2Move; |
| |
| public Problem getProblem() { |
| return problem; |
| } |
| |
| public MoveTransformer(MoveFileRefactoringPlugin move) { |
| super(true); |
| this.move = move; |
| classes2Move = move.classes; |
| } |
| |
| @Override |
| public void setWorkingCopy(WorkingCopy copy) throws ToPhaseException { |
| super.setWorkingCopy(copy); |
| originalFolder = workingCopy.getFileObject().getParent(); |
| originalPackage = RefactoringUtils.getPackageName(originalFolder); |
| isThisFileMoving = move.filesToMove.contains(workingCopy.getFileObject()); |
| elementsToImport = new HashSet<Element>(); |
| isThisFileReferencingOldPackage = false; |
| importToRemove = new HashSet<ImportTree>(); |
| importToAdd = new HashSet<String>(); |
| } |
| |
| @Override |
| public Tree visitMemberSelect(MemberSelectTree node, Element p) { |
| if (!JavaPluginUtils.isSyntheticPath(workingCopy, getCurrentPath())) { |
| final Element el = workingCopy.getTrees().getElement(getCurrentPath()); |
| if (el != null) { |
| if (isElementMoving(el)) { |
| //elementsAlreadyImported.add(el); |
| String newPackageName = getTargetPackageName(el); |
| |
| if (!"".equals(newPackageName)) { // |
| Tree nju = make.MemberSelect(make.Identifier(newPackageName), el); |
| rewrite(node, nju); |
| } else { |
| if (!moveToDefaulPackageProblem) { |
| problem = createProblem(problem, false, NbBundle.getMessage(MoveTransformer.class, "ERR_MovingClassToDefaultPackage")); |
| moveToDefaulPackageProblem = true; |
| } |
| } |
| } else { |
| if (isThisFileMoving) { |
| if (el.getKind() != ElementKind.PACKAGE) { |
| Element enclosingTypeElement = workingCopy.getElementUtilities().enclosingTypeElement(el); |
| |
| EnumSet<Modifier> neededMods = EnumSet.of(Modifier.PUBLIC); |
| TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, getCurrentPath(), true, true, true, true, false); |
| if(enclosingClassPath != null) { |
| Element enclosingClass = workingCopy.getTrees().getElement(enclosingClassPath); |
| if(enclosingTypeElement != null && enclosingClass != null |
| && workingCopy.getTypes().isSubtype(enclosingClass.asType(), enclosingTypeElement.asType())) { |
| neededMods = EnumSet.of(Modifier.PUBLIC, Modifier.PROTECTED); |
| } |
| } |
| |
| if(getPackageOf(el).toString().equals(originalPackage) |
| && (!(containsAnyOf(el, neededMods)) |
| || (enclosingTypeElement!=null? !containsAnyOf(enclosingTypeElement, neededMods) : false)) |
| && !move.filesToMove.contains(getFileObject(el))) { |
| problem = createProblem(problem, false, NbBundle.getMessage(MoveTransformer.class, "ERR_AccessesPackagePrivateFeature2",workingCopy.getFileObject().getName(),el, getTypeElement(el).getSimpleName())); |
| } |
| } |
| } else { |
| if (el.getKind()!=ElementKind.PACKAGE && el.getKind() != ElementKind.MODULE) { |
| Element enclosingTypeElement = workingCopy.getElementUtilities().enclosingTypeElement(el); |
| |
| EnumSet<Modifier> neededMods = EnumSet.of(Modifier.PUBLIC); |
| TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, getCurrentPath(), true, true, true, true, false); |
| if(enclosingClassPath != null) { |
| Element enclosingClass = workingCopy.getTrees().getElement(enclosingClassPath); |
| if(enclosingTypeElement != null && enclosingClass != null |
| && workingCopy.getTypes().isSubtype(enclosingClass.asType(), enclosingTypeElement.asType())) { |
| neededMods = EnumSet.of(Modifier.PUBLIC, Modifier.PROTECTED); |
| } |
| } |
| |
| if(getPackageOf(el).toString().equals(originalPackage) |
| && (!(containsAnyOf(el, neededMods)) |
| || (enclosingTypeElement!=null? !containsAnyOf(enclosingTypeElement, neededMods) : false)) |
| && move.filesToMove.contains(getFileObject(el))) { |
| problem = createProblem(problem, false, NbBundle.getMessage(MoveTransformer.class, "ERR_AccessesPackagePrivateFeature",workingCopy.getFileObject().getName(),el, getTypeElement(el).getSimpleName())); |
| } |
| } |
| } |
| } |
| } else if (isPackageRename() && "*".equals(node.getIdentifier().toString())) { // NOI18N |
| ExpressionTree exprTree = node.getExpression(); |
| TreePath exprPath = workingCopy.getTrees().getPath(workingCopy.getCompilationUnit(), exprTree); |
| Element elem = workingCopy.getTrees().getElement(exprPath); |
| if (elem != null && elem.getKind() == ElementKind.PACKAGE && isThisPackageMoving((PackageElement) elem)) { |
| String newPackageName = getTargetPackageName(elem); |
| Tree nju = make.MemberSelect(make.Identifier(newPackageName), "*"); // NOI18N |
| rewrite(node, nju); |
| } |
| } |
| } |
| return super.visitMemberSelect(node, p); |
| } |
| |
| @Override |
| public DocTree visitReference(ReferenceTree node, Element p) { |
| DocTrees trees = workingCopy.getDocTrees(); |
| DocTreePath currentDocPath = getCurrentDocPath(); |
| Element el = currentDocPath != null ? trees.getElement(currentDocPath) : null; |
| if(el != null) { |
| if (isElementMoving(el)) { |
| ReferenceTree newRef; |
| ExpressionTree classReference = workingCopy.getTreeUtilities().getReferenceClass(currentDocPath); |
| if(classReference != null && classReference.getKind() == Tree.Kind.MEMBER_SELECT) { |
| Name memberName = workingCopy.getTreeUtilities().getReferenceName(currentDocPath); |
| List<? extends Tree> methodParameters = workingCopy.getTreeUtilities().getReferenceParameters(currentDocPath); |
| String newPackageName = getTargetPackageName(el); |
| MemberSelectTree nju = make.MemberSelect(make.Identifier(newPackageName), el); |
| newRef = make.Reference(nju, memberName, methodParameters); |
| rewrite(currentDocPath.getTreePath().getLeaf(), node, newRef); |
| } |
| } else if (isPackageRename() |
| && el.getKind() == ElementKind.PACKAGE |
| && ((PackageElement) el).getQualifiedName().contentEquals(originalPackage)) { |
| ReferenceTree newRef = make.Reference((ExpressionTree) make.Type(getTargetPackageName(el)),null, null); |
| rewrite(currentDocPath.getTreePath().getLeaf(), node, newRef); |
| } |
| } |
| return super.visitReference(node, p); |
| } |
| |
| @Override |
| public Tree visitIdentifier(IdentifierTree node, Element p) { |
| if (!JavaPluginUtils.isSyntheticPath(workingCopy, getCurrentPath())) { |
| Element el = workingCopy.getTrees().getElement(getCurrentPath()); |
| if (el != null) { |
| if (!isThisFileMoving) { |
| if (isElementMoving(el)) { |
| String targetPackageName = getTargetPackageName(el); |
| if (!RefactoringUtils.getPackageName(workingCopy.getCompilationUnit()).equals(targetPackageName)) { |
| elementsToImport.add(el); |
| } |
| } else if (el.getKind() != ElementKind.PACKAGE && el.getKind() != ElementKind.MODULE) { |
| Element enclosingTypeElement = workingCopy.getElementUtilities().enclosingTypeElement(el); |
| |
| EnumSet<Modifier> neededMods = EnumSet.of(Modifier.PUBLIC); |
| TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, getCurrentPath(), true, true, true, true, false); |
| if(enclosingClassPath != null) { |
| Element enclosingClass = workingCopy.getTrees().getElement(enclosingClassPath); |
| if(enclosingTypeElement != null && enclosingClass != null |
| && workingCopy.getTypes().isSubtype(enclosingClass.asType(), enclosingTypeElement.asType())) { |
| neededMods = EnumSet.of(Modifier.PUBLIC, Modifier.PROTECTED); |
| } |
| } |
| |
| if (getPackageOf(el).toString().equals(originalPackage) |
| && (!(containsAnyOf(el, neededMods)) |
| || (enclosingTypeElement!=null? !containsAnyOf(enclosingTypeElement, neededMods) : false)) |
| && move.filesToMove.contains(getFileObject(el))) { |
| problem = createProblem(problem, false, NbBundle.getMessage(MoveTransformer.class, "ERR_AccessesPackagePrivateFeature", workingCopy.getFileObject().getName(), el, getTypeElement(el).getSimpleName())); |
| } |
| } |
| } else { |
| Boolean[] isElementMoving = new Boolean[1]; |
| if (isTopLevelClass(el) && !isElementMoving(el, isElementMoving) |
| && getPackageOf(el).toString().equals(originalPackage)) { |
| importToAdd.add(el.toString()); |
| isThisFileReferencingOldPackage = true; |
| } |
| if (el.getKind() != ElementKind.PACKAGE) { |
| Element enclosingTypeElement = workingCopy.getElementUtilities().enclosingTypeElement(el); |
| |
| EnumSet<Modifier> neededMods = EnumSet.of(Modifier.PUBLIC); |
| TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, getCurrentPath(), true, true, true, true, false); |
| if (enclosingClassPath != null) { |
| Element enclosingClass = workingCopy.getTrees().getElement(enclosingClassPath); |
| if (enclosingTypeElement != null && enclosingClass != null |
| && workingCopy.getTypes().isSubtype(enclosingClass.asType(), enclosingTypeElement.asType())) { |
| neededMods = EnumSet.of(Modifier.PUBLIC, Modifier.PROTECTED); |
| } |
| } |
| |
| if (getPackageOf(el).toString().equals(originalPackage) |
| && (!(containsAnyOf(el, neededMods)) |
| || (enclosingTypeElement!=null? !containsAnyOf(enclosingTypeElement, neededMods) : false)) |
| && !isElementMoving(el, isElementMoving) |
| && !move.filesToMove.contains(getFileObject(el))) { |
| problem = createProblem(problem, false, NbBundle.getMessage(MoveTransformer.class, "ERR_AccessesPackagePrivateFeature2", workingCopy.getFileObject().getName(), el, getTypeElement(el).getSimpleName())); |
| } |
| } |
| } |
| } |
| } |
| |
| return super.visitIdentifier(node, p); |
| } |
| |
| private FileObject getFileObject(Element el) { |
| return SourceUtilsEx.getFile(el, workingCopy.getClasspathInfo(), cacheOfSrcFiles); |
| } |
| |
| private boolean isThisPackageMoving(PackageElement el) { |
| return move.packageNames.contains(el.getQualifiedName().toString()); |
| } |
| |
| private String getTargetPackageName(Element el) { |
| return move.getTargetPackageName(getFileObject(el)); |
| } |
| |
| private TypeElement getTypeElement(Element e) { |
| TypeElement t = workingCopy.getElementUtilities().enclosingTypeElement(e); |
| if (t==null && (e.getKind().isClass() || e.getKind().isInterface())) { |
| return (TypeElement) e; |
| } |
| return t; |
| } |
| static final Problem createProblem(Problem result, boolean isFatal, String message) { |
| Problem problem = new Problem(isFatal, message); |
| if (result == null) { |
| return problem; |
| } |
| problem.setNext(result); |
| return problem; |
| } |
| |
| |
| private PackageElement getPackageOf(Element el) { |
| //return workingCopy.getElements().getPackageOf(el); |
| while (el.getKind() != ElementKind.PACKAGE) { |
| el = el.getEnclosingElement(); |
| } |
| return (PackageElement) el; |
| } |
| |
| private boolean isPackageRename() { |
| return move.isRenameRefactoring; |
| } |
| |
| private boolean isElementMoving(Element el, Boolean[] cache) { |
| if (cache[0] == null) { |
| cache[0] = isElementMoving(el); |
| } |
| |
| return cache[0]; |
| } |
| |
| private boolean isElementMoving(Element el) { |
| ElementKind kind = el.getKind(); |
| if (!(kind.isClass() || kind.isInterface())) { |
| return false; |
| } |
| ElementHandle<Element> elHandle = ElementHandle.create(el); |
| return classes2Move.contains(elHandle); |
| } |
| |
| private boolean isTopLevelClass(Element el) { |
| return (el.getKind().isClass() || |
| el.getKind().isInterface()) && |
| el.getEnclosingElement().getKind() == ElementKind.PACKAGE; |
| } |
| |
| @Override |
| public Tree visitCompilationUnit(CompilationUnitTree node, Element p) { |
| Tree result = super.visitCompilationUnit(node, p); |
| if (workingCopy.getTreeUtilities().isSynthetic(getCurrentPath())) { |
| return result; |
| } |
| CompilationUnitTree cut = node; |
| List<? extends ImportTree> imports = cut.getImports(); |
| if (!importToRemove.isEmpty()) { |
| List<ImportTree> temp = new ArrayList<ImportTree>(imports); |
| temp.removeAll(importToRemove); |
| imports = temp; |
| } |
| if (isThisFileMoving) { |
| // change package statement if old and new package exist, i.e. |
| // neither old nor new package is default |
| String newPckg = move.getTargetPackageName(workingCopy.getFileObject()); |
| if (node.getPackageName() != null && !"".equals(newPckg)) { |
| if (importToRemove.isEmpty()) { |
| rewrite(node.getPackageName(), make.Identifier(newPckg)); |
| } else { |
| cut = make.CompilationUnit(node.getPackageAnnotations(), make.Identifier(newPckg), imports, node.getTypeDecls(), node.getSourceFile()); |
| } |
| } else { |
| // in order to handle default package, we have to rewrite whole |
| // compilation unit: |
| cut = make.CompilationUnit( |
| node.getPackageAnnotations(), |
| "".equals(newPckg) ? null : make.Identifier(newPckg), |
| imports, |
| node.getTypeDecls(), |
| node.getSourceFile() |
| ); |
| } |
| if (isThisFileReferencingOldPackage) { |
| //add import to old package |
| ExpressionTree newPackageName = cut.getPackageName(); |
| if (newPackageName != null) { |
| try { |
| cut = RefactoringUtils.addImports(cut, new LinkedList<String>(importToAdd), make); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } else { |
| if (!moveToDefaulPackageProblem) { |
| problem = createProblem(problem, false, NbBundle.getMessage(MoveTransformer.class, "ERR_MovingClassToDefaultPackage")); |
| moveToDefaulPackageProblem = true; |
| } |
| } |
| |
| } |
| } else if (!importToRemove.isEmpty()) { |
| cut = make.CompilationUnit(node.getPackageName(), imports, node.getTypeDecls(), node.getSourceFile()); |
| } |
| |
| for (Element el:elementsToImport) { |
| String newPackageName = getTargetPackageName(el); |
| if (!"".equals(newPackageName)) { // NOI18N |
| cut = insertImport(cut, newPackageName + "." +el.getSimpleName(), el, newPackageName); // NOI18N |
| } |
| } |
| rewrite(node, cut); |
| return result; |
| } |
| |
| private CompilationUnitTree insertImport(CompilationUnitTree node, String imp, Element orig, String targetPkgOfOrig) { |
| for (ImportTree tree: node.getImports()) { |
| if (tree.getQualifiedIdentifier().toString().equals(imp) || tree.getQualifiedIdentifier().toString().equals(((TypeElement) orig).getQualifiedName().toString())) { |
| return node; |
| } |
| if (orig!=null) { |
| if (tree.getQualifiedIdentifier().toString().equals(getPackageOf(orig).getQualifiedName()+".*") && isPackageRename()) { // NOI18N |
| rewrite(tree.getQualifiedIdentifier(), make.Identifier(targetPkgOfOrig + ".*")); // NOI18N |
| return node; |
| } |
| } |
| } |
| CompilationUnitTree nju = make.insertCompUnitImport(node, 0, make.Import(make.Identifier(imp), false)); |
| return nju; |
| } |
| |
| @Override |
| public Tree visitImport(ImportTree node, Element p) { |
| if (!workingCopy.getTreeUtilities().isSynthetic(getCurrentPath())) { |
| Tree qualifiedIdentifier = node.getQualifiedIdentifier(); |
| final Element el = workingCopy.getTrees().getElement(new TreePath(getCurrentPath(), node.getQualifiedIdentifier())); |
| if (el != null) { |
| if (isElementMoving(el)) { |
| String newPackageName = getTargetPackageName(el); |
| |
| if (!"".equals(newPackageName)) { |
| String cuPackageName = RefactoringUtils.getPackageName(workingCopy.getCompilationUnit()); |
| if (cuPackageName.equals(newPackageName)) { //remove newly created import from same package |
| importToRemove.add(node); |
| return node; |
| } |
| } |
| } |
| } else if(qualifiedIdentifier.getKind() == Tree.Kind.MEMBER_SELECT) { |
| MemberSelectTree memberSelect = (MemberSelectTree) qualifiedIdentifier; |
| if(memberSelect.getIdentifier().contentEquals("*")) { |
| Element packageElement = workingCopy.getTrees().getElement(new TreePath(getCurrentPath(), memberSelect.getExpression())); |
| if(packageElement != null && packageElement.getKind() == ElementKind.PACKAGE) { |
| PackageElement pakketje = (PackageElement) packageElement; |
| if(isThisPackageMoving(pakketje)) { |
| importToRemove.add(node); |
| } else if(move.packages.contains(ElementHandle.create(pakketje))) { |
| boolean packageWillBeEmpty = true; |
| List<? extends Element> enclosedElements = pakketje.getEnclosedElements(); |
| for (Element element : enclosedElements) { |
| if(!isElementMoving(element)) { |
| packageWillBeEmpty = false; |
| break; |
| } else { |
| String targetPackageName = getTargetPackageName(element); |
| if(pakketje.getQualifiedName().contentEquals(targetPackageName)) { |
| packageWillBeEmpty = false; |
| break; |
| } |
| } |
| } |
| if(packageWillBeEmpty) { |
| importToRemove.add(node); |
| } |
| } |
| } |
| } |
| } |
| } |
| return super.visitImport(node, p); |
| } |
| |
| @Override |
| public Tree visitExports(ExportsTree node, Element p) { |
| if (!workingCopy.getTreeUtilities().isSynthetic(getCurrentPath())) { |
| final Element el = workingCopy.getTrees().getElement(new TreePath(getCurrentPath(), node.getPackageName())); |
| if (el != null && el.getKind() == ElementKind.PACKAGE && isThisPackageMoving((PackageElement)el)) { |
| Tree nju = make.Identifier(getTargetPackageName(el)); |
| rewrite(node.getPackageName(), nju); |
| } |
| } |
| return super.visitExports(node, p); |
| } |
| |
| private boolean containsAnyOf(Element el, EnumSet<Modifier> neededMods) { |
| for (Modifier mod : neededMods) { |
| if(el.getModifiers().contains(mod)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } |