| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Red Hat Inc. - copied to jdt.core.manipulation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.corext.refactoring.structure; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.ASTVisitor; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| 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.ImportDeclaration; |
| import org.eclipse.jdt.core.dom.Name; |
| import org.eclipse.jdt.core.dom.NameQualifiedType; |
| import org.eclipse.jdt.core.dom.QualifiedName; |
| import org.eclipse.jdt.core.dom.QualifiedType; |
| import org.eclipse.jdt.core.dom.SimpleName; |
| import org.eclipse.jdt.core.dom.Type; |
| import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; |
| import org.eclipse.jdt.core.manipulation.ImportReferencesCollector; |
| |
| import org.eclipse.jdt.internal.corext.dom.Bindings; |
| |
| /** |
| * Removes imports that are no longer required. |
| * <p> |
| * {@link #registerRemovedNode(ASTNode)} registers nodes that got removed from the AST. |
| * Do not register nodes that are moved to a different place in the same AST. |
| * <p> |
| * If a node is removed but some parts of it are moved to a different place in the same AST, |
| * then use {@link #registerRetainedNode(ASTNode)} to keep imports for the retained nodes. |
| * <p> |
| * Additional imports that will be added to the AST need to be registered with one of the |
| * {@code register*Import*(..)} methods. Such imports have typically been created with |
| * {@link ImportRewrite#addImport(ITypeBinding)} etc. |
| */ |
| public class ImportRemover { |
| |
| private static class StaticImportData { |
| |
| private boolean fField; |
| |
| private String fMember; |
| |
| private String fQualifier; |
| |
| private StaticImportData(String qualifier, String member, boolean field) { |
| fQualifier= qualifier; |
| fMember= member; |
| fField= field; |
| } |
| } |
| |
| private final String PROPERTY_KEY= String.valueOf(System.currentTimeMillis()); |
| private final String REMOVED= "removed"; //$NON-NLS-1$ |
| private final String RETAINED= "retained"; //$NON-NLS-1$ |
| |
| private Set<String> fAddedImports= new HashSet<>(); |
| |
| private Set<StaticImportData> fAddedStaticImports= new HashSet<>(); |
| |
| private final IJavaProject fProject; |
| |
| private boolean fHasRemovedNodes; |
| |
| private List<ImportDeclaration> fInlinedStaticImports= new ArrayList<>(); |
| |
| private final CompilationUnit fRoot; |
| |
| public ImportRemover(IJavaProject project, CompilationUnit root) { |
| fProject= project; |
| fRoot= root; |
| } |
| |
| private void divideTypeRefs(List<SimpleName> importNames, List<SimpleName> staticNames, List<SimpleName> removedRefs, List<SimpleName> unremovedRefs) { |
| final List<int[]> removedStartsEnds= new ArrayList<>(); |
| fRoot.accept(new ASTVisitor(true) { |
| int fRemovingStart= -1; |
| @Override |
| public void preVisit(ASTNode node) { |
| Object property= node.getProperty(PROPERTY_KEY); |
| if (property == REMOVED) { |
| if (fRemovingStart == -1) { |
| fRemovingStart= node.getStartPosition(); |
| } else { |
| /* |
| * Bug in client code: REMOVED node should not be nested inside another REMOVED node without |
| * an intermediate RETAINED node. |
| * Drop REMOVED property to prevent problems later (premature end of REMOVED section). |
| */ |
| node.setProperty(PROPERTY_KEY, null); |
| } |
| } else if (property == RETAINED) { |
| if (fRemovingStart != -1) { |
| removedStartsEnds.add(new int[] { fRemovingStart, node.getStartPosition() }); |
| fRemovingStart= -1; |
| } else { |
| /* |
| * Bug in client code: RETAINED node should not be nested inside another RETAINED node without |
| * an intermediate REMOVED node and must have an enclosing REMOVED node. |
| * Drop RETAINED property to prevent problems later (premature restart of REMOVED section). |
| */ |
| node.setProperty(PROPERTY_KEY, null); |
| } |
| } |
| super.preVisit(node); |
| } |
| @Override |
| public void postVisit(ASTNode node) { |
| Object property= node.getProperty(PROPERTY_KEY); |
| if (property == RETAINED) { |
| int end= node.getStartPosition() + node.getLength(); |
| fRemovingStart= end; |
| } else if (property == REMOVED) { |
| if (fRemovingStart != -1) { |
| int end= node.getStartPosition() + node.getLength(); |
| removedStartsEnds.add(new int[] { fRemovingStart, end }); |
| fRemovingStart= -1; |
| } |
| } |
| super.postVisit(node); |
| } |
| }); |
| |
| for (Iterator<SimpleName> iterator= importNames.iterator(); iterator.hasNext();) { |
| SimpleName name= iterator.next(); |
| if (isInRemoved(name, removedStartsEnds)) |
| removedRefs.add(name); |
| else |
| unremovedRefs.add(name); |
| } |
| for (Iterator<SimpleName> iterator= staticNames.iterator(); iterator.hasNext();) { |
| SimpleName name= iterator.next(); |
| if (isInRemoved(name, removedStartsEnds)) |
| removedRefs.add(name); |
| else |
| unremovedRefs.add(name); |
| } |
| for (Iterator<ImportDeclaration> iterator= fInlinedStaticImports.iterator(); iterator.hasNext(); ) { |
| ImportDeclaration importDecl= iterator.next(); |
| Name name= importDecl.getName(); |
| if (name instanceof QualifiedName) |
| name= ((QualifiedName) name).getName(); |
| removedRefs.add((SimpleName) name); |
| } |
| } |
| |
| private boolean isInRemoved(SimpleName ref, List<int[]> removedStartsEnds) { |
| int start= ref.getStartPosition(); |
| int end= start + ref.getLength(); |
| for (int[] removedStartsEnd : removedStartsEnds) { |
| if (start >= removedStartsEnd[0] && end <= removedStartsEnd[1]) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public IBinding[] getImportsToRemove() { |
| ArrayList<SimpleName> importNames= new ArrayList<>(); |
| ArrayList<SimpleName> staticNames= new ArrayList<>(); |
| |
| ImportReferencesCollector.collect(fRoot, fProject, null, importNames, staticNames); |
| |
| List<SimpleName> removedRefs= new ArrayList<>(); |
| List<SimpleName> unremovedRefs= new ArrayList<>(); |
| divideTypeRefs(importNames, staticNames, removedRefs, unremovedRefs); |
| if (removedRefs.size() == 0) |
| return new IBinding[0]; |
| |
| HashMap<String, IBinding> potentialRemoves= getPotentialRemoves(removedRefs); |
| for (Iterator<SimpleName> iterator= unremovedRefs.iterator(); iterator.hasNext();) { |
| SimpleName name= iterator.next(); |
| potentialRemoves.remove(name.getIdentifier()); |
| } |
| |
| Collection<IBinding> importsToRemove= potentialRemoves.values(); |
| return importsToRemove.toArray(new IBinding[importsToRemove.size()]); |
| } |
| |
| private HashMap<String, IBinding> getPotentialRemoves(List<SimpleName> removedRefs) { |
| HashMap<String, IBinding>potentialRemoves= new HashMap<>(); |
| for (Iterator<SimpleName> iterator= removedRefs.iterator(); iterator.hasNext();) { |
| SimpleName name= iterator.next(); |
| if (fAddedImports.contains(name.getIdentifier()) || hasAddedStaticImport(name)) |
| continue; |
| IBinding binding= name.resolveBinding(); |
| if (binding != null) |
| potentialRemoves.put(name.getIdentifier(), binding); |
| } |
| return potentialRemoves; |
| } |
| |
| private boolean hasAddedStaticImport(SimpleName name) { |
| IBinding binding= name.resolveBinding(); |
| if (binding instanceof IVariableBinding) { |
| IVariableBinding variable= (IVariableBinding) binding; |
| return hasAddedStaticImport(variable.getDeclaringClass().getQualifiedName(), variable.getName(), true); |
| } else if (binding instanceof IMethodBinding) { |
| IMethodBinding method= (IMethodBinding) binding; |
| return hasAddedStaticImport(method.getDeclaringClass().getQualifiedName(), method.getName(), false); |
| } |
| return false; |
| } |
| |
| private boolean hasAddedStaticImport(String qualifier, String member, boolean field) { |
| StaticImportData data= null; |
| for (final Iterator<StaticImportData> iterator= fAddedStaticImports.iterator(); iterator.hasNext();) { |
| data= iterator.next(); |
| if (data.fQualifier.equals(qualifier) && data.fMember.equals(member) && data.fField == field) |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean hasRemovedNodes() { |
| return fHasRemovedNodes || fInlinedStaticImports.size() != 0; |
| } |
| |
| public void registerAddedImport(String typeName) { |
| int dot= typeName.lastIndexOf('.'); |
| if (dot == -1) |
| fAddedImports.add(typeName); |
| else |
| fAddedImports.add(typeName.substring(dot + 1)); |
| } |
| |
| public void registerAddedImports(Type newTypeNode) { |
| newTypeNode.accept(new ASTVisitor(true) { |
| |
| private void addName(SimpleName name) { |
| fAddedImports.add(name.getIdentifier()); |
| } |
| |
| @Override |
| public boolean visit(NameQualifiedType node) { |
| addName(node.getName()); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(QualifiedName node) { |
| addName(node.getName()); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(QualifiedType node) { |
| addName(node.getName()); |
| return false; |
| } |
| |
| @Override |
| public boolean visit(SimpleName node) { |
| addName(node); |
| return false; |
| } |
| }); |
| } |
| |
| public void registerAddedStaticImport(String qualifier, String member, boolean field) { |
| fAddedStaticImports.add(new StaticImportData(qualifier, member, field)); |
| } |
| |
| public void registerAddedStaticImport(IBinding binding) { |
| if (binding instanceof IVariableBinding) { |
| ITypeBinding declaringType= ((IVariableBinding) binding).getDeclaringClass(); |
| fAddedStaticImports.add(new StaticImportData(Bindings.getRawQualifiedName(declaringType), binding.getName(), true)); |
| |
| } else if (binding instanceof IMethodBinding) { |
| ITypeBinding declaringType= ((IMethodBinding) binding).getDeclaringClass(); |
| fAddedStaticImports.add(new StaticImportData(Bindings.getRawQualifiedName(declaringType), binding.getName(), false)); |
| |
| } else { |
| throw new IllegalArgumentException(binding.toString()); |
| } |
| } |
| |
| public void registerRemovedNode(ASTNode removed) { |
| fHasRemovedNodes= true; |
| removed.setProperty(PROPERTY_KEY, REMOVED); |
| } |
| |
| public void registerRetainedNode(ASTNode retained) { |
| retained.setProperty(PROPERTY_KEY, RETAINED); |
| } |
| |
| public void applyRemoves(ImportRewrite importRewrite) { |
| IBinding[] bindings= getImportsToRemove(); |
| for (int i= 0; i < bindings.length; i++) { |
| if (bindings[i] instanceof ITypeBinding) { |
| ITypeBinding typeBinding= (ITypeBinding) bindings[i]; |
| importRewrite.removeImport(typeBinding.getTypeDeclaration().getQualifiedName()); |
| } else if (bindings[i] instanceof IMethodBinding) { |
| IMethodBinding binding= (IMethodBinding) bindings[i]; |
| importRewrite.removeStaticImport(binding.getDeclaringClass().getQualifiedName() + '.' + binding.getName()); |
| } else if (bindings[i] instanceof IVariableBinding) { |
| IVariableBinding binding= (IVariableBinding) bindings[i]; |
| importRewrite.removeStaticImport(binding.getDeclaringClass().getQualifiedName() + '.' + binding.getName()); |
| } |
| } |
| } |
| |
| public void registerInlinedStaticImport(ImportDeclaration importDecl) { |
| fInlinedStaticImports.add(importDecl); |
| } |
| } |