blob: c398b8485769959b83d278a403dfd6899f7ec638 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.refactoring.encapsulateFields;
import com.intellij.lang.findUsages.DescriptiveNameUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.*;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.listeners.RefactoringEventData;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.DocCommentPolicy;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class EncapsulateFieldsProcessor extends BaseRefactoringProcessor {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.encapsulateFields.EncapsulateFieldsProcessor");
private PsiClass myClass;
@NotNull
private final EncapsulateFieldsDescriptor myDescriptor;
private final FieldDescriptor[] myFieldDescriptors;
private HashMap<String,PsiMethod> myNameToGetter;
private HashMap<String,PsiMethod> myNameToSetter;
public EncapsulateFieldsProcessor(Project project, @NotNull EncapsulateFieldsDescriptor descriptor) {
super(project);
myDescriptor = descriptor;
myFieldDescriptors = descriptor.getSelectedFields();
myClass = descriptor.getTargetClass();
}
public static void setNewFieldVisibility(PsiField field, EncapsulateFieldsDescriptor descriptor) {
try {
if (descriptor.getFieldsVisibility() != null) {
field.normalizeDeclaration();
PsiUtil.setModifierProperty(field, descriptor.getFieldsVisibility(), true);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
@Nullable
@Override
protected String getRefactoringId() {
return "refactoring.encapsulateFields";
}
@Nullable
@Override
protected RefactoringEventData getBeforeData() {
RefactoringEventData data = new RefactoringEventData();
final List<PsiElement> fields = new ArrayList<PsiElement>();
for (FieldDescriptor fieldDescriptor : myFieldDescriptors) {
fields.add(fieldDescriptor.getField());
}
data.addElements(fields);
return data;
}
@Nullable
@Override
protected RefactoringEventData getAfterData(UsageInfo[] usages) {
RefactoringEventData data = new RefactoringEventData();
List<PsiElement> elements = new ArrayList<PsiElement>();
if (myNameToGetter != null) {
elements.addAll(myNameToGetter.values());
}
if (myNameToSetter != null) {
elements.addAll(myNameToSetter.values());
}
data.addElements(elements);
return data;
}
@NotNull
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
FieldDescriptor[] fields = new FieldDescriptor[myFieldDescriptors.length];
System.arraycopy(myFieldDescriptors, 0, fields, 0, myFieldDescriptors.length);
return new EncapsulateFieldsViewDescriptor(fields);
}
protected String getCommandName() {
return RefactoringBundle.message("encapsulate.fields.command.name", DescriptiveNameUtil.getDescriptiveName(myClass));
}
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
checkExistingMethods(conflicts, true);
checkExistingMethods(conflicts, false);
final Collection<PsiClass> classes = ClassInheritorsSearch.search(myClass).findAll();
for (FieldDescriptor fieldDescriptor : myFieldDescriptors) {
final Set<PsiMethod> setters = new HashSet<PsiMethod>();
final Set<PsiMethod> getters = new HashSet<PsiMethod>();
for (PsiClass aClass : classes) {
final PsiMethod getterOverrider =
myDescriptor.isToEncapsulateGet() ? aClass.findMethodBySignature(fieldDescriptor.getGetterPrototype(), false) : null;
if (getterOverrider != null) {
getters.add(getterOverrider);
}
final PsiMethod setterOverrider =
myDescriptor.isToEncapsulateSet() ? aClass.findMethodBySignature(fieldDescriptor.getSetterPrototype(), false) : null;
if (setterOverrider != null) {
setters.add(setterOverrider);
}
}
if (!getters.isEmpty() || !setters.isEmpty()) {
final PsiField field = fieldDescriptor.getField();
for (PsiReference reference : ReferencesSearch.search(field)) {
final PsiElement place = reference.getElement();
LOG.assertTrue(place instanceof PsiReferenceExpression);
final PsiExpression qualifierExpression = ((PsiReferenceExpression)place).getQualifierExpression();
final PsiClass ancestor;
if (qualifierExpression == null) {
ancestor = PsiTreeUtil.getParentOfType(place, PsiClass.class, false);
}
else {
ancestor = PsiUtil.resolveClassInType(qualifierExpression.getType());
}
final boolean isGetter = !PsiUtil.isAccessedForWriting((PsiExpression)place);
for (PsiMethod overridden : isGetter ? getters : setters) {
if (InheritanceUtil.isInheritorOrSelf(myClass, ancestor, true)) {
conflicts.putValue(overridden, "There is already a " +
RefactoringUIUtil.getDescription(overridden, true) +
" which would hide generated " +
(isGetter ? "getter" : "setter") + " for " + place.getText());
break;
}
}
}
}
}
return showConflicts(conflicts, refUsages.get());
}
private void checkExistingMethods(MultiMap<PsiElement, String> conflicts, boolean isGetter) {
if (isGetter) {
if (!myDescriptor.isToEncapsulateGet()) return;
}
else {
if (!myDescriptor.isToEncapsulateSet()) return;
}
for (FieldDescriptor descriptor : myFieldDescriptors) {
PsiMethod prototype = isGetter
? descriptor.getGetterPrototype()
: descriptor.getSetterPrototype();
final PsiType prototypeReturnType = prototype.getReturnType();
PsiMethod existing = myClass.findMethodBySignature(prototype, true);
if (existing != null) {
final PsiType returnType = existing.getReturnType();
if (!RefactoringUtil.equivalentTypes(prototypeReturnType, returnType, myClass.getManager())) {
final String descr = PsiFormatUtil.formatMethod(existing,
PsiSubstitutor.EMPTY,
PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS | PsiFormatUtilBase.SHOW_TYPE,
PsiFormatUtilBase.SHOW_TYPE
);
String message = isGetter ?
RefactoringBundle.message("encapsulate.fields.getter.exists", CommonRefactoringUtil.htmlEmphasize(descr),
CommonRefactoringUtil.htmlEmphasize(prototype.getName())) :
RefactoringBundle.message("encapsulate.fields.setter.exists", CommonRefactoringUtil.htmlEmphasize(descr),
CommonRefactoringUtil.htmlEmphasize(prototype.getName()));
conflicts.putValue(existing, message);
}
} else {
PsiClass containingClass = myClass.getContainingClass();
while (containingClass != null && existing == null) {
existing = containingClass.findMethodBySignature(prototype, true);
if (existing != null) {
for (PsiReference reference : ReferencesSearch.search(existing)) {
final PsiElement place = reference.getElement();
LOG.assertTrue(place instanceof PsiReferenceExpression);
final PsiExpression qualifierExpression = ((PsiReferenceExpression)place).getQualifierExpression();
final PsiClass inheritor;
if (qualifierExpression == null) {
inheritor = PsiTreeUtil.getParentOfType(place, PsiClass.class, false);
} else {
inheritor = PsiUtil.resolveClassInType(qualifierExpression.getType());
}
if (InheritanceUtil.isInheritorOrSelf(inheritor, myClass, true)) {
conflicts.putValue(existing, "There is already a " + RefactoringUIUtil.getDescription(existing, true) + " which would be hidden by generated " + (isGetter ? "getter" : "setter"));
break;
}
}
}
containingClass = containingClass.getContainingClass();
}
}
}
}
@NotNull protected UsageInfo[] findUsages() {
ArrayList<EncapsulateFieldUsageInfo> array = ContainerUtil.newArrayList();
for (FieldDescriptor fieldDescriptor : myFieldDescriptors) {
for (final PsiReference reference : ReferencesSearch.search(fieldDescriptor.getField())) {
final PsiElement element = reference.getElement();
if (element == null) continue;
final EncapsulateFieldHelper helper = EncapsulateFieldHelper.getHelper(element.getLanguage());
if (helper != null) {
EncapsulateFieldUsageInfo usageInfo = helper.createUsage(myDescriptor, fieldDescriptor, reference);
if (usageInfo != null) {
array.add(usageInfo);
}
}
}
}
EncapsulateFieldUsageInfo[] usageInfos = array.toArray(new EncapsulateFieldUsageInfo[array.size()]);
return UsageViewUtil.removeDuplicatedUsages(usageInfos);
}
protected void refreshElements(PsiElement[] elements) {
LOG.assertTrue(elements.length == myFieldDescriptors.length);
for (int idx = 0; idx < elements.length; idx++) {
PsiElement element = elements[idx];
LOG.assertTrue(element instanceof PsiField);
myFieldDescriptors[idx].refreshField((PsiField)element);
}
myClass = myFieldDescriptors[0].getField().getContainingClass();
}
protected void performRefactoring(UsageInfo[] usages) {
updateFieldVisibility();
generateAccessors();
processUsagesPerFile(usages);
}
private void updateFieldVisibility() {
if (myDescriptor.getFieldsVisibility() == null) return;
for (FieldDescriptor descriptor : myFieldDescriptors) {
setNewFieldVisibility(descriptor.getField(), myDescriptor);
}
}
private void generateAccessors() {
// generate accessors
myNameToGetter = new HashMap<String, PsiMethod>();
myNameToSetter = new HashMap<String, PsiMethod>();
for (FieldDescriptor fieldDescriptor : myFieldDescriptors) {
final DocCommentPolicy<PsiDocComment> commentPolicy = new DocCommentPolicy<PsiDocComment>(myDescriptor.getJavadocPolicy());
PsiField field = fieldDescriptor.getField();
final PsiDocComment docComment = field.getDocComment();
if (myDescriptor.isToEncapsulateGet()) {
final PsiMethod prototype = fieldDescriptor.getGetterPrototype();
assert prototype != null;
final PsiMethod getter = addOrChangeAccessor(prototype, myNameToGetter);
if (docComment != null) {
final PsiDocComment getterJavadoc = (PsiDocComment)getter.addBefore(docComment, getter.getFirstChild());
commentPolicy.processNewJavaDoc(getterJavadoc);
}
}
if (myDescriptor.isToEncapsulateSet() && !field.hasModifierProperty(PsiModifier.FINAL)) {
PsiMethod prototype = fieldDescriptor.getSetterPrototype();
assert prototype != null;
addOrChangeAccessor(prototype, myNameToSetter);
}
if (docComment != null) {
commentPolicy.processOldJavaDoc(docComment);
}
}
}
private void processUsagesPerFile(UsageInfo[] usages) {
Map<PsiFile, List<EncapsulateFieldUsageInfo>> usagesInFiles = new HashMap<PsiFile, List<EncapsulateFieldUsageInfo>>();
for (UsageInfo usage : usages) {
PsiElement element = usage.getElement();
if (element == null) continue;
final PsiFile file = element.getContainingFile();
List<EncapsulateFieldUsageInfo> usagesInFile = usagesInFiles.get(file);
if (usagesInFile == null) {
usagesInFile = new ArrayList<EncapsulateFieldUsageInfo>();
usagesInFiles.put(file, usagesInFile);
}
usagesInFile.add(((EncapsulateFieldUsageInfo)usage));
}
for (List<EncapsulateFieldUsageInfo> usageInfos : usagesInFiles.values()) {
//this is to avoid elements to become invalid as a result of processUsage
final EncapsulateFieldUsageInfo[] infos = usageInfos.toArray(new EncapsulateFieldUsageInfo[usageInfos.size()]);
CommonRefactoringUtil.sortDepthFirstRightLeftOrder(infos);
for (EncapsulateFieldUsageInfo info : infos) {
EncapsulateFieldHelper helper = EncapsulateFieldHelper.getHelper(info.getElement().getLanguage());
helper.processUsage(info,
myDescriptor,
myNameToSetter.get(info.getFieldDescriptor().getSetterName()),
myNameToGetter.get(info.getFieldDescriptor().getGetterName())
);
}
}
}
private PsiMethod addOrChangeAccessor(PsiMethod prototype, HashMap<String,PsiMethod> nameToAncestor) {
PsiMethod existing = myClass.findMethodBySignature(prototype, false);
PsiMethod result = existing;
try{
if (existing == null){
PsiUtil.setModifierProperty(prototype, myDescriptor.getAccessorsVisibility(), true);
result = (PsiMethod) myClass.add(prototype);
}
else{
//TODO : change visibility
}
nameToAncestor.put(prototype.getName(), result);
return result;
}
catch(IncorrectOperationException e){
LOG.error(e);
}
return null;
}
}