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