org.eclipse.ajdt.internal.ui.refactoring.PushInRefactoring.java Source code

Java tutorial

Introduction

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

Source

/*******************************************************************************
 * Copyright (c) 2009 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:
 *     Andrew Eisenberg - initial API and implementation
 *******************************************************************************/

package org.eclipse.ajdt.internal.ui.refactoring;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.aspectj.asm.IProgramElement;
import org.aspectj.asm.IProgramElement.Kind;
import org.eclipse.ajdt.core.AspectJCore;
import org.eclipse.ajdt.core.codeconversion.AspectsConvertingParser;
import org.eclipse.ajdt.core.javaelements.AJCompilationUnit;
import org.eclipse.ajdt.core.javaelements.DeclareElement;
import org.eclipse.ajdt.core.javaelements.DeclareElementInfo;
import org.eclipse.ajdt.core.javaelements.IAspectJElement;
import org.eclipse.ajdt.core.javaelements.IntertypeElement;
import org.eclipse.ajdt.core.model.AJProjectModelFacade;
import org.eclipse.ajdt.core.model.AJProjectModelFactory;
import org.eclipse.ajdt.core.model.AJRelationshipManager;
import org.eclipse.ajdt.core.model.AJRelationshipType;
import org.eclipse.ajdt.ui.AspectJUIPlugin;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.IAnnotatable;
import org.eclipse.jdt.core.IAnnotation;
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.ISourceRange;
import org.eclipse.jdt.core.IType;
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.ASTRequestor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.NameLookup;
import org.eclipse.jdt.internal.core.NameLookup.Answer;
import org.eclipse.jdt.internal.corext.codemanipulation.ImportReferencesCollector;
import org.eclipse.jface.text.Region;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.ChangeDescriptor;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.ltk.core.refactoring.resource.DeleteResourceChange;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;

/**
 * @author Andrew Eisenberg
 * @created Apr 30, 2009
 *
 */
public class PushInRefactoring extends Refactoring {

    public static final String ALL_ITDS = "all.itds";
    public static final String DELETE_EMPTY = "delete.empty";

    /**
     * Since each class is visited multiple times, and each visit may introduce
     * new imports, we must delay import rewriting
     * until each class has its ITDs already pushed in.
     *
     */
    private class PerUnitInformation {
        final Set<SimpleName> staticImports;
        final Set<SimpleName> typeImports;
        final Set<String> extraImports;
        final ICompilationUnit unit;

        // all declare parents (extends) to insert
        final Map<IType, Set<String>> declareParents;

        PerUnitInformation(ICompilationUnit unit) {
            staticImports = new HashSet<SimpleName>();
            typeImports = new HashSet<SimpleName>();
            extraImports = new HashSet<String>();
            this.unit = unit;
            declareParents = new HashMap<IType, Set<String>>();
        }

        // does not handle imports for declare parents
        void rewriteImports() throws CoreException {
            // first check to see if this unit has been deleted.
            Change change = (Change) allChanges.get(unit);
            if (!(change instanceof TextFileChange)) {
                return;
            }

            ImportRewrite rewrite = ImportRewrite.create(unit, true);
            for (Name name : typeImports) {
                ITypeBinding binding = name.resolveTypeBinding();
                if (binding != null) {
                    rewrite.addImport(binding);
                }
            }
            for (Name name : staticImports) {
                rewrite.addImport(name.resolveTypeBinding());
            }

            for (String qualName : extraImports) {
                rewrite.addImport(qualName);
            }
            TextEdit importEdit = rewrite.rewriteImports(new NullProgressMonitor());

            TextFileChange textChange = (TextFileChange) change;
            textChange.getEdit().addChild(importEdit);
        }

        void computeImports(List<IMember> itds, ICompilationUnit ajUnit, IProgressMonitor monitor)
                throws JavaModelException {
            IJavaProject project = ajUnit.getJavaProject();
            ASTParser parser = ASTParser.newParser(AST.JLS8);
            parser.setProject(project);
            parser.setResolveBindings(true);
            parser.setSource(ajUnit);
            parser.setKind(ASTParser.K_COMPILATION_UNIT);
            CompilationUnit ajAST = (CompilationUnit) parser.createAST(monitor);

            for (IMember itd : itds) {
                ISourceRange range = itd.getSourceRange();
                ImportReferencesCollector.collect(ajAST, project, new Region(range.getOffset(), range.getLength()),
                        typeImports, staticImports);
                if (itd instanceof DeclareElement) {
                    DeclareElement declare = (DeclareElement) itd;
                    // no types added for declare removers
                    if (!((DeclareElementInfo) declare.getElementInfo()).isAnnotationRemover()) {
                        String qualType = getQualifiedTypeForDeclareAnnotation(declare);
                        if (qualType != null && qualType.length() > 0) {
                            extraImports.add(qualType);
                        }
                        List<Type> types = getExtraImportsFromDeclareElement(declare, ajAST);
                        for (Type type : types) {
                            ImportReferencesCollector.collect(type, project,
                                    new Region(type.getStartPosition(), type.getLength()), typeImports,
                                    staticImports);
                        }
                    }
                }
            }
        }

        // This method finds the extra imports required by a declare element
        // (eg- used inside of a declare @annotation's annotation)
        // We take advantage of the format of the converted source from aspectj to java
        // the last fields of the last type when following a particular naming convention, 
        // are around to force import statements to exist.  We can read them and use them 
        // to seed the extra imports list
        private List<Type> getExtraImportsFromDeclareElement(DeclareElement itd, CompilationUnit ajAST) {
            int numTypes = ajAST.types().size();
            if (numTypes == 0) {
                return Collections.emptyList();
            }

            String details = null;
            AbstractTypeDeclaration lastType = (AbstractTypeDeclaration) ajAST.types().get(numTypes - 1);
            @SuppressWarnings("unchecked")
            List<BodyDeclaration> bodyDecls = lastType.bodyDeclarations();
            List<Type> extraSimpleNames = new LinkedList<Type>();
            for (int i = bodyDecls.size() - 1; i >= 0; i--) {
                BodyDeclaration decl = (BodyDeclaration) bodyDecls.get(i);
                if (decl.getNodeType() == ASTNode.FIELD_DECLARATION) {
                    FieldDeclaration fDecl = (FieldDeclaration) decl;
                    if (fDecl.fragments().size() == 1) {
                        VariableDeclarationFragment frag = (VariableDeclarationFragment) fDecl.fragments().get(0);
                        if (frag.getName().toString().startsWith(AspectsConvertingParser.ITD_INSERTED_IDENTIFIER)) {
                            if (details == null) {
                                IProgramElement ipe = getModel(itd).javaElementToProgramElement(itd);
                                details = ipe.getDetails();
                            }
                            Type type = fDecl.getType();
                            // only add if this type exists in the declare @annotation 
                            if (details.indexOf(type.toString()) != -1) {
                                extraSimpleNames.add(type);
                            }

                            continue;
                        }
                    }
                }
                // break on the first body declaration that does not conform
                break;
            }
            return extraSimpleNames;
        }

        public void addDeclarParents(IType type, List<String> parentTypes) {
            Set<String> set = declareParents.get(type);
            if (set == null) {
                set = new HashSet<String>();
                declareParents.put(type, set);
            }
            set.addAll(parentTypes);
            for (String parent : parentTypes) {
                String[] split = parent.split("<|>|,");
                for (String name : split) {
                    extraImports.add(name.trim());
                }
            }
        }
    }

    private boolean deleteEmpty = true;

    private Map<ICompilationUnit, Change> allChanges = null;

    private List<IMember> itds = null;

    private Map<IProject, AJProjectModelFacade> allModels = new HashMap<IProject, AJProjectModelFacade>();

    private AJProjectModelFacade getModel(IJavaElement elt) {
        IProject project = elt.getJavaProject().getProject();
        AJProjectModelFacade model = allModels.get(project);
        if (model == null) {
            model = AJProjectModelFactory.getInstance().getModelForProject(project);
            allModels.put(project, model);
        }
        return model;
    }

    private Map<IJavaProject, NameLookup> allLookups = new HashMap<IJavaProject, NameLookup>();

    private NameLookup getLookup(IJavaElement elt) throws JavaModelException {
        IJavaProject javaProject = elt.getJavaProject();
        NameLookup nameLookup = allLookups.get(javaProject);
        if (nameLookup == null) {
            nameLookup = ((JavaProject) javaProject).newNameLookup(DefaultWorkingCopyOwner.PRIMARY);
            allLookups.put(javaProject, nameLookup);
        }
        return nameLookup;
    }

    public RefactoringStatus checkFinalConditions(IProgressMonitor monitor)
            throws CoreException, OperationCanceledException {
        final RefactoringStatus status = new RefactoringStatus();
        try {
            monitor.beginTask("Checking final conditions...", 2);
            allChanges = new LinkedHashMap<ICompilationUnit, Change>();
            // map from AJCUs to contained ITDs that will be pushed in
            Map<ICompilationUnit, List<IMember>> unitToITDs = new HashMap<ICompilationUnit, List<IMember>>();
            Map<ICompilationUnit, PerUnitInformation> importsMap = new HashMap<ICompilationUnit, PerUnitInformation>();

            for (IMember itd : itds) {
                if (itd instanceof IAspectJElement && ((IAspectJElement) itd).getAJKind() == Kind.DECLARE_PARENTS) {
                    AJProjectModelFacade model = getModel(itd);
                    // rememebr the types pushed in and associate them with the target types
                    List<IJavaElement> elts = model.getRelationshipsForElement(itd,
                            AJRelationshipManager.DECLARED_ON);
                    IProgramElement ipe = model.javaElementToProgramElement(itd);
                    List<String> parentTypes = ipe.getParentTypes();
                    for (IJavaElement elt : elts) {
                        if (elt.getElementType() == IJavaElement.TYPE) {
                            IType type = (IType) elt;
                            ICompilationUnit owningUnit = type.getCompilationUnit();
                            PerUnitInformation holder = importsMap.get(owningUnit);
                            if (holder == null) {
                                holder = new PerUnitInformation(owningUnit);
                                importsMap.put(owningUnit, holder);
                            }
                            holder.addDeclarParents(type, parentTypes);
                        }
                    }
                }

                // remember the ITDs per compilation unit so that we can remove them later
                ICompilationUnit unit = itd.getCompilationUnit();
                List<IMember> itdList = unitToITDs.get(unit);
                if (itdList == null) {
                    itdList = new LinkedList<IMember>();
                    unitToITDs.put(unit, itdList);
                }
                itdList.add(itd);
            }

            // now do the work ITDs and declare annotation
            for (Map.Entry<ICompilationUnit, List<IMember>> entry : unitToITDs.entrySet()) {
                status.merge(checkFinalConditionsForITD(entry.getKey(), entry.getValue(), importsMap, monitor));
            }

            // now go through and create the import edits
            for (PerUnitInformation holder : importsMap.values()) {
                holder.rewriteImports();
            }

        } finally {
            allLookups.clear();
            allModels.clear();
            monitor.done();
        }
        return status;
    }

    /**
     * Checks the conditions for a single {@link AJCompilationUnit}
     * @param ajUnit the unit to check
     * @param itdsForUnit all itds in this unit
     * @param imports a map from target {@link ICompilationUnit} to imports that need to be added
     * initially empty, but populated in this method
     * @param monitor
     * @return
     * @throws JavaModelException
     */
    private RefactoringStatus checkFinalConditionsForITD(final ICompilationUnit ajUnit,
            final List<IMember> itdsForUnit, final Map<ICompilationUnit, PerUnitInformation> imports,
            final IProgressMonitor monitor) throws JavaModelException {

        final RefactoringStatus status = new RefactoringStatus();

        // group all of the ITD targets by the ICompilationUnit that they are in
        final Map<ICompilationUnit, Set<IMember>> unitsToTypes = getUnitTypeMap(getTargets(itdsForUnit));

        // group all of the ICompilationUnits by project
        final Map<IJavaProject, Collection<ICompilationUnit>> projects = new HashMap<IJavaProject, Collection<ICompilationUnit>>();
        for (ICompilationUnit targetUnit : unitsToTypes.keySet()) {
            IJavaProject project = targetUnit.getJavaProject();
            if (project != null) {
                Collection<ICompilationUnit> collection = projects.get(project);
                if (collection == null) {
                    collection = new ArrayList<ICompilationUnit>();
                    projects.put(project, collection);
                }
                collection.add(targetUnit);
            }
        }

        // also add the ajunit to the collection of affected units
        Collection<ICompilationUnit> units;
        IJavaProject aspectProject = ajUnit.getJavaProject();
        if (projects.containsKey(aspectProject)) {
            units = projects.get(aspectProject);
        } else {
            units = new ArrayList<ICompilationUnit>();
            projects.put(aspectProject, units);
        }
        units.add(ajUnit);

        // this requestor performs the real work
        ASTRequestor requestors = new ASTRequestor() {
            public void acceptAST(ICompilationUnit source, CompilationUnit ast) {
                try {

                    // compute the imports that this itd adds to this unit
                    PerUnitInformation holder;
                    if (imports.containsKey(source)) {
                        holder = (PerUnitInformation) imports.get(source);
                    } else {
                        holder = new PerUnitInformation(source);
                        imports.put(source, holder);
                    }
                    holder.computeImports(itdsForUnit, ajUnit, monitor);

                    for (Entry<IType, Set<String>> entry : holder.declareParents.entrySet()) {
                        rewriteDeclareParents(entry.getKey(), ast, entry.getValue(), source);
                    }

                    // make the simplifying assumption that the CU that contains the
                    // ITD does not also contain a target type
                    // Bug 310020
                    if (isCUnitContainingITD(source, (IMember) itdsForUnit.get(0))) {
                        // this is an AJCU.
                        rewriteAspectType(itdsForUnit, source, ast);
                    } else {
                        // this is a regular CU

                        for (IMember itd : itdsForUnit) {

                            // filter out the types not affected by itd 
                            Collection<IMember> members = new ArrayList<IMember>();
                            members.addAll(unitsToTypes.get(source));
                            AJProjectModelFacade model = getModel(itd);
                            List<IJavaElement> realTargets;
                            if (itd instanceof IAspectJElement
                                    && ((IAspectJElement) itd).getAJKind().isDeclareAnnotation()) {
                                realTargets = model.getRelationshipsForElement(itd,
                                        AJRelationshipManager.ANNOTATES);
                            } else {
                                // regular ITD or an ITIT
                                realTargets = model.getRelationshipsForElement(itd,
                                        AJRelationshipManager.DECLARED_ON);
                            }
                            for (Iterator<IMember> memberIter = members.iterator(); memberIter.hasNext();) {
                                IMember member = memberIter.next();
                                if (!realTargets.contains(member)) {
                                    memberIter.remove();
                                }
                            }

                            if (members.size() > 0) {
                                // if declare parents, store until later
                                if (itd instanceof IAspectJElement
                                        && ((IAspectJElement) itd).getAJKind() == Kind.DECLARE_PARENTS) {
                                    // already taken care of
                                }
                                applyTargetTypeEdits(itd, source, members);
                            }
                        } // for (Iterator itdIter = itdsForUnit.iterator(); itdIter.hasNext();) {
                    }
                } catch (JavaModelException e) {
                } catch (CoreException e) {
                }
            }
        };
        IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 1);
        try {
            try {
                final Set<IJavaProject> set = projects.keySet();
                subMonitor.beginTask("Compiling source...", set.size());
                for (IJavaProject project : projects.keySet()) {
                    ASTParser parser = ASTParser.newParser(AST.JLS8);
                    parser.setProject(project);
                    parser.setResolveBindings(true);
                    Collection<ICompilationUnit> collection = projects.get(project);
                    parser.createASTs(
                            (ICompilationUnit[]) collection.toArray(new ICompilationUnit[collection.size()]),
                            new String[0], requestors, new SubProgressMonitor(subMonitor, 1));
                }

            } finally {
                subMonitor.done();
            }

        } finally {
            subMonitor.done();
        }
        return status;
    }

    /**
     * Removes all ITDs in the given compilation unit.
     * Will delete an aspect if it has no more members.
     * Will delete an {@link AJCompilationUnit} if all 
     * of its types are deleted.
     * @param itdsForUnit the ITDs for the given unit
     * @param source 
     * @param ast used to calculate imports
     * @throws JavaModelException
     * @throws CoreException
     */
    protected void rewriteAspectType(List<IMember> itdsForUnit, ICompilationUnit source, CompilationUnit ast)
            throws JavaModelException, CoreException {

        // used to keep track of aspect types that are deleted.
        Map<IType, Integer> removalStored = new HashMap<IType, Integer>();
        Map<IType, List<DeleteEdit>> typeDeletes = new HashMap<IType, List<DeleteEdit>>();

        // go through each ITD and create a delete edit for it
        for (IMember itd : itdsForUnit) {
            IType parentAspectType = (IType) itd.getParent();
            int numRemovals;
            if (removalStored.containsKey(parentAspectType)) {
                numRemovals = ((Integer) removalStored.get(parentAspectType)).intValue();
                removalStored.put(parentAspectType, new Integer(++numRemovals));
            } else {
                removalStored.put(parentAspectType, new Integer(1));
            }
            List<DeleteEdit> deletes;
            if (typeDeletes.containsKey(parentAspectType)) {
                deletes = typeDeletes.get(parentAspectType);
            } else {
                deletes = new LinkedList<DeleteEdit>();
                typeDeletes.put(parentAspectType, deletes);
            }

            DeleteEdit edit = new DeleteEdit(itd.getSourceRange().getOffset(),
                    itd.getSourceRange().getLength() + 1);
            deletes.add(edit);
        }

        if (deleteTypes(ast, typeDeletes, removalStored)) {
            allChanges.put(source, new DeleteResourceChange(source.getResource().getFullPath(), false));
        } else {
            applyAspectEdits(source, typeDeletes);
        }
    }

    /**
     * Adds the specified new parents to the type.
     * Need to determine if the new paretns are extends or implements
     * 
     * FIXADE will not handle generic types
     * @param targetType
     * @param astUnit
     * @param newParents
     * @param holder
     * @throws JavaModelException 
     */
    @SuppressWarnings("unchecked")
    private void rewriteDeclareParents(IType targetType, CompilationUnit astUnit, Set<String> newParents,
            ICompilationUnit unit) throws JavaModelException {

        // find the Type declaration in the ast
        TypeDeclaration typeDecl = findType(astUnit, targetType.getElementName());
        if (typeDecl == null) {
            createJavaModelException(
                    "Couldn't find type " + targetType.getElementName() + " in " + unit.getElementName());
        }

        // convert all parents to simple names
        List<String> simpleParents = new ArrayList<String>(newParents.size());
        for (String qual : newParents) {
            simpleParents.add(convertToSimple(qual));
        }

        // now remove any possible duplicates
        Type superclassType = typeDecl.getSuperclassType();
        Type supr = superclassType;
        if (supr != null && supr.isSimpleType()) {
            simpleParents.remove(((SimpleType) supr).getName().getFullyQualifiedName());
        }
        for (Type iface : (Iterable<Type>) typeDecl.superInterfaceTypes()) {
            if (iface.isSimpleType()) {
                simpleParents.remove(((SimpleType) iface).getName().getFullyQualifiedName());
            }
        }

        // Find the super class if exists
        // make assumption that there is at most one super class defined.
        // if this weren't the case, then there would be a compile error
        // and it would not be possible to invoke refactoring
        String newSuper = null;
        for (String parent : newParents) {
            if (isClass(parent, targetType)) {
                newSuper = convertToSimple(parent);
                simpleParents.remove(newSuper);
            }
        }

        // do the rewrite.  Only need to add simple names since imports are already taken care of
        // in the holder
        ASTRewrite rewriter = ASTRewrite.create(astUnit.getAST());
        AST ast = typeDecl.getAST();
        if (newSuper != null) {
            Type newSuperType = createTypeAST(newSuper, ast);
            if (superclassType == null) {
                rewriter.set(typeDecl, TypeDeclaration.SUPERCLASS_TYPE_PROPERTY, newSuperType, null);
            } else {
                rewriter.replace(superclassType, newSuperType, null);
            }
        }
        if (simpleParents.size() > 0) {
            ListRewrite listRewrite = rewriter.getListRewrite(typeDecl,
                    TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY);
            for (String simpleParent : simpleParents) {
                listRewrite.insertLast(createTypeAST(simpleParent, ast), null);
            }
        }
        // finally, add the new change
        TextEdit edit = rewriter.rewriteAST();
        if (!isEmptyEdit(edit)) {
            TextFileChange change = (TextFileChange) allChanges.get(unit);
            if (change == null) {
                change = new TextFileChange(unit.getElementName(), (IFile) unit.getResource());
                change.setTextType("java");
                change.setEdit(new MultiTextEdit());
                allChanges.put(unit, change);
            }
            change.getEdit().addChild(edit);
        }
    }

    private Type createTypeAST(String newSuper, AST ast) {
        String toParse = newSuper + " t;";
        ASTParser parser = ASTParser.newParser(AST.JLS8);
        parser.setSource((toParse).toCharArray());
        parser.setKind(ASTParser.K_CLASS_BODY_DECLARATIONS);
        ASTNode astNode = parser.createAST(null);
        Type t = null;
        if (astNode instanceof TypeDeclaration) {
            Object object = ((TypeDeclaration) astNode).bodyDeclarations().get(0);
            if (object instanceof FieldDeclaration) {
                t = ((FieldDeclaration) object).getType();
                t = (Type) ASTNode.copySubtree(ast, t);
            }
        }
        if (t == null) {
            t = ast.newSimpleType(ast.newSimpleName("MISSING"));
        }
        return t;
    }

    /**
     * Converts from a possibly generic fully qualified type name to a simple fully
     * qualified type name
     * @param qualName
     * @return
     */
    String convertToSimple(String qualName) {
        char[] charArray = qualName.toCharArray();
        StringBuilder candidate = new StringBuilder(charArray.length);
        StringBuilder complete = new StringBuilder(charArray.length);
        for (char c : charArray) {
            switch (c) {
            case '.':
                candidate.delete(0, candidate.length());
                break;
            case '<':
            case ',':
            case '>':
                complete.append(candidate).append(c);
                candidate.delete(0, candidate.length());
                break;
            default:
                candidate.append(c);
            }
        }
        complete.append(candidate);
        return complete.toString();
    }

    /**
     * @return true iff name is the fully qualified name of a class, false if an interface, enum, etc 
     * @throws JavaModelException 
     */
    private boolean isClass(String name, IType type) throws JavaModelException {
        String erasedName = name;
        int genericsIndex = name.indexOf('<');
        if (genericsIndex > 0) {
            erasedName = name.substring(0, genericsIndex);
        }

        int dotIndex = erasedName.lastIndexOf('.');
        String packageName;
        String simpleName;
        if (dotIndex > 0) {
            packageName = erasedName.substring(0, dotIndex);
            simpleName = erasedName.substring(dotIndex + 1);
        } else {
            packageName = "";
            simpleName = erasedName;
        }
        IType found = findType(packageName, simpleName, type);
        return found != null && found.isClass();
    }

    private IType findType(String packageName, String simpleName, IType type) throws JavaModelException {
        NameLookup lookup = getLookup(type);
        Answer answer = lookup.findType(simpleName, packageName, false,
                NameLookup.ACCEPT_CLASSES | NameLookup.ACCEPT_INTERFACES, true, false, false, null);
        if (answer != null) {
            return answer.type;
        }
        // might be an inner type
        int dotIndex = packageName.lastIndexOf('.');
        if (dotIndex > 0) {
            IType foundType = findType(packageName.substring(0, dotIndex), packageName.substring(dotIndex + 1),
                    type);
            if (foundType != null && foundType.getType(simpleName).exists()) {
                return foundType.getType(simpleName);
            }
        }
        return null;
    }

    private void createJavaModelException(String message) throws JavaModelException {
        throw new JavaModelException(
                new CoreException(new Status(IStatus.INFO, AspectJUIPlugin.PLUGIN_ID, message)));
    }

    @SuppressWarnings("unchecked")
    private TypeDeclaration findType(CompilationUnit ast, String name) {
        for (TypeDeclaration type : (Iterable<TypeDeclaration>) ast.types()) {
            if (type.getName().getIdentifier().equals(name)) {
                return type;
            }
        }
        return null;
    }

    /**
     * Deletes all aspect types that have no more children
     * returns true if the compilation unit should be deleted
     * @param ast
     * @param typeDeletes
     * @param removalStored
     * @return
     * @throws JavaModelException
     */
    private boolean deleteTypes(CompilationUnit ast, Map<IType, List<DeleteEdit>> typeDeletes,
            Map<IType, Integer> removalStored) throws JavaModelException {
        if (!deleteEmpty) {
            return false;
        }
        int typesDeleted = 0;
        for (Map.Entry<IType, Integer> entry : removalStored.entrySet()) {
            IType type = entry.getKey();
            int removals = entry.getValue();

            // check to see if all of the type's children 
            // have been removed.  If so, delete the type 
            if (type.getChildren().length == removals) {
                @SuppressWarnings("unchecked")
                List<AbstractTypeDeclaration> typeNodes = ast.types();
                for (AbstractTypeDeclaration typeNode : typeNodes) {
                    if (typeNode.getName().toString().equals(type.getElementName())) {
                        List<DeleteEdit> deletes = typeDeletes.get(type);
                        deletes.clear();
                        deletes.add(new DeleteEdit(type.getSourceRange().getOffset(),
                                type.getSourceRange().getLength()));
                        typesDeleted++;
                    }
                }
            }
        }

        // check to see if all types have been deleted.
        return (ast.types().size() == typesDeleted);
    }

    private void applyTargetTypeEdits(IMember itd, ICompilationUnit source, Collection<IMember> targets)
            throws CoreException, JavaModelException {
        MultiTextEdit multiEdit = new MultiTextEdit();
        for (IMember target : targets) {
            TextEdit edit = null;
            if (itd instanceof IAspectJElement) {
                IAspectJElement ajElement = (IAspectJElement) itd;
                if (itd instanceof IntertypeElement) {
                    if (target instanceof IType) {
                        IType type = (IType) target;
                        // ignore ITD fields and constructors on interfaces
                        if (type.isInterface() && ajElement.getAJKind() != Kind.INTER_TYPE_METHOD) {
                            edit = null;
                        } else {
                            edit = createEditForITDTarget((IntertypeElement) itd, type);
                        }
                    }
                } else if (ajElement.getAJKind().isDeclareAnnotation()) {
                    edit = createEditForDeclareTarget((DeclareElement) itd, target);
                }
            } else if (itd instanceof IType) {
                // an ITIT
                edit = createEditForIntertypeInnerType((IType) itd, (IType) target);
            }
            if (edit != null) {
                multiEdit.addChild(edit);
            }
        }

        if (!isEmptyEdit(multiEdit)) {
            TextFileChange change = (TextFileChange) allChanges.get(source);
            if (change == null) {
                change = new TextFileChange(source.getElementName(), (IFile) source.getResource());
                change.setTextType("java");
                change.setEdit(new MultiTextEdit());
                allChanges.put(source, change);
            }
            change.getEdit().addChild(multiEdit);
        }
    }

    private String getQualifiedTypeForDeclareAnnotation(DeclareElement itd) {
        IProgramElement ipe = getModel(itd).javaElementToProgramElement(itd);
        if (ipe != null) {
            return ipe.getAnnotationType();
        }
        return null;
    }

    /**
     * Creates the text edit for pushing in an intertype inner type
     * @param itit the intertype inner type to push in 
     * @param target the target to push the itit into.
     * @return
     */
    private TextEdit createEditForIntertypeInnerType(IType itit, IType target) throws JavaModelException {
        String source = itit.getSource();
        // now, we must replace the '.' and the preceding identifier in the type name
        int nameOffset = itit.getNameRange().getOffset() - itit.getSourceRange().getOffset();
        int dotOffset = source.lastIndexOf('$', nameOffset);
        int classIndex = source.lastIndexOf("class ", dotOffset);
        source = "\n\t" + source.substring(0, classIndex + "class ".length())
                + source.substring(dotOffset + 1, source.length()) + "\n";
        return new InsertEdit(getITDInsertLocation(target), source);
    }

    private TextEdit createEditForDeclareTarget(DeclareElement itd, IMember target) throws JavaModelException {
        DeclareElementInfo declareElementInfo = (DeclareElementInfo) itd.getElementInfo();
        if (declareElementInfo.isAnnotationRemover()) {
            // must use the model to access removals
            AJProjectModelFacade model = getModel(itd);
            IProgramElement ipe = model.javaElementToProgramElement(itd);
            return getAnnotationRemovalEdit(target, ipe.getRemovedAnnotationTypes());
        } else {
            return new InsertEdit(getDeclareInsertLocation(target), getTextForDeclare(itd));
        }
    }

    /**
     * Delete the specified annotations on the target
     * @param target target element to have annotations to delete
     * @param removedAnnotationTypes annotations to delete
     * @return the delete edit.  May be a MultiText edit
     * if multiple annotations are removed
     * @throws JavaModelException 
     */
    private TextEdit getAnnotationRemovalEdit(IMember target, String[] removedAnnotationTypes)
            throws JavaModelException {
        if (target instanceof IAnnotatable) {
            IAnnotatable annotatable = (IAnnotatable) target;
            IAnnotation[] anns = annotatable.getAnnotations();
            List<DeleteEdit> deletes = new ArrayList<DeleteEdit>(removedAnnotationTypes.length);
            for (String removedType : removedAnnotationTypes) {
                // there can be only one match per removed type
                // however, there can be two annotations with the
                // same simple name on the member, but at least one has
                // a matching qual name.
                // So, if there is a simple name match, keep on looking, 
                // but a qual name match, end it.
                DeleteEdit edit = null;
                for (IAnnotation ann : anns) {
                    // don't know if ann is a simple or a qualified name, 
                    // so check both.
                    String annName = ann.getElementName();
                    boolean qualMatch = removedType.equals(annName);
                    boolean simpleMatch = removedType.endsWith("." + annName);
                    if (simpleMatch || qualMatch) {
                        // match!
                        ISourceRange range = ann.getSourceRange();
                        edit = new DeleteEdit(range.getOffset(), range.getLength());
                        if (qualMatch) {
                            // can only be one qualified match
                            break;
                        }
                    }
                }
                if (edit != null) {
                    deletes.add(edit);
                }
            }
            if (deletes.size() == 0) {
                return null;
            } else if (deletes.size() == 1) {
                return deletes.get(0);
            } else {
                MultiTextEdit multi = new MultiTextEdit();
                for (DeleteEdit delete : deletes) {
                    multi.addChild(delete);
                }
                return multi;
            }
        } else {
            // nothing found
            return null;
        }
    }

    private String getTextForDeclare(DeclareElement itd) throws JavaModelException {
        IProgramElement ipe = getModel(itd).javaElementToProgramElement(itd);
        if (ipe != null) {
            String details = ipe.getDetails();
            int colonIndex = details.indexOf(':');
            String text = details.substring(colonIndex + 1).trim();
            if (itd.getAJKind() == Kind.DECLARE_ANNOTATION_AT_TYPE) {
                // assume top level type
                return text + "\n";
            } else {
                return text + "\n\t";
            }
        } else {
            throw new RuntimeException(
                    "Could not find program element in AspectJ model for " + itd.getHandleIdentifier());
        }
    }

    private int getDeclareInsertLocation(IMember target) throws JavaModelException {
        return target.getSourceRange().getOffset();
    }

    private TextEdit createEditForITDTarget(IntertypeElement itd, IType target) throws JavaModelException {
        // don't add fields or constructors to interfaces
        TextEdit edit;
        if (target.isInterface()) {
            edit = new InsertEdit(getITDInsertLocation(target), getTargetTextForInterface(itd));
        } else {
            edit = new InsertEdit(getITDInsertLocation(target), getTargetTextForClass(itd));
        }
        return edit;
    }

    private String getTargetTextForClass(IntertypeElement itd) throws JavaModelException {
        String itdName = itd.getElementName();
        String[] splits = itdName.split("\\.");
        String newName = splits[splits.length - 1];
        itdName = itdName.replaceAll("\\.", "\\\\\\$");

        // check for constructor
        if (itdName.endsWith("_new")) {
            // check to see if constructor
            String maybeConstructor = itdName.substring(0, itdName.length() - "_new".length());
            String[] maybeConstructorArr = maybeConstructor.split("\\\\\\$");
            if (maybeConstructorArr.length == 2 && maybeConstructorArr[0].equals(maybeConstructorArr[1])) {
                itdName = maybeConstructorArr[0] + "\\$new";
                newName = maybeConstructorArr[0];
            }
        }

        String targetSource = getTargetSource(newName, itd);
        targetSource = "\n\t" + targetSource + "\n";
        // also replace other pplaces where the itdName may exist
        targetSource = targetSource.replaceAll(itdName, newName);

        return targetSource;
    }

    private String getTargetTextForInterface(IntertypeElement itd) throws JavaModelException {
        String itdName = itd.getElementName();
        String[] splits = itdName.split("\\.");
        String newName = splits[splits.length - 1];
        itdName = itdName.replaceAll("\\.", "\\\\\\$");
        String targetSource = getTargetSource(newName, itd);
        int closeParen = targetSource.indexOf(")"); // assumption here...closing paren doesn't exist in comments
        if (closeParen >= 0) {
            targetSource = targetSource.substring(0, closeParen + 1) + ';';
        }
        targetSource = "\n\t" + targetSource + "\n\n";
        // also replace any other occurrences of the itdName
        targetSource = targetSource.replaceAll(itdName, newName);
        return targetSource;
    }

    /**
     * get the target source and replace the ITD name with the new name
     * @param newName
     * @param itd
     * @return
     * @throws JavaModelException 
     */
    private String getTargetSource(String newName, IntertypeElement itd) throws JavaModelException {
        int itdStart = itd.getSourceRange().getOffset();
        int itdNameStart = itd.getTargetTypeSourceRange().getOffset() - itdStart;
        int itdNameEnd = itd.getNameRange().getOffset() + itd.getNameRange().getLength() - itdStart;
        String targetSource = itd.getSource();
        targetSource = targetSource.substring(0, itdNameStart) + newName + targetSource.substring(itdNameEnd);
        return targetSource;
    }

    private int getITDInsertLocation(IType type) throws JavaModelException {
        return type.getSourceRange().getOffset() + type.getSourceRange().getLength() - 1;
    }

    private void applyAspectEdits(ICompilationUnit source, Map<IType, List<DeleteEdit>> typeDeletes)
            throws JavaModelException, CoreException {
        MultiTextEdit edit = new MultiTextEdit();
        for (List<DeleteEdit> deletesForOneType : typeDeletes.values()) {
            for (DeleteEdit delete : deletesForOneType) {
                edit.addChild(delete);
            }
        }

        if (!isEmptyEdit(edit)) {
            TextFileChange change = (TextFileChange) allChanges.get(source);
            if (change == null) {
                change = new TextFileChange(source.getElementName(), (IFile) source.getResource());
                change.setTextType("java");
                change.setEdit(edit);
                allChanges.put(source, change);
            } else {
                change.getEdit().addChild(edit);
            }
        }
    }

    /**
     * Finds all of the compilation units for the given array of target elements
     * @param targets an array of target elements that are the target of ITDs in a single Aspect
     * @return all of the targets grouped by their {@link ICompilationUnit} 
     */
    private Map<ICompilationUnit, Set<IMember>> getUnitTypeMap(IMember[] targets) {
        Map<ICompilationUnit, Set<IMember>> unitToTypes = new HashMap<ICompilationUnit, Set<IMember>>();
        for (int i = 0; i < targets.length; i++) {
            IMember target = targets[i];
            ICompilationUnit unit = target.getCompilationUnit();
            Set<IMember> currTypes;
            if (unitToTypes.containsKey(unit)) {
                currTypes = unitToTypes.get(unit);
            } else {
                currTypes = new HashSet<IMember>();
                unitToTypes.put(unit, currTypes);
            }
            currTypes.add(target);
        }
        return unitToTypes;
    }

    public RefactoringStatus checkInitialConditions(IProgressMonitor monitor)
            throws CoreException, OperationCanceledException {
        if (monitor == null) {
            monitor = new NullProgressMonitor();
        }
        RefactoringStatus status = new RefactoringStatus();

        monitor.beginTask("Checking preconditions...", 1);

        if (itds.isEmpty()) {
            return RefactoringStatus.createWarningStatus("No Intertype declarations selected.  Nothing to do.");
        }

        try {
            for (IMember itd : itds) {
                status.merge(initialITDCheck(itd));
            }
        } finally {
            monitor.done();
        }
        return status;
    }

    private RefactoringStatus initialITDCheck(IMember itd) {
        RefactoringStatus status = new RefactoringStatus();

        if (itd == null) {
            status.merge(RefactoringStatus.createFatalErrorStatus("Intertype declaration has not been specified"));
            return status;
        }
        if (!(itd instanceof IAspectJElement)) {
            return status;
        }
        try {
            AJProjectModelFacade model = getModel(itd);
            if (!model.hasModel()) {
                status.merge(RefactoringStatus
                        .createFatalErrorStatus("No crosscutting model available.  Rebuild project."));
            }
            ICompilationUnit unit = itd.getCompilationUnit();
            Object[] itdName = new Object[] { unit.getElementName() };
            if (!itd.exists()) {
                status.merge(RefactoringStatus.createFatalErrorStatus(MessageFormat
                        .format("ITD ''{0}'' does not exist.", new Object[] { itd.getElementName() })));
            } else if (!unit.isStructureKnown()) {
                status.merge(RefactoringStatus.createFatalErrorStatus(
                        MessageFormat.format("Compilation unit ''{0}'' contains compile errors.", itdName)));
            } else
                try {
                    if (unit.getResource().findMaxProblemSeverity(IMarker.MARKER, true,
                            IResource.DEPTH_ZERO) >= IMarker.SEVERITY_ERROR) {
                        status.merge(RefactoringStatus.createFatalErrorStatus(MessageFormat
                                .format("Compilation unit ''{0}'' contains compile errors.", itdName)));
                    }
                } catch (CoreException e) {
                    status.merge(RefactoringStatus.create(e.getStatus()));
                }
            // now check target types
            IMember[] targets = getTargets(Collections.singletonList(itd));
            if (targets.length > 0) {
                for (int i = 0; i < targets.length; i++) {
                    IMember target = targets[i];
                    if (!target.exists()) {
                        status.merge(RefactoringStatus.createFatalErrorStatus(MessageFormat.format(
                                "Target type ''{0}'' does not exist.", new Object[] { target.getElementName() })));
                    } else if (target.isBinary()) {
                        status.merge(RefactoringStatus.createFatalErrorStatus(MessageFormat.format(
                                "Target type ''{0}'' is binary.", new Object[] { target.getElementName() })));
                    } else if (!unit.isStructureKnown()) {
                        status.merge(RefactoringStatus.createFatalErrorStatus(
                                MessageFormat.format("Compilation unit ''{0}'' contains compile errors.",
                                        new Object[] { target.getCompilationUnit().getElementName() })));
                    }
                }
            } else {
                status.merge(
                        RefactoringStatus.createWarningStatus(MessageFormat.format(
                                "ITD ''{0}'' has no target.  This refactoring will delete the declaration.  "
                                        + "Perhaps there is an unresolved compilation error?",
                                itd.getElementName())));
            }
        } catch (JavaModelException e) {
            status.addFatalError(
                    "JavaModelException:\n\t" + e.getMessage() + "\n\t" + e.getJavaModelStatus().getMessage());
        }
        return status;
    }

    public Change createChange(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
        monitor.beginTask("Creating change...", 1);
        try {
            final Collection<Change> changes = allChanges.values();
            CompositeChange change = new CompositeChange(getName(),
                    (Change[]) changes.toArray(new Change[changes.size()])) {

                public ChangeDescriptor getDescriptor() {
                    return new RefactoringChangeDescriptor(createDescriptor());
                }

            };
            return change;
        } finally {
            monitor.done();
        }
    }

    public PushInRefactoringDescriptor createDescriptor() {
        StringBuffer projectsb = new StringBuffer();
        StringBuffer descriptionsb = new StringBuffer();
        StringBuffer commentsb = new StringBuffer();
        StringBuffer argssb = new StringBuffer();
        for (IMember itd : itds) {
            projectsb.append(itd.getJavaProject().getElementName() + "\n");
            descriptionsb.append(MessageFormat.format("Push In intertype declaration for ''{0}''\n",
                    new Object[] { itd.getElementName() }));
            String itdLabel = getModel(itd).getJavaElementLinkName(itd);
            commentsb.append(
                    MessageFormat.format("Push In intertype declaration for ''{0}''\n", new Object[] { itdLabel }));
            argssb.append(itd.getHandleIdentifier() + "\n");
        }
        Map<String, String> arguments = new HashMap<String, String>();
        arguments.put(ALL_ITDS, argssb.toString());
        return new PushInRefactoringDescriptor(projectsb.toString(), descriptionsb.toString(), commentsb.toString(),
                arguments);
    }

    public String getName() {
        return "Push-In";
    }

    public RefactoringStatus initialize(Map<String, String> arguments) {
        String value = arguments.get(ALL_ITDS);
        if (value != null) {
            String[] values = value.split("\\n");
            List<IMember> newitds = new ArrayList<IMember>(values.length);
            for (int i = 0; i < values.length; i++) {
                newitds.add((IMember) AspectJCore.create(value));
            }
            setITDs(newitds);
            return new RefactoringStatus();
        } else {
            return RefactoringStatus.createErrorStatus("No ITD specified.");
        }
    }

    public void setITDs(List<IMember> itds) {
        this.itds = itds;
    }

    public List<IMember> getITDs() {
        return itds;
    }

    /**
     * This method determines all of the targets of the set of ITDs passed in.
     * it does not distinguish between which type is affected by which ITD
     * later, we need to do that filtering
     * @param itds set of ITDs to putsh in from a single compilation unit
     * @return array of target elements for these ITDs.
     * @throws JavaModelException
     */
    private IMember[] getTargets(List<IMember> itds) throws JavaModelException {
        AJProjectModelFacade model = getModel((IJavaElement) itds.get(0));
        List<IMember> targets = new ArrayList<IMember>();

        for (IMember itd : itds) {
            List<IJavaElement> elts;
            AJRelationshipType relationship = itd instanceof IAspectJElement
                    && ((IAspectJElement) itd).getAJKind().isDeclareAnnotation() ? AJRelationshipManager.ANNOTATES :
            // either an ITD or an ITIT (IType)
                            AJRelationshipManager.DECLARED_ON;
            elts = model.getRelationshipsForElement(itd, relationship);

            for (IJavaElement elt : elts) {
                targets.add((IMember) elt);
            }
        }
        return (IMember[]) targets.toArray(new IMember[targets.size()]);
    }

    private boolean isEmptyEdit(TextEdit edit) {
        return edit.getClass() == MultiTextEdit.class && !edit.hasChildren();
    }

    private boolean isCUnitContainingITD(ICompilationUnit unit, IMember itd) {
        return itd != null && itd.getCompilationUnit().equals(unit);
    }
}