org.eclipse.ajdt.internal.ui.refactoring.pullout.PullOutRefactoring.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ajdt.internal.ui.refactoring.pullout.PullOutRefactoring.java

Source

/*******************************************************************************
 * Copyright (c) 2010 SpringSource 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:
 *     Kris De Volder - initial API and implementation
 *******************************************************************************/
package org.eclipse.ajdt.internal.ui.refactoring.pullout;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.ajdt.core.javaelements.AJCompilationUnit;
import org.eclipse.ajdt.core.javaelements.AspectElement;
import org.eclipse.ajdt.core.javaelements.IntertypeElement;
import org.eclipse.ajdt.core.javaelements.SourceRange;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.FieldDeclaration;
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.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.corext.codemanipulation.ImportReferencesCollector;
import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Region;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;

public class PullOutRefactoring extends Refactoring {

    /**
     * An instance of this class pulls together all required info for
     * the rewriting of the aspect and handles the rewriting.
     */
    class AspectRewrite {

        /**
         * Collects the text of all ITDs.
         */
        private StringBuffer itds = new StringBuffer();

        /**
         * Collects info for and handles rewriting of imports for the target aspect.
         */
        private ImportRewrite importRewrite;

        /**
         * This field may be set while creating the "deletion" edits to pull out ITDs.
         * This will only happen if the compilation unit from which we are pulling out 
         * is the same as the target aspect's compilation unit. If set to a non-null
         * value this cuChange object should be used for recording the "insertion"
         * edits.
         */
        private CompilationUnitChange cuChange = null;

        public AspectRewrite() throws JavaModelException {
            importRewrite = ImportRewrite.create(targetAspect.getCompilationUnit(), true);
        }

        public void addITD(ITDCreator itd, RefactoringStatus status)
                throws JavaModelException, BadLocationException {
            itd.collectImports(importRewrite, status);
            itds.append(itd.createText());
        }

        /**
         * Create an ICompiltationUnitChange with all changes to the aspect's CU, related to the insertion 
         * of ITDs. Ensure these changes are added to the allChanges object.
         */
        private void rewriteAspect(IProgressMonitor submonitor, CompositeChange allChanges) {
            try {
                CompilationUnitChange edits = getCUChange();

                // Create edit to add "privileged"
                if (isMakePrivileged() && !isPrivileged()) {
                    int start = targetAspect.getSourceRange().getOffset();
                    int nameStart = targetAspect.getNameRange().getOffset() - start;
                    String aspectText = targetAspect.getSource().substring(0, nameStart);
                    // If all is well, we now have the aspectText, upto the keywords "aspect"
                    // Caveat: for some reason AJDT has replaced this keyword by the "class" keyword.
                    int aspectKeywordStart = aspectText.lastIndexOf("class");
                    Assert.isTrue(aspectKeywordStart >= 0, "The aspect keyword was not found in the aspect source");
                    aspectKeywordStart += start; // Adjust because the start of our string may not be the
                                                 // start of the compilation unit.
                    edits.addEdit(new InsertEdit(aspectKeywordStart, "privileged "));
                }

                // Create edit to the imports section of compilation unit
                if (importRewrite.hasRecordedChanges()) {
                    try {
                        edits.addEdit(importRewrite.rewriteImports(submonitor));
                    } catch (Exception e) {
                        //An aspect handles this
                    }
                }

                // Add the itds to the aspect
                edits.addEdit(new InsertEdit(getInsertLocation(), itds.toString()));

                if (edits.getParent() == null) {
                    allChanges.add(edits);
                } else {
                    //If not null, it means we already added it, because the aspect is
                    //in the same CU as some pulled out members.
                }
            } catch (JavaModelException e) {
            }
        }

        /**
         * Get the CompilationUnitChange object that should be used to record the changes related to
         * inserting ITDs into the aspect. The object is created if necessary, or reused if it
         * already exists.
         */
        private CompilationUnitChange getCUChange() {
            if (cuChange == null) {
                cuChange = newCompilationUnitChange(getAspect().getCompilationUnit());
                cuChange.setEdit(new MultiTextEdit()); // root element must be set, or we can't add edits!
            }
            return cuChange;
        }

        public void setCUChange(CompilationUnitChange cuChange) {
            this.cuChange = cuChange;
        }

    }

    /**
     * Helper class to create ITD text from a member. An instance of this class is created
     * to provide a working area in which to build the ITD text.
     * <p>
     * This class was introduced to help manage the complexity of context information that 
     * was getting passed along with various helper methods that break down the creation of an ITD
     * into smaller steps. This was starting to result in extremely long argument lists.
     * <p>
     * This class keeps all that information in one convenient place. It also provides a nice
     * high-level interface to manipulate the properties of the created ITD, while encapsulating
     * the messier parts of the rewriting and text manipulation code inside the class. 
     * 
     * @author kdvolder
     */
    static class ITDCreator {

        private static final int VISIBILITY_MODIFIERS = Modifier.PRIVATE | Modifier.PUBLIC | Modifier.PROTECTED;

        /**
         * The original member from which we create the ITD
         */
        private IMember member;

        /**
         * The AST node corresponding to the original member.
         */
        private BodyDeclaration memberNode;

        /**
         * We place the text of the original member in this document, so that
         * we can accumulate and apply edits against the document to create
         * the final ITD text.
         */
        private IDocument memberText;

        /**
         * Rather than applying edits immeditiately, we accumulate them in here, this is so that
         * we don't end up destroying position information before we have figured out
         * all the edits to apply to the original text.
         */
        private MultiTextEdit edits = new MultiTextEdit();

        /**
         * Modifiers that should be removed when we rewrite the ITD's modifiers.
         * This is a "bitfield". See {@link Modifier} for the meaning of the bits.
         */
        private int deleteMods = 0;

        /**
         * Either an empty String, or a String containing all the modifiers to add, separated 
         * by spaces and with one trailing space.
         */
        private String insertMods = "";

        /**
         * The name of the declaring type, as it should be written in the aspect context (i.e. as
         * a simple name, or a fully qualified name, depending on whether it could be imported.
         * <p>
         * This field is initialized during "collectImports".
         */
        private String declaringTypeRef = null;

        /**
         * The ITD creator requires an IMember and its corresponding AST node, to be
         * able to perform its work of creating the ITD text.
         * 
         * @param member
         * @param memberNode
         * @throws JavaModelException 
         */
        public ITDCreator(IMember member, BodyDeclaration memberNode) throws JavaModelException {
            this.member = member;
            this.memberNode = memberNode;
            this.memberText = new Document(getAJSource(member));
        }

        /**
         * Aspect aware method for getting source code. For elements inside an .aj file, it fetches the 
         * "original" source code, not the rewritten source code. For elements in regular .java file
         * it is identical to the getSource method.
         */
        private String getAJSource(IMember member) throws JavaModelException {
            ICompilationUnit cu = member.getCompilationUnit();
            if (cu instanceof AJCompilationUnit) {
                AJCompilationUnit ajcu = (AJCompilationUnit) cu;
                ajcu.requestOriginalContentMode();
                try {
                    return member.getSource();
                } finally {
                    ajcu.discardOriginalContentMode();
                }
            }
            return member.getSource();
        }

        @Override
        public String toString() {
            if (memberText == null)
                return "ITDCreator(DISPOSED)";
            else {
                return "ITDCreator(----\n" + memberText.get() + "\n" + "----)";
            }
        }

        /**
         * Collect imports needed for this ITD, and add them to the aspects compilation unit's 
         * importRewriter.
         * <p>
         * This also applies the necessary edits to the ITD text if some imports fail because
         * of name clashes.
         * <p>
         * If some references can't be resolved a warning is added to the refactoring satus.
         */
        public void collectImports(ImportRewrite importRewrite, RefactoringStatus status)
                throws JavaModelException {
            Region rangeLimit = new Region(memberNode.getStartPosition(), memberNode.getLength());

            List<SimpleName> extraType = new ArrayList<SimpleName>();
            List<SimpleName> extraStatic = new ArrayList<SimpleName>();

            ImportReferencesCollector.collect(memberNode, member.getJavaProject(), rangeLimit, extraType,
                    extraStatic);

            for (Name name : extraStatic) {
                IBinding binding = name.resolveBinding();
                if (binding == null) {
                    status.addWarning("Couldn't resolve binding, imports may be incorrect",
                            PullOutRefactoring.makeContext(member, name));
                } else {
                    replaceNameRef(name, importRewrite.addStaticImport(binding));
                }
            }

            for (Name name : extraType) {
                ITypeBinding binding = (ITypeBinding) name.resolveBinding();
                if (binding == null) {
                    status.addWarning("Couldn't resolve binding, imports may be incorrect",
                            PullOutRefactoring.makeContext(member, name));
                } else {
                    if (binding.isParameterizedType()) {
                        // Simple names should not be treated as complete generic type references
                        binding = binding.getErasure();
                    }
                    replaceNameRef(name, importRewrite.addImport(binding));
                }
            }

            if (wasIntertype()) {
                IntertypeElement ite = (IntertypeElement) member;
                AJCompilationUnit cu = (AJCompilationUnit) ite.getCompilationUnit();
                IType targetType = ite.findTargetType();
                String typeQName = targetType != null ? targetType.getFullyQualifiedName() : ite.getTargetName();
                declaringTypeRef = importRewrite.addImport(typeQName);
            } else {
                declaringTypeRef = importRewrite.addImport(member.getDeclaringType().getFullyQualifiedName());
            }
        }

        /**
         * @return true if the original pulled out member was *already* an ITD.
         */
        private boolean wasIntertype() {
            return member instanceof IntertypeElement;
        }

        /**
         * Add an extra textedit, if needed for updating a potentially failed import rewrite
         */
        private void replaceNameRef(Name name, String replaceStr)
                throws MalformedTreeException, JavaModelException {
            String orgRefText = name.getFullyQualifiedName();
            if (replaceStr.equals(orgRefText))
                return;
            edits.addChild(new ReplaceEdit(name.getStartPosition() - memberStart(), name.getLength(), replaceStr));
        }

        /**
         * Create the necessary edits to change modifiers on the ITD as described by the
         * deleteMods and insertMods fields.
         */
        private void rewriteModifiers() throws BadLocationException, MalformedTreeException, JavaModelException {
            if (deleteMods != 0) {
                List<Modifier> mods = memberNode.modifiers();
                //String replaceText = makePublic?"public ":"";
                String replaceText = "";
                for (Modifier modifier : mods) {
                    if ((modifier.getKeyword().toFlagValue() & deleteMods) != 0) {
                        int modStart = modifier.getStartPosition() - memberStart();
                        int modEnd = modStart + modifier.getLength();
                        if (Character.isWhitespace(memberText.getChar(modEnd))) {
                            // Delete one extra white space character for a nicer look.
                            // but only if there is one! (imagine there being a weird comment there instead)
                            modEnd++;
                        }
                        edits.addChild(new ReplaceEdit(modStart, modEnd - modStart, replaceText));
                    }
                }
            }
            if (insertMods != null && !"".equals(insertMods)) {
                int insertPos;
                if (memberNode instanceof MethodDeclaration) {
                    MethodDeclaration methodNode = (MethodDeclaration) memberNode;
                    if (methodNode.isConstructor())
                        insertPos = methodNode.getName().getStartPosition() - memberStart();
                    else
                        insertPos = methodNode.getReturnType2().getStartPosition() - memberStart();
                } else if (memberNode instanceof FieldDeclaration) {
                    FieldDeclaration fieldNode = (FieldDeclaration) memberNode;
                    insertPos = fieldNode.getType().getStartPosition() - memberStart();
                } else {
                    insertPos = 0;
                }
                edits.addChild(new InsertEdit(insertPos, insertMods));
            }
        }

        private int memberStart() throws JavaModelException {
            return member.getSourceRange().getOffset();
        }

        public void removeModifier(int mod) {
            this.deleteMods |= mod;
        }

        /**
         * Produce text for the ITD, by applying all requested changes to the
         * original ITD text.
         * <p>
         * This is a 'once only' operation. After the text of the ITD is created
         * this object has served its purpose and should not be used anymore.
         * @throws JavaModelException 
         * @throws BadLocationException 
         * @throws MalformedTreeException 
         */
        public String createText() throws JavaModelException, MalformedTreeException, BadLocationException {
            try {
                int memberStart = memberStart();

                // All positions, except for memberStart itself, will be computed relative to member
                // start (since our memberText document only contains the member text)

                int memberEnd = memberText.getLength();
                int nameStart = member.getNameRange().getOffset() - memberStart;

                // Add some indentation correction to the front
                edits.addChild(new InsertEdit(0, CodeFormatterUtil.createIndentString(1, member.getJavaProject())));

                // Rewrite modifiers
                rewriteModifiers();

                //Rewrite stuff in and around the name... only when original is *not* an intertype element!
                if (!wasIntertype()) {
                    // Insert declaring type reference in front of name
                    IType declaringType = member.getDeclaringType();
                    Assert.isNotNull(declaringTypeRef,
                            "The declaring type name is computed by collectImports. Forgot to call it?");
                    StringBuffer typeName = new StringBuffer(declaringTypeRef);
                    ITypeParameter[] typeParameters = declaringType.getTypeParameters();
                    if (typeParameters != null && typeParameters.length > 0) {
                        typeName.append("<");
                        for (int i = 0; i < typeParameters.length; i++) {
                            if (i > 0)
                                typeName.append(", ");
                            typeName.append(typeParameters[i].getElementName());
                        }
                        typeName.append(">");
                    }
                    typeName.append(".");
                    edits.addChild(new InsertEdit(nameStart, typeName.toString()));

                    // For constructors, must change name to "new"
                    if (member instanceof IMethod && ((IMethod) member).isConstructor()) {
                        edits.addChild(new ReplaceEdit(nameStart, member.getNameRange().getLength(), "new"));
                    }
                }

                // Add some newlines to the end for nicer spacing
                String newline = memberText.getLineDelimiter(0);
                if (newline == null) // We tried to use the same as in the memberText but it has none
                    newline = System.getProperty("line.separator");
                edits.addChild(new InsertEdit(memberEnd, newline + newline));

                // applying these edits should produce the ITD text
                edits.apply(memberText, TextEdit.NONE);
                return memberText.get();
            } finally {
                // This object has served it's purpose and should not be reused.
                dispose();
            }
        }

        /**
         * Destroy this object. Further use of the object will probably cause NPE exception.
         */
        private void dispose() {
            member = null;
            memberNode = null;
            memberText = null;
            edits = null;
            insertMods = null;
            declaringTypeRef = null;
        }

        /**
         * Was the original member protected. 
         */
        public boolean wasProtected() throws JavaModelException {
            return JdtFlags.isProtected(member);
        }

        /**
         * Was the original member public. 
         */
        public boolean wasPublic() throws JavaModelException {
            return JdtFlags.isPublic(member);
        }

        /**
         * Was the original member private. 
         */
        public boolean wasPrivate() throws JavaModelException {
            return JdtFlags.isPrivate(member);
        }

        /**
         * Was the original member abstract.
         */
        public boolean wasAbstract() throws JavaModelException {
            return JdtFlags.isAbstract(member);
        }

        /**
         * Was the original member "package visible" (i.e. it has no visibility
         * modifiers at all. 
         */
        public boolean wasPackageVisible() throws JavaModelException {
            return JdtFlags.isPackageVisible(member);
        }

        /**
         * Get the original member from which we are creating an ITD. 
         */
        public IMember getMember() {
            return member;
        }

        /**
         * Get the IJavaElement name of the original member. 
         */
        public String getElementName() {
            return member.getElementName();
        }

        public ASTNode getMemberNode() {
            return memberNode;
        }

        public void addModifier(int modFlag) {
            if (isVisibilityModifier(modFlag)) {
                //When adding a visiblity modifier, make sure to remove any preexisting
                //visibility modifiers first
                deleteMods |= VISIBILITY_MODIFIERS;
            }
            ModifierKeyword toAdd = ModifierKeyword.fromFlagValue(modFlag);
            String toAddStr = toAdd.toString();
            removeModifier(modFlag);
            if (!insertMods.contains(toAddStr)) {
                insertMods += toAddStr + " ";
            }
        }

        private boolean isVisibilityModifier(int modFlag) {
            return (modFlag & VISIBILITY_MODIFIERS) != 0;
        }

        /**
         * Replace the body of the method with given text. This is really only
         * supposed to be used to add method stubs for methods that where abstract
         * before.
         */
        public void setBody(String bodyText) {
            Object bodyNode = memberNode.getStructuralProperty(MethodDeclaration.BODY_PROPERTY);
            Assert.isTrue(bodyNode == null, "There already is a method body for this member: " + getMember());
            int startPos = memberText.get().lastIndexOf(';');
            edits.addChild(new ReplaceEdit(startPos, 1, bodyText));
        }

        /**
         * Is the original member a constructor (does not include the case where the original member is
         * already an ITD, even if that ITD introduces a constructor).
         */
        public boolean wasConstructorMethod() throws JavaModelException {
            return (member instanceof IMethod) && !wasIntertype() && ((IMethod) member).isConstructor();
        }

        /**
         * Determine whether this ITD's original member (which is assumed to be a constructor) has a
         * call to 'this()'
         */
        public boolean hasThisCall() throws JavaModelException {
            Assert.isNotNull(memberNode);
            Assert.isLegal(wasConstructorMethod());
            Block body = ((MethodDeclaration) memberNode).getBody();
            if (body == null)
                return false;
            @SuppressWarnings("unchecked")
            List<Statement> stms = body.statements();
            if (stms == null || stms.size() == 0)
                return false;
            Statement firstStm = stms.get(0);
            if (!(firstStm instanceof ConstructorInvocation))
                return false;
            ConstructorInvocation call = (ConstructorInvocation) firstStm;
            // The class ConstructorInvocation only represents "this(...)" calls 
            return call.arguments().isEmpty();
        }
    }

    private static final String MAKE_PRIVILEGED = "makePrivileged";
    private static final String MEMBER = "member";

    protected static final String ASPECT = "aspect";

    /**
     * The members to pull out, grouped by compilation unit for efficiency sake (
     * so we can process them one CU at a time)
     */
    private Map<ICompilationUnit, Collection<IMember>> memberMap;
    private HashSet<IMember> memberSet;

    /**
     * The target aspect to where the method should be moved.
     */
    private AspectElement targetAspect;

    /**
     * Should we make the aspect privileged
     */
    private boolean makePrivileged = false;

    /**
     * Allow pulling abstract methods. This deletes abstract keyword and generates
     * method stubs for "abstract" ITDs.
     */
    private boolean generateAbstractMethodStubs = false;

    /**
     * Allow the deletion of the protected keyword from ITDs (because this keyword
     * is not allowed on ITDs by AspectJ).
     */
    private boolean allowDeleteProtected = false;

    /**
     * Allow to make ITDs public to avoid breaking references to pulled members.
     */
    private boolean allowMakePublic;

    private IJavaProject javaProject;
    private AspectRewrite aspectChanges;

    public PullOutRefactoring() {
        clearMembers(); // initializes the member map and sets
    }

    public void addMember(IMember member, RefactoringStatus status) {
        ICompilationUnit cu = member.getCompilationUnit();
        Collection<IMember> members = getMembers(cu);
        members.add(member);
        memberSet.add(member);
        if (javaProject == null)
            javaProject = member.getJavaProject();
        else if (javaProject != member.getJavaProject())
            status.addError("Pull-out refactoring across multiple projects is not suppored", makeContext(member));
    }

    @Override
    public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
            throws CoreException, OperationCanceledException {
        RefactoringStatus status = new RefactoringStatus();
        SubProgressMonitor submonitor = new SubProgressMonitor(pm, memberMap.keySet().size());
        submonitor.beginTask("Checking preconditions...", memberMap.keySet().size());
        try {
            aspectChanges = new AspectRewrite();
            // For a more predictable and orderly outcome, sort by the name of the CU
            ICompilationUnit[] cus = memberMap.keySet().toArray(new ICompilationUnit[0]);
            Arrays.sort(cus, CompilationUnitComparator.the);
            for (ICompilationUnit cu : cus) {
                ASTParser parser = ASTParser.newParser(AST.JLS8);
                parser.setSource(cu);
                parser.setResolveBindings(true);
                ASTNode cuNode = parser.createAST(pm);
                for (IMember member : memberMap.get(cu)) {
                    BodyDeclaration memberNode = (BodyDeclaration) findASTNode(cuNode, member);
                    ITDCreator itd = new ITDCreator(member, memberNode);
                    if (member.getDeclaringType().isInterface()) {
                        // No need to check "isAllowMakePublic" since technically it was already public.
                        itd.addModifier(Modifier.PUBLIC);
                    }
                    if (itd.wasProtected()) {
                        if (isAllowDeleteProtected()) {
                            itd.removeModifier(Modifier.PROTECTED);
                        } else {
                            status.addWarning("moved member '" + member.getElementName() + "' is protected\n"
                                    + "protected ITDs are not allowed by AspectJ.\n", makeContext(member));
                        }
                    }
                    if (itd.wasAbstract()) {
                        if (isGenerateAbstractMethodStubs()) {
                            itd.removeModifier(Modifier.ABSTRACT);
                            itd.setBody(getAbstractMethodStubBody(member));
                        } else {
                            status.addWarning("moved member '" + member.getElementName() + "' is abstract.\n"
                                    + "abstract ITDs are not allowed by AspectJ.\n"
                                    + "You can enable the 'convert abstract methods' option to avoid this error.",
                                    makeContext(member));
                            //If you choose to ignore this error and perform refactoring anyway...
                            // We make sure the abstract keyword is added to the itd, so you will get a compile error
                            // and be forced to deal with that error.
                            itd.addModifier(Modifier.ABSTRACT);
                        }
                    }
                    checkOutgoingReferences(itd, status);
                    checkIncomingReferences(itd, status);
                    checkConctructorThisCall(itd, status);
                    aspectChanges.addITD(itd, status);
                }
                submonitor.worked(1);
            }
        } catch (BadLocationException e) {
            status.merge(RefactoringStatus.createFatalErrorStatus("Internal error:" + e.getMessage()));
        } finally {
            submonitor.done();
        }
        return status;
    }

    /**
     * Check whether the constructor is "safe" to pull out, or whether it might change
     * the meaning of the program (no longer executing initialiser code in target class).
     * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=318936
     */
    private void checkConctructorThisCall(ITDCreator itd, RefactoringStatus status) throws JavaModelException {
        if (itd.wasConstructorMethod() && !itd.hasThisCall()) {
            status.addWarning("Program semantics changed: moved '" + itd.getElementName()
                    + "' constructor has no this() call. Initializers in the target class will not be executed "
                    + "by the intertype constructor", makeContext(itd.getMember()));
        }
    }

    /**
     * Find AST node corresponding to a given IMember.
     */
    private ASTNode findASTNode(ASTNode cuNode, IMember member) throws JavaModelException {
        ISourceRange range = member.getSourceRange();
        NodeFinder finder = new NodeFinder(cuNode, range.getOffset(), range.getLength());
        return finder.getCoveredNode();
        // Note: why we *have* to use getCoveredNode explicitly rather than use the
        // perform methods defined on NodeFinder.
        // See BUG 316945: Normally, we have exact positions and covering/covered are the same node.
        // but in the BUG case we should use the covered node since a JDT bug makes the source range 
        // be too large.
    }

    /**
     * Check whether references to moved elements become broken. Update status message
     * accordingly (but only if allowModifierConversion is set to false).
     * 
     * @return true if no references become broken
     */
    private boolean checkIncomingReferences(ITDCreator movedMember, RefactoringStatus status) throws CoreException {
        if (movedMember.wasPublic())
            return true; //Always ok if member was already public
        boolean ok = true;
        IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaElement[] { javaProject });
        SearchPattern pattern = SearchPattern.createPattern(movedMember.getMember(),
                IJavaSearchConstants.REFERENCES);
        SearchEngine engine = new SearchEngine();
        final Set<SearchMatch> references = new HashSet<SearchMatch>();
        engine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, scope,
                new SearchRequestor() {
                    @Override
                    public void acceptSearchMatch(SearchMatch match) throws CoreException {
                        if (match.getAccuracy() == SearchMatch.A_ACCURATE && !match.isInsideDocComment())
                            references.add(match);
                    }
                }, new NullProgressMonitor());

        String referredPkg = getPackageName(targetAspect); // since the element is moved it's package *will* be...
        for (SearchMatch match : references) {
            if (match.getElement() instanceof IJavaElement) {
                IJavaElement referingElement = (IJavaElement) match.getElement();
                if (!isMoved(referingElement)) {
                    if (movedMember.wasPrivate()) {
                        ok = false;
                        if (isAllowMakePublic()) {
                            movedMember.addModifier(Modifier.PUBLIC);
                        } else {
                            status.addWarning(
                                    "The moved private member '" + movedMember.getElementName()
                                            + "' will not be accessible" + " after refactoring.",
                                    makeContext(match));
                        }
                    } else if (movedMember.wasPackageVisible() || movedMember.wasProtected()) {
                        String referringPkg = getPackageName(referingElement);
                        if (referringPkg != null && !referringPkg.equals(referredPkg)) {
                            ok = false;
                            if (isAllowMakePublic()) {
                                movedMember.addModifier(Modifier.PUBLIC);
                            } else {
                                status.addWarning(
                                        "The moved member '" + movedMember.getElementName()
                                                + "' may not be accessible " + "after refactoring",
                                        makeContext(match));
                            }
                        }
                    }
                }
            }
        }
        return ok;
    }

    /**
     * Retrieve package name of a IJavaElement.
     * @return The name of the package, or null if the IJavaElement is not nested inside a IPackagFragment.
     */
    private String getPackageName(IJavaElement el) {
        IPackageFragment pkg = (IPackageFragment) el.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
        if (pkg == null)
            return null;
        return pkg.getElementName();
    }

    @Override
    public RefactoringStatus checkInitialConditions(IProgressMonitor monitor)
            throws CoreException, OperationCanceledException {

        RefactoringStatus status = new RefactoringStatus();
        monitor.beginTask("Checking preconditions...", 1);
        try {
            if (memberMap == null || memberMap.isEmpty())
                status.merge(RefactoringStatus.createFatalErrorStatus("No pullout targets have been specified."));
            else {
                for (ICompilationUnit cu : memberMap.keySet()) {
                    for (IMember member : memberMap.get(cu)) {
                        if (!member.exists()) {
                            status.merge(RefactoringStatus.createFatalErrorStatus(MessageFormat.format(
                                    "Member ''{0}'' does not exist.", new Object[] { member.getElementName() })));
                        } else if (!isInTopLevelType(member)) {
                            status.merge(RefactoringStatus.createFatalErrorStatus(MessageFormat.format(
                                    "Member ''{0}'' is not directly nested in a top-level type.",
                                    new Object[] { member.getElementName() })));
                        } else if (member.isBinary()) {
                            status.merge(RefactoringStatus.createFatalErrorStatus(MessageFormat.format(
                                    "Member ''{0}'' is not in source code. Binary methods can not be refactored.",
                                    new Object[] { member.getElementName() })));
                        } else if (!member.getCompilationUnit().isStructureKnown()) {
                            status.merge(RefactoringStatus.createFatalErrorStatus(
                                    MessageFormat.format("Compilation unit ''{0}'' contains compile errors.",
                                            new Object[] { cu.getElementName() })));
                        }
                    }
                }
            }
        } finally {
            monitor.done();
        }
        return status;
    }

    private void checkOutgoingReferences(final ITDCreator itd, final RefactoringStatus status)
            throws CoreException, OperationCanceledException {
        if (willBePrivileged())
            return; //Always OK!

        // Walk the AST to find problematic references (e.g. references to private members from within the moved method)
        itd.getMemberNode().accept(new ASTVisitor() {
            /**
             * Check for various problems that may be caused by moving a reference into
             * a different context.
             */
            private void checkReference(ASTNode node, final IBinding binding, RefactoringStatus status) {
                if (isField(binding) || isMethod(binding) || isType(binding)) {
                    if (isTypeParameter(binding))
                        return; //Exclude these, or they'll look like package restricted types in code below.
                    if (isMoved(binding))
                        return; //OK: anything moved to the aspect will be accessible from the aspect
                    int mods = binding.getModifiers();
                    if (Modifier.isPrivate(mods)) {
                        status.addWarning(
                                "private member '" + binding.getName()
                                        + "' accessed and refactored aspect is not privileged",
                                makeContext(itd.getMember(), node));
                    }
                    if (JdtFlags.isProtected(binding) || JdtFlags.isPackageVisible(binding)) {
                        // FIXKDV: separate case for protected
                        // These are really two separate cases, but the cases where this matters (i.e.
                        // aspects that have a super type are rare so I'm not dealing with that
                        // right now (this is relatively harmless: will result in a spurious warning message 
                        // in rare case where the pulled member is protected and is pulled from target aspect's
                        // supertype that is not in the same package as target aspect)
                        String referredPkg = getPackageName(binding.getJavaElement());
                        if (referredPkg != null) {
                            //If it has no package, we'll just ignore it, whatever it is, it's probably not subject to
                            // package scope :-)
                            String aspectPkg = targetAspect.getPackageFragment().getElementName();
                            if (!referredPkg.equals(aspectPkg)) {
                                String keyword = JdtFlags.isProtected(binding) ? "protected" : "package restricted";
                                status.addWarning(
                                        keyword + " member '" + binding.getName()
                                                + "' is accessed and refactored aspect is not privileged",
                                        makeContext(itd.getMember(), node));
                            }
                        }
                    }
                }
            }

            private boolean isField(IBinding binding) {
                return (binding instanceof IVariableBinding) && ((IVariableBinding) binding).isField();
            }

            private boolean isMethod(IBinding binding) {
                return (binding instanceof IMethodBinding);
            }

            private boolean isType(IBinding binding) {
                return binding instanceof ITypeBinding;
            }

            private boolean isTypeParameter(IBinding binding) {
                return binding instanceof ITypeBinding
                        && (((ITypeBinding) binding).isCapture() || ((ITypeBinding) binding).isTypeVariable());
            }

            @Override
            public boolean visit(SimpleName node) {
                IBinding binding = node.resolveBinding();
                checkReference(node, binding, status);
                return true;
            }

        });
    }

    private void clearMembers() {
        memberMap = new HashMap<ICompilationUnit, Collection<IMember>>();
        memberSet = new HashSet<IMember>();
        javaProject = null;
    }

    @Override
    public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
        try {
            pm.beginTask("Creating changes...", memberMap.keySet().size());
            CompositeChange allChanges = new CompositeChange("PullOut ITDs");
            for (ICompilationUnit cu : memberMap.keySet()) {

                // Prepare an ASTRewriter for this compilation unit
                ASTParser parser = ASTParser.newParser(AST.JLS8);
                parser.setSource(cu);
                ASTNode cuNode = parser.createAST(pm);
                MultiTextEdit cuEdits = new MultiTextEdit();

                // Apply all operations to the AST rewriter
                for (IMember member : getMembers(cu)) {
                    ISourceRange range = member.getSourceRange();
                    range = grabSpaceBefore(cu, range);
                    cuEdits.addChild(new DeleteEdit(range.getOffset(), range.getLength()));
                }

                // Create CUChange object with the accumulated deletion edits.
                CompilationUnitChange cuChanges = newCompilationUnitChange(cu);
                cuChanges.setEdit(cuEdits);

                // Add changes for this compilation unit.
                allChanges.add(cuChanges);

                pm.worked(1);
            }
            aspectChanges.rewriteAspect(pm, allChanges);
            return allChanges;
        } finally {
            pm.done();
        }
    }

    /**
     * For cosmetic reasons (nicer indentation of resulting text after deletion of membernode
     * we force the nodes sourcerange to include any spaces in front of the node, upto the 
     * beginning of the line.
     * @param cu 
     * @return 
     */
    private ISourceRange grabSpaceBefore(ICompilationUnit cu, ISourceRange range) {
        try {
            IBuffer sourceText = cu.getBuffer();
            int start = range.getOffset();
            int len = range.getLength();
            while (start > 0 && isSpace(sourceText.getChar(start - 1))) {
                start--;
                len++;
            }
            return new SourceRange(start, len);
        } catch (JavaModelException e) {
            //This operation is not essential, so it is fine if it silently fails.
            return range;
        }
    }

    private boolean isSpace(char c) {
        return c == ' ' || c == '\t';
    }

    private CompilationUnitChange newCompilationUnitChange(ICompilationUnit cu) {
        CompilationUnitChange cuChange = new CompilationUnitChange("PullOut ITDs", cu);
        if (targetAspect.getCompilationUnit() == cu) {
            //Also use this cuChange object for the aspect changes
            aspectChanges.setCUChange(cuChange);
        }
        return cuChange;
    }

    /**
     * @return The target aspect where we will create the intertype declarations.
     */
    public AspectElement getAspect() {
        return targetAspect;
    }

    public String getAspectName() {
        AspectElement theAspect = getAspect();
        if (theAspect == null)
            return "";
        return theAspect.getFullyQualifiedName();
    }

    /**
     * Compute the location where new ITDs will be inserted in the target aspect's source code.
     */
    private int getInsertLocation() {
        try {
            return targetAspect.getSourceRange().getOffset() + targetAspect.getSourceRange().getLength() - 1;
        } catch (JavaModelException e) {
            return 0;
        }
    }

    IJavaProject getJavaProject() {
        return javaProject;
    }

    public IMember[] getMembers() {
        List<IMember> members = new ArrayList<IMember>();
        for (ICompilationUnit cu : memberMap.keySet()) {
            members.addAll(getMembers(cu));
        }
        return members.toArray(new IMember[members.size()]);
    }

    private Collection<IMember> getMembers(ICompilationUnit cu) {
        Collection<IMember> result = memberMap.get(cu);
        if (result == null) {
            result = new ArrayList<IMember>();
            memberMap.put(cu, result);
        }
        return result;
    }

    @Override
    public String getName() {
        return "Pull-Out";
    }

    public boolean hasMembers() {
        return !memberSet.isEmpty();
    }

    public RefactoringStatus initialize(Map<String, String> args) {
        RefactoringStatus status = new RefactoringStatus();
        setMakePrivileged(Boolean.valueOf(args.get(MAKE_PRIVILEGED)));
        setMember((IMember) JavaCore.create(args.get(MEMBER)), status);
        return status;
    }

    private boolean isInTopLevelType(IMember member) {
        IJavaElement parent = member.getParent();
        Assert.isTrue(parent.getElementType() == IJavaElement.TYPE);
        return parent.getParent().getElementType() == IJavaElement.COMPILATION_UNIT;
    }

    /**
     * Is the "make privileged" option of the refactoring set.
     */
    public boolean isMakePrivileged() {
        return makePrivileged;
    }

    /**
     * Does given IBinding refer to an IJavaElement that will be moved into the Aspect?
     */
    private boolean isMoved(IBinding binding) {
        return isPulled(binding.getJavaElement());
    }

    /**
     * Will the given IJavaElement be moved into the Aspect? This test returns true, also for
     * IJavaElements that are contained inside pulled elements!
     */
    public boolean isMoved(IJavaElement javaElement) {
        //Null case is handled to easily terminate recursion
        return javaElement != null && (isPulled(javaElement) || isMoved(javaElement.getParent()));
    }

    /**
     * Is the target aspect privileged before the refactoring?
     */
    private boolean isPrivileged() {
        if (targetAspect == null)
            return false;
        try {
            return targetAspect.isPrivileged();
        } catch (JavaModelException e) {
            return false;
        }
    }

    /**
     * Are we allowed to delete the abstract modifier and fabricate
     * dummy method bodies?
     */
    public boolean isGenerateAbstractMethodStubs() {
        return generateAbstractMethodStubs;
    }

    /**
     * Allow pulling out of abstract methods. The abstract keyword will
     * be removed and a dummy method body added.
     */
    public void setGenerateAbstractMethodStubs(boolean allow) {
        this.generateAbstractMethodStubs = allow;
    }

    /**
     * Allow ITDs to be made public, as needed.
     */
    public void setAllowMakePublic(boolean allow) {
        this.allowMakePublic = allow;
    }

    /**
     * Allow ITDs to be made public, as needed.
     */
    public void setAllowDeleteProtected(boolean allow) {
        this.allowDeleteProtected = allow;
    }

    /**
     * Are we allowed to delete any visibility modifier (on ITDs)
     * and make the ITD public?
     */
    public boolean isAllowMakePublic() {
        return allowMakePublic;
    }

    /**
     * Are we allowed to delete the protected keyword from ITDs?
     */
    public boolean isAllowDeleteProtected() {
        return allowDeleteProtected || allowMakePublic;
    }

    /**
     * Is the given IJaveElement selected to be pulled into the Aspect. Elements moved because they are 
     * nested inside selected elements are *not* considered (if you want this, use isMoved instead).
     */
    private boolean isPulled(IJavaElement javaElement) {
        return memberSet.contains(javaElement);
    }

    private static RefactoringStatusContext makeContext(ICompilationUnit cu, ASTNode node) {
        return JavaStatusContext.create(cu, new SourceRange(node.getStartPosition(), node.getLength()));
    }

    private static RefactoringStatusContext makeContext(IMember member) {
        try {
            return JavaStatusContext.create(member.getCompilationUnit(), member.getSourceRange());
        } catch (JavaModelException e) {
            return null; // Too bad, no context for the error message
        }
    }

    static RefactoringStatusContext makeContext(IMember member, ASTNode node) {
        return makeContext(member.getCompilationUnit(), node);
    }

    private static RefactoringStatusContext makeContext(SearchMatch match) {
        try {
            IJavaElement element = (IJavaElement) match.getElement();
            ITypeRoot typeRoot = (ITypeRoot) element.getAncestor(IJavaElement.COMPILATION_UNIT);
            if (typeRoot == null) {
                typeRoot = (ITypeRoot) element.getAncestor(IJavaElement.CLASS_FILE);
            }
            ISourceRange range = new SourceRange(match.getOffset(), match.getLength());
            return JavaStatusContext.create(typeRoot, range);
        } catch (Throwable e) {
            return null;
        }
    }

    public void setAspect(AspectElement target) {
        this.targetAspect = target;
    }

    /**
     * Set the target aspect by giving the name of the aspect. Note that this method
     * only works if it can figure out what project to look for the aspect, so at least
     * one member to be pulled out has to be set prior to calling this method.
     */
    public RefactoringStatus setAspect(String name) {
        IType type = null;

        try {
            if (name.length() == 0)
                return RefactoringStatus.createFatalErrorStatus("Select an Aspect.");

            type = getJavaProject().findType(name, new NullProgressMonitor());
            if (type == null || !type.exists())
                return RefactoringStatus
                        .createErrorStatus(MessageFormat.format("Aspect ''{0}'' does not exist.", name));
            if (!(type instanceof AspectElement))
                return RefactoringStatus.createErrorStatus(MessageFormat.format("''{0}'' is not an Aspect.", name));
        } catch (JavaModelException exception) {
            return RefactoringStatus.createFatalErrorStatus("Could not determine type.");
        }

        if (type.isReadOnly())
            return RefactoringStatus.createFatalErrorStatus("Type is read-only.");

        if (type.isBinary())
            return RefactoringStatus.createFatalErrorStatus("Type is binary.");

        targetAspect = (AspectElement) type;

        return new RefactoringStatus();
    }

    /**
     * Set the "make privileged" option of the refactoring.
     */
    public void setMakePrivileged(boolean makePrivileged) {
        this.makePrivileged = makePrivileged;
    }

    public void setMember(IMember member, RefactoringStatus status) {
        clearMembers();
        addMember(member, status);
    }

    /**
     * Will the target aspect be privileged after refactoring
     */
    public boolean willBePrivileged() {
        return isPrivileged() || isMakePrivileged();
    }

    protected String getAbstractMethodStubBody(IMember originalMember) {
        //FIXKDV: Stupid implementation for now... maybe we can use the Eclipse Java Code templates somehow
        // or have the user specify their own abstract method stub template in the wizard.
        return " { throw new Error(\"abstract method stub\"); }";
    }
}