1 /******************************************************************************* 2 * Copyright (c) 2000, 2019 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 * Microsoft Corporation - Add fFormattingOptions field - https://bugs.eclipse.org/551601 15 *******************************************************************************/ 16 package org.eclipse.jdt.internal.corext.refactoring.structure; 17 18 import java.util.ArrayList; 19 import java.util.List; 20 import java.util.Map; 21 22 import org.eclipse.core.runtime.CoreException; 23 import org.eclipse.core.runtime.IProgressMonitor; 24 25 import org.eclipse.text.edits.MultiTextEdit; 26 import org.eclipse.text.edits.TextEdit; 27 import org.eclipse.text.edits.TextEditGroup; 28 29 import org.eclipse.jface.text.Document; 30 import org.eclipse.jface.text.IDocument; 31 32 import org.eclipse.ltk.core.refactoring.CategorizedTextEditGroup; 33 import org.eclipse.ltk.core.refactoring.GroupCategorySet; 34 35 import org.eclipse.jdt.core.ICompilationUnit; 36 import org.eclipse.jdt.core.JavaModelException; 37 import org.eclipse.jdt.core.WorkingCopyOwner; 38 import org.eclipse.jdt.core.dom.AST; 39 import org.eclipse.jdt.core.dom.CompilationUnit; 40 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; 41 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; 42 import org.eclipse.jdt.core.manipulation.CodeStyleConfiguration; 43 import org.eclipse.jdt.core.refactoring.CompilationUnitChange; 44 45 import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin; 46 import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; 47 import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; 48 import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; 49 50 /** 51 * A {@link CompilationUnitRewrite} holds all data structures that are typically 52 * required for non-trivial refactorings. All getters are initialized lazily to 53 * avoid lengthy processing in 54 * {@link org.eclipse.ltk.core.refactoring.Refactoring#checkInitialConditions(org.eclipse.core.runtime.IProgressMonitor)}. 55 * <p> 56 * Bindings are resolved by default, but can be disabled with <code>setResolveBindings(false)</code>. 57 * Statements recovery is enabled by default, but can be disabled with <code>setStatementsRecovery(false)</code>. 58 * Bindings recovery is disabled by default, but can be enabled with <code>setBindingRecovery(true)</code>. 59 * </p> 60 * 61 * see JDTUIHelperClasses 62 */ 63 public class CompilationUnitRewrite { 64 //TODO: add RefactoringStatus fStatus;? 65 private ICompilationUnit fCu; 66 private List<TextEditGroup> fTextEditGroups= new ArrayList<>(); 67 68 private CompilationUnit fRoot; // lazily initialized 69 private ASTRewrite fRewrite; // lazily initialized 70 private ImportRewrite fImportRewrite; // lazily initialized 71 private ImportRemover fImportRemover; // lazily initialized 72 private boolean fResolveBindings= true; 73 private boolean fStatementsRecovery= true; 74 private boolean fBindingsRecovery= false; 75 private final WorkingCopyOwner fOwner; 76 private IDocument fRememberContent= null; 77 private Map<String, String> fFormattingOptions; 78 79 CompilationUnitRewrite(ICompilationUnit cu)80 public CompilationUnitRewrite(ICompilationUnit cu) { 81 this(null, cu, null, null); 82 } 83 CompilationUnitRewrite(WorkingCopyOwner owner, ICompilationUnit cu)84 public CompilationUnitRewrite(WorkingCopyOwner owner, ICompilationUnit cu) { 85 this(owner, cu, null, null); 86 } 87 CompilationUnitRewrite(ICompilationUnit cu, CompilationUnit root)88 public CompilationUnitRewrite(ICompilationUnit cu, CompilationUnit root) { 89 this(null, cu, root, null); 90 } 91 CompilationUnitRewrite(WorkingCopyOwner owner, ICompilationUnit cu, CompilationUnit root)92 public CompilationUnitRewrite(WorkingCopyOwner owner, ICompilationUnit cu, CompilationUnit root) { 93 this(owner, cu, root, null); 94 } 95 CompilationUnitRewrite(WorkingCopyOwner owner, ICompilationUnit cu, CompilationUnit root, Map<String, String> options)96 public CompilationUnitRewrite(WorkingCopyOwner owner, ICompilationUnit cu, CompilationUnit root, Map<String, String> options) { 97 fOwner= owner; 98 fCu= cu; 99 fRoot= root; 100 fFormattingOptions = options; 101 } 102 rememberContent()103 public void rememberContent() { 104 fRememberContent= new Document(); 105 } 106 107 108 /** 109 * Controls whether the compiler should provide binding information for the AST 110 * nodes it creates. To be effective, this method must be called before any 111 * of {@link #getRoot()},{@link #getASTRewrite()}, 112 * {@link #getImportRemover()}. This method has no effect if the target object 113 * has been created with {@link #CompilationUnitRewrite(ICompilationUnit, CompilationUnit)}. 114 * <p> 115 * Defaults to <b><code>true</code></b> (do resolve bindings). 116 * </p> 117 * 118 * @param resolve 119 * <code>true</code> if bindings are wanted, and 120 * <code>false</code> if bindings are not of interest 121 * @see org.eclipse.jdt.core.dom.ASTParser#setResolveBindings(boolean) 122 * Note: The default value (<code>true</code>) differs from the one of 123 * the corresponding method in ASTParser. 124 */ setResolveBindings(boolean resolve)125 public void setResolveBindings(boolean resolve) { 126 fResolveBindings= resolve; 127 } 128 129 /** 130 * Controls whether the compiler should perform statements recovery. 131 * To be effective, this method must be called before any 132 * of {@link #getRoot()},{@link #getASTRewrite()}, 133 * {@link #getImportRemover()}. This method has no effect if the target object 134 * has been created with {@link #CompilationUnitRewrite(ICompilationUnit, CompilationUnit)}. 135 * <p> 136 * Defaults to <b><code>true</code></b> (do perform statements recovery). 137 * </p> 138 * 139 * @param statementsRecovery whether statements recovery should be performed 140 * @see org.eclipse.jdt.core.dom.ASTParser#setStatementsRecovery(boolean) 141 */ setStatementsRecovery(boolean statementsRecovery)142 public void setStatementsRecovery(boolean statementsRecovery) { 143 fStatementsRecovery= statementsRecovery; 144 } 145 146 /** 147 * Controls whether the compiler should perform bindings recovery. 148 * To be effective, this method must be called before any 149 * of {@link #getRoot()},{@link #getASTRewrite()}, 150 * {@link #getImportRemover()}. This method has no effect if the target object 151 * has been created with {@link #CompilationUnitRewrite(ICompilationUnit, CompilationUnit)}. 152 * <p> 153 * Defaults to <b><code>false</code></b> (do not perform bindings recovery). 154 * </p> 155 * 156 * @param bindingsRecovery whether bindings recovery should be performed 157 * @see org.eclipse.jdt.core.dom.ASTParser#setBindingsRecovery(boolean) 158 */ setBindingRecovery(boolean bindingsRecovery)159 public void setBindingRecovery(boolean bindingsRecovery) { 160 fBindingsRecovery= bindingsRecovery; 161 } 162 clearASTRewrite()163 public void clearASTRewrite() { 164 fRewrite= null; 165 fTextEditGroups= new ArrayList<>(); 166 } 167 clearImportRewrites()168 public void clearImportRewrites() { 169 fImportRewrite= null; 170 fImportRemover= null; 171 } 172 clearASTAndImportRewrites()173 public void clearASTAndImportRewrites() { 174 clearASTRewrite(); 175 clearImportRewrites(); 176 } 177 createCategorizedGroupDescription(String name, GroupCategorySet set)178 public CategorizedTextEditGroup createCategorizedGroupDescription(String name, GroupCategorySet set) { 179 CategorizedTextEditGroup result= new CategorizedTextEditGroup(name, set); 180 fTextEditGroups.add(result); 181 return result; 182 } 183 createGroupDescription(String name)184 public TextEditGroup createGroupDescription(String name) { 185 TextEditGroup result= new TextEditGroup(name); 186 fTextEditGroups.add(result); 187 return result; 188 } 189 190 /** 191 * Creates a compilation unit change based on the events recorded by this compilation unit 192 * rewrite. 193 * 194 * @param generateGroups <code>true</code> to generate text edit groups, <code>false</code> otherwise 195 * @return a {@link CompilationUnitChange}, or <code>null</code> for an empty change 196 * @throws CoreException when text buffer acquisition or import rewrite text edit creation fails 197 * @throws IllegalArgumentException when the AST rewrite encounters problems 198 * @since 3.6 199 */ createChange(boolean generateGroups)200 public CompilationUnitChange createChange(boolean generateGroups) throws CoreException { 201 return createChange(generateGroups, null); 202 } 203 204 /** 205 * Creates a compilation unit change based on the events recorded by this compilation unit 206 * rewrite. 207 * <p> 208 * DO NOT REMOVE, used in a product.</p> 209 * 210 * @return a {@link org.eclipse.jdt.core.refactoring.CompilationUnitChange}, or <code>null</code> for an empty change 211 * @throws CoreException when text buffer acquisition or import rewrite text edit creation fails 212 * @throws IllegalArgumentException when the AST rewrite encounters problems 213 * @deprecated since 3.5, replaced by {@link #createChange(boolean)} 214 */ 215 @Deprecated createChange()216 public org.eclipse.jdt.internal.corext.refactoring.changes.CompilationUnitChange createChange() throws CoreException { 217 CompilationUnitChange change= createChange(true); 218 if (change == null) 219 return null; 220 return new org.eclipse.jdt.internal.corext.refactoring.changes.CompilationUnitChange(change); 221 } 222 223 /** 224 * Creates a compilation unit change based on the events recorded by this compilation unit 225 * rewrite. 226 * 227 * @param generateGroups <code>true</code> to generate text edit groups, <code>false</code> 228 * otherwise 229 * @param monitor the progress monitor or <code>null</code> 230 * @return a {@link CompilationUnitChange}, or <code>null</code> for an empty change 231 * @throws CoreException when text buffer acquisition or import rewrite text edit creation fails 232 * @throws IllegalArgumentException when the AST rewrite encounters problems 233 */ createChange(boolean generateGroups, IProgressMonitor monitor)234 public CompilationUnitChange createChange(boolean generateGroups, IProgressMonitor monitor) throws CoreException { 235 return createChange(fCu.getElementName(), generateGroups, monitor); 236 } 237 238 /** 239 * Creates a compilation unit change based on the events recorded by this compilation unit rewrite. 240 * @param name the name of the change to create 241 * @param generateGroups <code>true</code> to generate text edit groups, <code>false</code> otherwise 242 * @param monitor the progress monitor or <code>null</code> 243 * @return a {@link CompilationUnitChange}, or <code>null</code> for an empty change 244 * @throws CoreException when text buffer acquisition or import rewrite text edit creation fails 245 * @throws IllegalArgumentException when the AST rewrite encounters problems 246 */ createChange(String name, boolean generateGroups, IProgressMonitor monitor)247 public CompilationUnitChange createChange(String name, boolean generateGroups, IProgressMonitor monitor) throws CoreException { 248 CompilationUnitChange cuChange= new CompilationUnitChange(name, fCu); 249 MultiTextEdit multiEdit= new MultiTextEdit(); 250 cuChange.setEdit(multiEdit); 251 return attachChange(cuChange, generateGroups, monitor); 252 } 253 254 255 /** 256 * Attaches the changes of this compilation unit rewrite to the given CU Change. The given 257 * change <b>must</b> either have no root edit, or a MultiTextEdit as a root edit. 258 * The edits in the given change <b>must not</b> overlap with the changes of 259 * this compilation unit. 260 * 261 * @param cuChange existing CompilationUnitChange with a MultiTextEdit root or no root at all. 262 * @param generateGroups <code>true</code> to generate text edit groups, <code>false</code> otherwise 263 * @param monitor the progress monitor or <code>null</code> 264 * @return a change combining the changes of this rewrite and the given rewrite, or <code>null</code> for an empty change 265 * @throws CoreException when text buffer acquisition or import rewrite text edit creation fails 266 */ attachChange(CompilationUnitChange cuChange, boolean generateGroups, IProgressMonitor monitor)267 public CompilationUnitChange attachChange(CompilationUnitChange cuChange, boolean generateGroups, IProgressMonitor monitor) throws CoreException { 268 try { 269 boolean needsAstRewrite= fRewrite != null; // TODO: do we need something like ASTRewrite#hasChanges() here? 270 boolean needsImportRemoval= fImportRemover != null && fImportRemover.hasRemovedNodes(); 271 boolean needsImportRewrite= fImportRewrite != null && fImportRewrite.hasRecordedChanges() || needsImportRemoval; 272 if (!needsAstRewrite && !needsImportRemoval && !needsImportRewrite) 273 return null; 274 275 MultiTextEdit multiEdit= (MultiTextEdit) cuChange.getEdit(); 276 if (multiEdit == null) { 277 multiEdit= new MultiTextEdit(); 278 cuChange.setEdit(multiEdit); 279 } 280 281 if (needsAstRewrite) { 282 // clean up garbage from earlier calls to ASTRewrite#rewriteAST(..), see https://bugs.eclipse.org/bugs/show_bug.cgi?id=408334#c2 283 clearGroupDescriptionEdits(); 284 TextEdit rewriteEdit; 285 if (fRememberContent != null) { 286 rewriteEdit= fRewrite.rewriteAST(fRememberContent, fFormattingOptions == null ? fCu.getJavaProject().getOptions(true) : fFormattingOptions); 287 } else { 288 if (fFormattingOptions == null) { 289 rewriteEdit= fRewrite.rewriteAST(); 290 } else { 291 rewriteEdit= fRewrite.rewriteAST(new Document(fCu.getSource()), fFormattingOptions); 292 } 293 } 294 if (!isEmptyEdit(rewriteEdit)) { 295 multiEdit.addChild(rewriteEdit); 296 if (generateGroups) { 297 for (TextEditGroup group : fTextEditGroups) { 298 cuChange.addTextEditGroup(group); 299 } 300 } 301 } 302 } 303 if (needsImportRemoval) { 304 fImportRemover.applyRemoves(getImportRewrite()); 305 } 306 if (needsImportRewrite) { 307 TextEdit importsEdit= fImportRewrite.rewriteImports(monitor); 308 if (!isEmptyEdit(importsEdit)) { 309 multiEdit.addChild(importsEdit); 310 String importUpdateName= RefactoringCoreMessages.ASTData_update_imports; 311 cuChange.addTextEditGroup(new TextEditGroup(importUpdateName, importsEdit)); 312 } 313 } else { 314 315 } 316 if (isEmptyEdit(multiEdit)) 317 return null; 318 return cuChange; 319 } finally { 320 if (monitor != null) 321 monitor.done(); 322 } 323 } 324 isEmptyEdit(TextEdit edit)325 private static boolean isEmptyEdit(TextEdit edit) { 326 return edit.getClass() == MultiTextEdit.class && ! edit.hasChildren(); 327 } 328 getCu()329 public ICompilationUnit getCu() { 330 return fCu; 331 } 332 getRoot()333 public CompilationUnit getRoot() { 334 if (fRoot == null) 335 fRoot= new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL).parse(fCu, fOwner, fResolveBindings, fStatementsRecovery, fBindingsRecovery, null); 336 return fRoot; 337 } 338 getAST()339 public AST getAST() { 340 return getRoot().getAST(); 341 } 342 getASTRewrite()343 public ASTRewrite getASTRewrite() { 344 if (fRewrite == null) { 345 fRewrite= ASTRewrite.create(getRoot().getAST()); 346 if (fRememberContent != null) { // wain until ast rewrite is accessed first 347 try { 348 fRememberContent.set(fCu.getSource()); 349 } catch (JavaModelException e) { 350 fRememberContent= null; 351 } 352 } 353 } 354 return fRewrite; 355 } 356 getImportRewrite()357 public ImportRewrite getImportRewrite() { 358 if (fImportRewrite == null) { 359 // lazily initialized to avoid lengthy processing in checkInitialConditions(..) 360 try { 361 /* If bindings are to be resolved, then create the AST, so that 362 * ImportRewrite#setUseContextToFilterImplicitImports(boolean) will be set to true 363 * and ContextSensitiveImportRewriteContext etc. can be used. */ 364 if (fRoot == null && ! fResolveBindings) { 365 fImportRewrite= CodeStyleConfiguration.createImportRewrite(fCu, true); 366 } else { 367 fImportRewrite= createImportRewrite(getRoot(), true); 368 } 369 } catch (CoreException e) { 370 JavaManipulationPlugin.log(e); 371 throw new IllegalStateException(e.getMessage()); // like ASTParser#createAST(..) does 372 } 373 } 374 return fImportRewrite; 375 376 } 377 createImportRewrite(CompilationUnit astRoot, boolean restoreExistingImports)378 private ImportRewrite createImportRewrite(CompilationUnit astRoot, boolean restoreExistingImports) { 379 ImportRewrite rewrite= CodeStyleConfiguration.createImportRewrite(astRoot, restoreExistingImports); 380 if (astRoot.getAST().hasResolvedBindings()) { 381 rewrite.setUseContextToFilterImplicitImports(true); 382 } 383 return rewrite; 384 } 385 386 getImportRemover()387 public ImportRemover getImportRemover() { 388 if (fImportRemover == null) { 389 fImportRemover= new ImportRemover(fCu.getJavaProject(), getRoot()); 390 } 391 return fImportRemover; 392 } 393 clearGroupDescriptionEdits()394 private void clearGroupDescriptionEdits() { 395 for (TextEditGroup group : fTextEditGroups) { 396 group.clearTextEdits(); 397 } 398 } 399 getFormattingOptions()400 public Map<String, String> getFormattingOptions() { 401 return fFormattingOptions; 402 } 403 setFormattingOptions(Map<String, String> formattingOptions)404 public void setFormattingOptions(Map<String, String> formattingOptions) { 405 fFormattingOptions = formattingOptions; 406 } 407 } 408