blob: e0a8f4bd50740d8c0a4f7de7752c059bec8567a4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 SpringSource and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Andrew Eisenberg - initial version
*******************************************************************************/
package org.eclipse.ajdt.internal.core.refactoring;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.aspectj.asm.IProgramElement.Kind;
import org.eclipse.ajdt.core.AspectJPlugin;
import org.eclipse.ajdt.core.javaelements.AJCompilationUnit;
import org.eclipse.ajdt.core.javaelements.AspectElement;
import org.eclipse.ajdt.core.javaelements.IntertypeElement;
import org.eclipse.ajdt.core.model.AJProjectModelFacade;
import org.eclipse.ajdt.core.model.AJProjectModelFactory;
import org.eclipse.ajdt.core.model.AJRelationshipManager;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
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.Signature;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.internal.core.search.JavaSearchScope;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextEditChangeGroup;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor;
import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
/**
* @author Andrew Eisenberg
* @created Apr 22, 2010
* fields that have ITDs that are getters or setters are
* renamed with this participant
*/
public class ITDAccessorRenameParticipant extends RenameParticipant {
private IField field;
private AJCompilationUnit aspectDeclaringGetter;
private AJCompilationUnit aspectDeclaringSetter;
private boolean useIsForBooleanGetter = false;
/**
* Although I don't like referring to Roo from
* within AJDT, we need a way to disable the renaming of
* the Aspect that declares the ITDs since Roo will
* automatically regenerate these files anyway.
*/
private boolean disableITDUpdatingForRoo;
@Override
public RefactoringStatus checkConditions(IProgressMonitor pm,
CheckConditionsContext context) throws OperationCanceledException {
return new RefactoringStatus();
}
@Override
public String getName() {
return "Rename occurrences of intertype getters and setters";
}
@Override
protected boolean initialize(Object element) {
if (element instanceof IField) {
field = (IField) element;
if (AspectJPlugin.isAJProject(field.getJavaProject().getProject())) {
disableITDUpdatingForRoo = shouldDisableITDUpdatingForRoo();
return true;
}
}
return false;
}
@Override
public Change createChange(IProgressMonitor pm) throws CoreException,
OperationCanceledException {
boolean shouldRenameGetter = shouldRename(true);
boolean shouldRenameSetter = shouldRename(false);
boolean shouldRenamePrivateField = shouldRenamePrivateField();
if (! shouldRenameGetter && ! shouldRenameSetter && ! shouldRenamePrivateField) {
return null;
}
IType declaring = field.getDeclaringType();
AJProjectModelFacade model = AJProjectModelFactory.getInstance().getModelForJavaElement(declaring);
if (model.hasModel()) {
List<IJavaElement> itds = model.getRelationshipsForElement(declaring, AJRelationshipManager.ASPECT_DECLARATIONS);
IntertypeElement getter = null, setter = null;
for (IJavaElement elt : itds) {
if (shouldRenameGetter && isGetter(elt)) {
getter = (IntertypeElement) elt;
aspectDeclaringGetter = (AJCompilationUnit) getter.getCompilationUnit();
} else if (shouldRenameSetter && isSetter(elt)) {
setter = (IntertypeElement) elt;
aspectDeclaringSetter = (AJCompilationUnit) setter.getCompilationUnit();
}
}
if (getter == null && setter == null) {
return null;
}
// do a search for the getter
List<SearchMatch> getterReferences;
if (getter != null) {
getterReferences = findReferences(getter);
} else {
getterReferences = Collections.emptyList();
}
// do a search for the setter
List<SearchMatch> setterReferences;
if (setter != null) {
setterReferences = findReferences(setter);
} else {
setterReferences = Collections.emptyList();
}
// now look for private field references in Aspects
List<SearchMatch> privateFieldReferences;
if (shouldRenamePrivateField) {
privateFieldReferences = findPrivateAspectReferences();
} else {
privateFieldReferences = Collections.emptyList();
}
CompositeChange change = new CompositeChange(getName());
createDeclarationChange(getter, change, getOldGetterName(useIsForBooleanGetter), getNewGetterName(useIsForBooleanGetter));
createDeclarationChange(setter, change, getOldSetterName(), getNewSetterName());
createMatchedChanges(privateFieldReferences, change, field.getElementName(), getArguments().getNewName());
createMatchedChanges(getterReferences, change, getOldGetterName(useIsForBooleanGetter), getNewGetterName(useIsForBooleanGetter));
createMatchedChanges(setterReferences, change, getOldSetterName(), getNewSetterName());
if (change.getChildren().length > 0) {
return change;
}
}
return null;
}
private List<SearchMatch> findPrivateAspectReferences() {
List<SearchMatch> maybeMatches = findReferences(field);
// now remove all matches that are not inside of aspects
// We could be checking for privileged aspects here, but
// I think it is better to be more general.
for (Iterator<SearchMatch> matchIter = maybeMatches.iterator(); matchIter.hasNext();) {
SearchMatch maybeMatch = matchIter.next();
if (maybeMatch.getElement() instanceof IMember) {
IMember member = (IMember) maybeMatch.getElement();
if (! (member.getAncestor(IJavaElement.TYPE) instanceof AspectElement)) {
matchIter.remove();
}
}
}
return maybeMatches;
}
private void createMatchedChanges(List<SearchMatch> references,
CompositeChange finalChange, String oldName, String newName) {
for (SearchMatch searchMatch : references) {
Object elt = searchMatch.getElement();
if (elt instanceof IMember) {
addChange(finalChange, (IMember) elt, searchMatch.getOffset(), oldName.length(), newName);
}
}
}
private void createDeclarationChange(IntertypeElement accessor,
CompositeChange finalChange, String oldName, String newName) throws JavaModelException {
if (accessor == null) {
return;
}
ISourceRange region = accessor.getNameRange();
addChange(finalChange, accessor, region.getOffset()+region.getLength()-oldName.length(), oldName.length(), newName);
}
private void addChange(CompositeChange finalChange, IMember enclosingElement,
int offset, int length, String newName) {
ICompilationUnit unit = enclosingElement.getCompilationUnit();
if (disableITDUpdatingForRoo && unit != null && (unit.equals(aspectDeclaringGetter) || unit.equals(aspectDeclaringSetter))) {
return;
}
CompilationUnitChange existingChange = findOrCreateChange(
enclosingElement, finalChange);
TextEditChangeGroup[] groups = existingChange.getTextEditChangeGroups();
TextEdit occurrenceEdit = new ReplaceEdit(offset, length, newName);
boolean isOverlapping = false;
for (TextEditChangeGroup group : groups) {
if (group.getTextEdits()[0].covers(occurrenceEdit)) {
isOverlapping = true;
break;
}
}
if (isOverlapping) {
// don't step on someone else's feet
return;
}
existingChange.addEdit(occurrenceEdit);
existingChange.addChangeGroup(new TextEditChangeGroup(existingChange,
new TextEditGroup("Update ITD accessor occurrence", occurrenceEdit)));
}
private CompilationUnitChange findOrCreateChange(IMember accessor,
CompositeChange finalChange) {
TextChange textChange = getTextChange(accessor.getCompilationUnit());
CompilationUnitChange existingChange = null;
if (textChange instanceof CompilationUnitChange) {
// check to see if change exists from some other part of the refactoring
existingChange = (CompilationUnitChange) textChange;
} else {
// check to see if we have already touched this file
Change[] children = finalChange.getChildren();
for (Change change : children) {
if (change instanceof CompilationUnitChange) {
if (((CompilationUnitChange) change).getCompilationUnit().equals(accessor.getCompilationUnit())) {
existingChange = (CompilationUnitChange) change;
break;
}
}
}
}
if (existingChange == null) {
// nope...must create a new change
existingChange = new CompilationUnitChange("ITD accessor renamings for " + accessor.getCompilationUnit().getElementName(), accessor.getCompilationUnit());
existingChange.setEdit(new MultiTextEdit());
finalChange.add(existingChange);
}
return existingChange;
}
private List<SearchMatch> findReferences(IMember accessor) {
SearchPattern pattern = SearchPattern.createPattern(accessor, IJavaSearchConstants.REFERENCES);
SearchEngine engine = new SearchEngine();
JavaSearchScope scope = new JavaSearchScope(false);
try {
scope.add(accessor.getJavaProject());
CollectingSearchRequestor requestor = new CollectingSearchRequestor();
engine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
scope, requestor, new NullProgressMonitor());
return requestor.getResults();
} catch (JavaModelException e) {
} catch (CoreException e) {
}
return Collections.emptyList();
}
private boolean isSetter(IJavaElement elt) throws JavaModelException {
if (elt instanceof IntertypeElement &&
elt.getElementName().endsWith("." + getOldSetterName())) {
IntertypeElement itd = (IntertypeElement) elt;
String[] parameterTypes = itd.getParameterTypes();
if (itd.getAJKind() == Kind.INTER_TYPE_METHOD &&
parameterTypes != null &&
parameterTypes.length == 1 &&
parameterTypes[0].equals(field.getTypeSignature()) &&
itd.getReturnType().equals(Signature.SIG_VOID)) {
return true;
}
}
return false;
}
private boolean isGetter(IJavaElement elt) throws JavaModelException {
boolean nameMatch = false;
boolean isFound = false;
if (elt instanceof IntertypeElement &&
elt.getElementName().endsWith("." + getOldGetterName(false))) {
nameMatch = true;
} else if (elt instanceof IntertypeElement &&
elt.getElementName().endsWith("." + getOldGetterName(true))) {
nameMatch = true;
}
if (nameMatch) {
useIsForBooleanGetter = isFound;
IntertypeElement itd = (IntertypeElement) elt;
String[] parameterTypes = itd.getParameterTypes();
if (itd.getAJKind() == Kind.INTER_TYPE_METHOD &&
(parameterTypes == null ||
parameterTypes.length == 0) &&
itd.getReturnType().equals(field.getTypeSignature())) {
return true;
}
}
return false;
}
private String getNewSetterName() {
return accessorName("set", getArguments().getNewName());
}
private String getNewGetterName(boolean useIsForBoolean) {
return accessorName("get", getArguments().getNewName());
}
private String getOldSetterName() {
return accessorName("set", field.getElementName());
}
private String getOldGetterName(boolean useIsForBoolean) {
return accessorName(useIsForBoolean ? "is" : "get", field.getElementName());
}
private String accessorName(String prefix, String name) {
return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1);
}
/**
* Privileged aspects can see private fields. But, they
* are not renamed in the RenameFieldProcessor, so must check
* here to see if we need to do extra logic to rename in an
* aspect
* @throws JavaModelException
*/
private boolean shouldRenamePrivateField() throws JavaModelException {
return Flags.isPrivate(field.getFlags());
}
private Field fRenameGetterField;
private Field fRenameSetterField;
private boolean shouldRename(boolean getter) {
try {
RefactoringProcessor processor = getProcessor();
if (! (processor.getClass().getCanonicalName().equals("org.eclipse.jdt.internal.corext.refactoring.rename.RenameFieldProcessor"))) {
return false;
}
Field thisField;
if (getter) {
if (fRenameGetterField == null) {
fRenameGetterField = processor.getClass().getDeclaredField("fRenameGetter");
fRenameGetterField.setAccessible(true);
}
thisField = fRenameGetterField;
} else {
if (fRenameSetterField == null) {
fRenameSetterField = processor.getClass().getDeclaredField("fRenameSetter");
fRenameSetterField.setAccessible(true);
}
thisField = fRenameSetterField;
}
return thisField.getBoolean(processor);
} catch (Exception e) {
}
return false;
}
/**
* if this is a roo project and roo is installed in the system.
*/
private boolean shouldDisableITDUpdatingForRoo() {
try {
return field.getJavaProject().getProject().hasNature("com.springsource.sts.roo.core.nature") &&
(
Platform.getBundle("com.springsource.sts.roo.core") != null ||
Platform.getBundle("rg.springframework.ide.eclipse.roo.core") != null
);
} catch (CoreException e) {
return false;
}
}
}