1 /******************************************************************************* 2 * Copyright (c) 2000, 2018 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Red Hat Inc. - copied to jdt.core.manipulation 14 *******************************************************************************/ 15 package org.eclipse.jdt.internal.corext.refactoring.structure; 16 17 import java.util.ArrayList; 18 import java.util.Collection; 19 import java.util.HashMap; 20 import java.util.HashSet; 21 import java.util.Iterator; 22 import java.util.List; 23 import java.util.Set; 24 25 import org.eclipse.jdt.core.IJavaProject; 26 import org.eclipse.jdt.core.dom.ASTNode; 27 import org.eclipse.jdt.core.dom.ASTVisitor; 28 import org.eclipse.jdt.core.dom.CompilationUnit; 29 import org.eclipse.jdt.core.dom.IBinding; 30 import org.eclipse.jdt.core.dom.IMethodBinding; 31 import org.eclipse.jdt.core.dom.ITypeBinding; 32 import org.eclipse.jdt.core.dom.IVariableBinding; 33 import org.eclipse.jdt.core.dom.ImportDeclaration; 34 import org.eclipse.jdt.core.dom.Name; 35 import org.eclipse.jdt.core.dom.NameQualifiedType; 36 import org.eclipse.jdt.core.dom.QualifiedName; 37 import org.eclipse.jdt.core.dom.QualifiedType; 38 import org.eclipse.jdt.core.dom.SimpleName; 39 import org.eclipse.jdt.core.dom.Type; 40 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; 41 import org.eclipse.jdt.core.manipulation.ImportReferencesCollector; 42 43 import org.eclipse.jdt.internal.corext.dom.Bindings; 44 45 /** 46 * Removes imports that are no longer required. 47 * <p> 48 * {@link #registerRemovedNode(ASTNode)} registers nodes that got removed from the AST. 49 * Do not register nodes that are moved to a different place in the same AST. 50 * <p> 51 * If a node is removed but some parts of it are moved to a different place in the same AST, 52 * then use {@link #registerRetainedNode(ASTNode)} to keep imports for the retained nodes. 53 * <p> 54 * Additional imports that will be added to the AST need to be registered with one of the 55 * {@code register*Import*(..)} methods. Such imports have typically been created with 56 * {@link ImportRewrite#addImport(ITypeBinding)} etc. 57 */ 58 public class ImportRemover { 59 60 private static class StaticImportData { 61 62 private boolean fField; 63 64 private String fMember; 65 66 private String fQualifier; 67 StaticImportData(String qualifier, String member, boolean field)68 private StaticImportData(String qualifier, String member, boolean field) { 69 fQualifier= qualifier; 70 fMember= member; 71 fField= field; 72 } 73 } 74 75 private final String PROPERTY_KEY= String.valueOf(System.currentTimeMillis()); 76 private final String REMOVED= "removed"; //$NON-NLS-1$ 77 private final String RETAINED= "retained"; //$NON-NLS-1$ 78 79 private Set<String> fAddedImports= new HashSet<>(); 80 81 private Set<StaticImportData> fAddedStaticImports= new HashSet<>(); 82 83 private final IJavaProject fProject; 84 85 private boolean fHasRemovedNodes; 86 87 private List<ImportDeclaration> fInlinedStaticImports= new ArrayList<>(); 88 89 private final CompilationUnit fRoot; 90 ImportRemover(IJavaProject project, CompilationUnit root)91 public ImportRemover(IJavaProject project, CompilationUnit root) { 92 fProject= project; 93 fRoot= root; 94 } 95 divideTypeRefs(List<SimpleName> importNames, List<SimpleName> staticNames, List<SimpleName> removedRefs, List<SimpleName> unremovedRefs)96 private void divideTypeRefs(List<SimpleName> importNames, List<SimpleName> staticNames, List<SimpleName> removedRefs, List<SimpleName> unremovedRefs) { 97 final List<int[]> removedStartsEnds= new ArrayList<>(); 98 fRoot.accept(new ASTVisitor(true) { 99 int fRemovingStart= -1; 100 @Override 101 public void preVisit(ASTNode node) { 102 Object property= node.getProperty(PROPERTY_KEY); 103 if (property == REMOVED) { 104 if (fRemovingStart == -1) { 105 fRemovingStart= node.getStartPosition(); 106 } else { 107 /* 108 * Bug in client code: REMOVED node should not be nested inside another REMOVED node without 109 * an intermediate RETAINED node. 110 * Drop REMOVED property to prevent problems later (premature end of REMOVED section). 111 */ 112 node.setProperty(PROPERTY_KEY, null); 113 } 114 } else if (property == RETAINED) { 115 if (fRemovingStart != -1) { 116 removedStartsEnds.add(new int[] { fRemovingStart, node.getStartPosition() }); 117 fRemovingStart= -1; 118 } else { 119 /* 120 * Bug in client code: RETAINED node should not be nested inside another RETAINED node without 121 * an intermediate REMOVED node and must have an enclosing REMOVED node. 122 * Drop RETAINED property to prevent problems later (premature restart of REMOVED section). 123 */ 124 node.setProperty(PROPERTY_KEY, null); 125 } 126 } 127 super.preVisit(node); 128 } 129 @Override 130 public void postVisit(ASTNode node) { 131 Object property= node.getProperty(PROPERTY_KEY); 132 if (property == RETAINED) { 133 int end= node.getStartPosition() + node.getLength(); 134 fRemovingStart= end; 135 } else if (property == REMOVED) { 136 if (fRemovingStart != -1) { 137 int end= node.getStartPosition() + node.getLength(); 138 removedStartsEnds.add(new int[] { fRemovingStart, end }); 139 fRemovingStart= -1; 140 } 141 } 142 super.postVisit(node); 143 } 144 }); 145 146 for (SimpleName name : importNames) { 147 if (isInRemoved(name, removedStartsEnds)) 148 removedRefs.add(name); 149 else 150 unremovedRefs.add(name); 151 } 152 for (SimpleName name : staticNames) { 153 if (isInRemoved(name, removedStartsEnds)) 154 removedRefs.add(name); 155 else 156 unremovedRefs.add(name); 157 } 158 for (ImportDeclaration importDecl : fInlinedStaticImports) { 159 Name name= importDecl.getName(); 160 if (name instanceof QualifiedName) 161 name= ((QualifiedName) name).getName(); 162 removedRefs.add((SimpleName) name); 163 } 164 } 165 isInRemoved(SimpleName ref, List<int[]> removedStartsEnds)166 private boolean isInRemoved(SimpleName ref, List<int[]> removedStartsEnds) { 167 int start= ref.getStartPosition(); 168 int end= start + ref.getLength(); 169 for (int[] removedStartsEnd : removedStartsEnds) { 170 if (start >= removedStartsEnd[0] && end <= removedStartsEnd[1]) { 171 return true; 172 } 173 } 174 return false; 175 } 176 getImportsToRemove()177 public IBinding[] getImportsToRemove() { 178 ArrayList<SimpleName> importNames= new ArrayList<>(); 179 ArrayList<SimpleName> staticNames= new ArrayList<>(); 180 181 ImportReferencesCollector.collect(fRoot, fProject, null, importNames, staticNames); 182 183 List<SimpleName> removedRefs= new ArrayList<>(); 184 List<SimpleName> unremovedRefs= new ArrayList<>(); 185 divideTypeRefs(importNames, staticNames, removedRefs, unremovedRefs); 186 if (removedRefs.isEmpty()) 187 return new IBinding[0]; 188 189 HashMap<String, IBinding> potentialRemoves= getPotentialRemoves(removedRefs); 190 for (SimpleName name : unremovedRefs) { 191 potentialRemoves.remove(name.getIdentifier()); 192 } 193 194 Collection<IBinding> importsToRemove= potentialRemoves.values(); 195 return importsToRemove.toArray(new IBinding[importsToRemove.size()]); 196 } 197 getPotentialRemoves(List<SimpleName> removedRefs)198 private HashMap<String, IBinding> getPotentialRemoves(List<SimpleName> removedRefs) { 199 HashMap<String, IBinding>potentialRemoves= new HashMap<>(); 200 for (SimpleName name : removedRefs) { 201 if (fAddedImports.contains(name.getIdentifier()) || hasAddedStaticImport(name)) 202 continue; 203 IBinding binding= name.resolveBinding(); 204 if (binding != null) 205 potentialRemoves.put(name.getIdentifier(), binding); 206 } 207 return potentialRemoves; 208 } 209 hasAddedStaticImport(SimpleName name)210 private boolean hasAddedStaticImport(SimpleName name) { 211 IBinding binding= name.resolveBinding(); 212 if (binding instanceof IVariableBinding) { 213 IVariableBinding variable= (IVariableBinding) binding; 214 return hasAddedStaticImport(variable.getDeclaringClass().getQualifiedName(), variable.getName(), true); 215 } else if (binding instanceof IMethodBinding) { 216 IMethodBinding method= (IMethodBinding) binding; 217 return hasAddedStaticImport(method.getDeclaringClass().getQualifiedName(), method.getName(), false); 218 } 219 return false; 220 } 221 hasAddedStaticImport(String qualifier, String member, boolean field)222 private boolean hasAddedStaticImport(String qualifier, String member, boolean field) { 223 StaticImportData data= null; 224 for (final Iterator<StaticImportData> iterator= fAddedStaticImports.iterator(); iterator.hasNext();) { 225 data= iterator.next(); 226 if (data.fQualifier.equals(qualifier) && data.fMember.equals(member) && data.fField == field) 227 return true; 228 } 229 return false; 230 } 231 hasRemovedNodes()232 public boolean hasRemovedNodes() { 233 return fHasRemovedNodes || !fInlinedStaticImports.isEmpty(); 234 } 235 registerAddedImport(String typeName)236 public void registerAddedImport(String typeName) { 237 int dot= typeName.lastIndexOf('.'); 238 if (dot == -1) 239 fAddedImports.add(typeName); 240 else 241 fAddedImports.add(typeName.substring(dot + 1)); 242 } 243 registerAddedImports(Type newTypeNode)244 public void registerAddedImports(Type newTypeNode) { 245 newTypeNode.accept(new ASTVisitor(true) { 246 247 private void addName(SimpleName name) { 248 fAddedImports.add(name.getIdentifier()); 249 } 250 251 @Override 252 public boolean visit(NameQualifiedType node) { 253 addName(node.getName()); 254 return false; 255 } 256 257 @Override 258 public boolean visit(QualifiedName node) { 259 addName(node.getName()); 260 return false; 261 } 262 263 @Override 264 public boolean visit(QualifiedType node) { 265 addName(node.getName()); 266 return false; 267 } 268 269 @Override 270 public boolean visit(SimpleName node) { 271 addName(node); 272 return false; 273 } 274 }); 275 } 276 registerAddedStaticImport(String qualifier, String member, boolean field)277 public void registerAddedStaticImport(String qualifier, String member, boolean field) { 278 fAddedStaticImports.add(new StaticImportData(qualifier, member, field)); 279 } 280 registerAddedStaticImport(IBinding binding)281 public void registerAddedStaticImport(IBinding binding) { 282 if (binding instanceof IVariableBinding) { 283 ITypeBinding declaringType= ((IVariableBinding) binding).getDeclaringClass(); 284 fAddedStaticImports.add(new StaticImportData(Bindings.getRawQualifiedName(declaringType), binding.getName(), true)); 285 286 } else if (binding instanceof IMethodBinding) { 287 ITypeBinding declaringType= ((IMethodBinding) binding).getDeclaringClass(); 288 fAddedStaticImports.add(new StaticImportData(Bindings.getRawQualifiedName(declaringType), binding.getName(), false)); 289 290 } else { 291 throw new IllegalArgumentException(binding.toString()); 292 } 293 } 294 registerRemovedNode(ASTNode removed)295 public void registerRemovedNode(ASTNode removed) { 296 fHasRemovedNodes= true; 297 removed.setProperty(PROPERTY_KEY, REMOVED); 298 } 299 registerRetainedNode(ASTNode retained)300 public void registerRetainedNode(ASTNode retained) { 301 retained.setProperty(PROPERTY_KEY, RETAINED); 302 } 303 applyRemoves(ImportRewrite importRewrite)304 public void applyRemoves(ImportRewrite importRewrite) { 305 for (IBinding b : getImportsToRemove()) { 306 if (b instanceof ITypeBinding) { 307 ITypeBinding typeBinding= (ITypeBinding) b; 308 importRewrite.removeImport(typeBinding.getTypeDeclaration().getQualifiedName()); 309 } else if (b instanceof IMethodBinding) { 310 IMethodBinding binding= (IMethodBinding) b; 311 importRewrite.removeStaticImport(binding.getDeclaringClass().getQualifiedName() + '.' + binding.getName()); 312 } else if (b instanceof IVariableBinding) { 313 IVariableBinding binding= (IVariableBinding) b; 314 importRewrite.removeStaticImport(binding.getDeclaringClass().getQualifiedName() + '.' + binding.getName()); 315 } 316 } 317 } 318 registerInlinedStaticImport(ImportDeclaration importDecl)319 public void registerInlinedStaticImport(ImportDeclaration importDecl) { 320 fInlinedStaticImports.add(importDecl); 321 } 322 } 323