/******************************************************************************* * Copyright (c) 2009, 2015 Zend Technologies 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: * Zend Technologies - initial API and implementation *******************************************************************************/ package org.eclipse.php.refactoring.ui.corext; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.WorkingCopyOwner; import org.eclipse.dltk.core.manipulation.SourceModuleChange; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.ltk.core.refactoring.CategorizedTextEditGroup; import org.eclipse.ltk.core.refactoring.GroupCategorySet; import org.eclipse.php.core.ast.nodes.AST; import org.eclipse.php.core.ast.nodes.Program; import org.eclipse.php.core.project.ProjectOptions; import org.eclipse.php.internal.core.ast.rewrite.ASTRewrite; import org.eclipse.php.refactoring.ui.RefactoringUIPlugin; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.edits.TextEditGroup; /** * A {@link ProgramRewrite} holds all data structures that are typically * required for non-trivial refactorings. All getters are initialized lazily to * avoid lengthy processing in * {@link org.eclipse.ltk.core.refactoring.Refactoring#checkInitialConditions(org.eclipse.core.runtime.IProgressMonitor)} * . * <p> * Bindings are resolved by default, but can be disabled with * <code>setResolveBindings(false)</code>. Statements recovery is enabled by * default, but can be disabled with <code>setStatementsRecovery(false)</code>. * Bindings recovery is disabled by default, but can be enabled with * <code>setBindingRecovery(true)</code>. * </p> */ public class CompilationUnitRewrite { // TODO: add RefactoringStatus fStatus;? private ISourceModule fCu; private List<TextEditGroup> fTextEditGroups = new ArrayList<>(); private Program fRoot; // lazily initialized private ASTRewrite fRewrite; // lazily initialized // private ImportRewrite fImportRewrite; // lazily initialized // private ImportRemover fImportRemover; // lazily initialized private boolean fResolveBindings = true; private boolean fStatementsRecovery = true; private boolean fBindingsRecovery = false; private final WorkingCopyOwner fOwner; private IDocument fRememberContent = null; public CompilationUnitRewrite(ISourceModule cu) { this(null, cu, null); } public CompilationUnitRewrite(WorkingCopyOwner owner, ISourceModule cu) { this(owner, cu, null); } public CompilationUnitRewrite(ISourceModule cu, Program root) { this(null, cu, root); } public CompilationUnitRewrite(WorkingCopyOwner owner, ISourceModule cu, Program root) { fOwner = owner; fCu = cu; fRoot = root; } public void rememberContent() { fRememberContent = new Document(); } /** * Controls whether the compiler should provide binding information for the * AST nodes it creates. To be effective, this method must be called before * any of {@link #getRoot()},{@link #getASTRewrite()}, * {@link #getImportRemover()}. This method has no effect if the target * object has been created with * {@link #ProgramRewrite(ISourceModule, Program)}. * <p> * Defaults to <b><code>true</code></b> (do resolve bindings). * </p> * * @param resolve * <code>true</code> if bindings are wanted, and * <code>false</code> if bindings are not of interest * @see org.eclipse.jdt.core.dom.ASTParser#setResolveBindings(boolean) Note: * The default value (<code>true</code>) differs from the one of the * corresponding method in ASTParser. */ public void setResolveBindings(boolean resolve) { fResolveBindings = resolve; } /** * Controls whether the compiler should perform statements recovery. To be * effective, this method must be called before any of {@link #getRoot()}, * {@link #getASTRewrite()}, {@link #getImportRemover()}. This method has no * effect if the target object has been created with * {@link #ProgramRewrite(ISourceModule, Program)}. * <p> * Defaults to <b><code>true</code></b> (do perform statements recovery). * </p> * * @param statementsRecovery * whether statements recovery should be performed * @see org.eclipse.jdt.core.dom.ASTParser#setStatementsRecovery(boolean) */ public void setStatementsRecovery(boolean statementsRecovery) { fStatementsRecovery = statementsRecovery; } /** * Controls whether the compiler should perform bindings recovery. To be * effective, this method must be called before any of {@link #getRoot()}, * {@link #getASTRewrite()}, {@link #getImportRemover()}. This method has no * effect if the target object has been created with * {@link #ProgramRewrite(ISourceModule, Program)}. * <p> * Defaults to <b><code>false</code></b> (do not perform bindings recovery). * </p> * * @param bindingsRecovery * whether bindings recovery should be performed * @see org.eclipse.jdt.core.dom.ASTParser#setBindingsRecovery(boolean) */ public void setBindingRecovery(boolean bindingsRecovery) { fBindingsRecovery = bindingsRecovery; } public void clearASTRewrite() { fRewrite = null; fTextEditGroups = new ArrayList<>(); } public void clearImportRewrites() { // fImportRewrite= null; } public void clearASTAndImportRewrites() { clearASTRewrite(); // fImportRewrite= null; } public CategorizedTextEditGroup createCategorizedGroupDescription(String name, GroupCategorySet set) { CategorizedTextEditGroup result = new CategorizedTextEditGroup(name, set); fTextEditGroups.add(result); return result; } public TextEditGroup createGroupDescription(String name) { TextEditGroup result = new TextEditGroup(name); fTextEditGroups.add(result); return result; } /** * Creates a compilation unit change based on the events recorded by this * compilation unit rewrite. * * @return a {@link SourceModuleChange}, or <code>null</code> for an empty * change * @throws CoreException * when text buffer acquisition or import rewrite text edit * creation fails * @throws IllegalArgumentException * when the AST rewrite encounters problems */ public SourceModuleChange createChange() throws CoreException { return createChange(true, null); } /** * Creates a compilation unit change based on the events recorded by this * compilation unit rewrite. * * @param generateGroups * <code>true</code> to generate text edit groups, * <code>false</code> otherwise * @param monitor * the progress monitor or <code>null</code> * @return a {@link SourceModuleChange}, or <code>null</code> for an empty * change * @throws CoreException * when text buffer acquisition or import rewrite text edit * creation fails * @throws IllegalArgumentException * when the AST rewrite encounters problems */ public SourceModuleChange createChange(boolean generateGroups, IProgressMonitor monitor) throws CoreException { return createChange(fCu.getElementName(), generateGroups, monitor); } /** * Creates a compilation unit change based on the events recorded by this * compilation unit rewrite. * * @param name * the name of the change to create * @param generateGroups * <code>true</code> to generate text edit groups, * <code>false</code> otherwise * @param monitor * the progress monitor or <code>null</code> * @return a {@link SourceModuleChange}, or <code>null</code> for an empty * change * @throws CoreException * when text buffer acquisition or import rewrite text edit * creation fails * @throws IllegalArgumentException * when the AST rewrite encounters problems */ public SourceModuleChange createChange(String name, boolean generateGroups, IProgressMonitor monitor) throws CoreException { SourceModuleChange cuChange = new SourceModuleChange(name, fCu); MultiTextEdit multiEdit = new MultiTextEdit(); cuChange.setEdit(multiEdit); return attachChange(cuChange, generateGroups, monitor); } /** * Attaches the changes of this compilation unit rewrite to the given CU * Change. The given change <b>must</b> either have no root edit, or a * MultiTextEdit as a root edit. The edits in the given change <b>must * not</b> overlap with the changes of this compilation unit. * * @param cuChange * existing SourceModuleChange with a MultiTextEdit root or no * root at all. * @param generateGroups * <code>true</code> to generate text edit groups, * <code>false</code> otherwise * @param monitor * the progress monitor or <code>null</code> * @return a change combining the changes of this rewrite and the given * rewrite, or <code>null</code> for an empty change * @throws CoreException * when text buffer acquisition or import rewrite text edit * creation fails */ public SourceModuleChange attachChange(SourceModuleChange cuChange, boolean generateGroups, IProgressMonitor monitor) throws CoreException { try { boolean needsAstRewrite = fRewrite != null; // TODO: do we need // something like // ASTRewrite#hasChanges() // here? // boolean needsImportRemoval= fImportRemover != null && // fImportRemover.hasRemovedNodes(); // boolean needsImportRewrite= fImportRewrite != null && // fImportRewrite.hasRecordedChanges() || needsImportRemoval; if (!needsAstRewrite/* * && !needsImportRemoval && !needsImportRewrite */) return null; MultiTextEdit multiEdit = (MultiTextEdit) cuChange.getEdit(); if (multiEdit == null) { multiEdit = new MultiTextEdit(); cuChange.setEdit(multiEdit); } if (needsAstRewrite) { TextEdit rewriteEdit; if (fRememberContent != null) { rewriteEdit = fRewrite.rewriteAST(fRememberContent, fCu.getScriptProject().getOptions(true)); } else { rewriteEdit = fRewrite.rewriteAST(); } if (!isEmptyEdit(rewriteEdit)) { multiEdit.addChild(rewriteEdit); if (generateGroups) { for (Iterator<TextEditGroup> iter = fTextEditGroups.iterator(); iter.hasNext();) { TextEditGroup group = iter.next(); cuChange.addTextEditGroup(group); } } } } // if (needsImportRemoval) { // fImportRemover.applyRemoves(getImportRewrite()); // } // if (needsImportRewrite) { // TextEdit importsEdit= fImportRewrite.rewriteImports(monitor); // if (!isEmptyEdit(importsEdit)) { // multiEdit.addChild(importsEdit); // String importUpdateName= // RefactoringCoreMessages.ASTData_update_imports; // cuChange.addTextEditGroup(new TextEditGroup(importUpdateName, // importsEdit)); // } // } else { // // } if (isEmptyEdit(multiEdit)) return null; return cuChange; } finally { if (monitor != null) monitor.done(); } } private static boolean isEmptyEdit(TextEdit edit) { return edit.getClass() == MultiTextEdit.class && !edit.hasChildren(); } public ISourceModule getCu() { return fCu; } public Program getRoot() { if (fRoot == null) { try { fRoot = new RefactoringASTParser(ProjectOptions.getPHPVersion(fCu), ProjectOptions.useShortTags(fCu)) .parse(fCu, fOwner, fResolveBindings, fStatementsRecovery, fBindingsRecovery, null); } catch (Exception e) { RefactoringUIPlugin.log(e); } } return fRoot; } public AST getAST() { return getRoot().getAST(); } public ASTRewrite getASTRewrite() { if (fRewrite == null) { fRewrite = ASTRewrite.create(getRoot().getAST()); if (fRememberContent != null) { // wain until ast rewrite is // accessed first try { fRememberContent.set(fCu.getSource()); } catch (ModelException e) { fRememberContent = null; } } } return fRewrite; } public void clearGroupDescriptions() { for (Iterator<TextEditGroup> iter = fTextEditGroups.iterator(); iter.hasNext();) { TextEditGroup group = iter.next(); group.clearTextEdits(); } } }