blob: 5017e65fa9749b2f2a481ed7c006c842f7736308 [file] [log] [blame]
/*******************************************************************************
* 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.LinkedHashSet;
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.core.manipulation.ImportReferencesCollector;
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.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
* @author Andy Clement
*/
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 = 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) {
ITypeBinding binding = name.resolveTypeBinding();
if (binding != null) {
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 = 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 LinkedHashSet<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;
}
@Override
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() {
@Override
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 = 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, 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(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 = 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;
}
@Override
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;
}
@Override
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(), changes.toArray(new Change[changes.size()])) {
@Override
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);
}
@Override
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(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 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);
}
}