blob: e83b6d4b59db0145015c91dcf6f39338f8bfc5bc [file] [log] [blame]
/*
* Copyright 2000-2014 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 org.jetbrains.plugins.groovy.intentions.control;
import com.intellij.ide.util.DefaultPsiElementCellRenderer;
import com.intellij.ide.util.MethodCellRenderer;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.util.*;
import com.intellij.refactoring.changeSignature.JavaThrownExceptionInfo;
import com.intellij.refactoring.changeSignature.ThrownExceptionInfo;
import com.intellij.ui.components.JBList;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle;
import org.jetbrains.plugins.groovy.intentions.base.Intention;
import org.jetbrains.plugins.groovy.intentions.base.PsiElementPredicate;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.refactoring.DefaultGroovyVariableNameValidator;
import org.jetbrains.plugins.groovy.refactoring.changeSignature.GrChangeInfoImpl;
import org.jetbrains.plugins.groovy.refactoring.changeSignature.GrChangeSignatureProcessor;
import org.jetbrains.plugins.groovy.refactoring.changeSignature.GrParameterInfo;
import javax.swing.*;
import java.util.*;
/**
* @author Maxim.Medvedev
*/
public class CreateParameterForFieldIntention extends Intention {
private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.intentions.control.CreateParameterForFieldIntention");
private static final Key<CachedValue<List<GrField>>> FIELD_CANDIDATES = Key.create("Fields.candidates");
@NotNull
@Override
public String getText() {
return super.getText();
}
@Override
protected void processIntention(@NotNull PsiElement element, final Project project, final Editor editor)
throws IncorrectOperationException {
final List<GrField> candidates = findFieldCandidates(element);
if (candidates != null) {
performForConstructor(element, project, editor, candidates);
}
else {
final List<GrMethod> constructors = findConstructorCandidates(element);
performForField(element, project, editor, constructors);
}
}
private static void performForField(PsiElement element, final Project project, Editor editor, List<GrMethod> constructors) {
final GrField field = PsiTreeUtil.getParentOfType(element, GrField.class);
if (constructors.isEmpty()) return;
if (ApplicationManager.getApplication().isUnitTestMode()) {
for (GrMethod constructor : constructors) {
addParameter(field, constructor, project);
}
return;
}
final JList list = new JBList(constructors.toArray(new GrMethod[constructors.size()]));
list.setCellRenderer(new MethodCellRenderer(true));
new PopupChooserBuilder(list).setTitle(GroovyIntentionsBundle.message("create.parameter.for.field.intention.name")).
setMovable(true).
setItemChoosenCallback(new Runnable() {
@Override
public void run() {
final Object[] selectedValues = list.getSelectedValues();
Arrays.sort(selectedValues, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
return ((GrMethod)o2).getParameterList().getParametersCount() - ((GrMethod)o1).getParameterList().getParametersCount();
}
});
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
AccessToken accessToken = WriteAction.start();
try{
for (Object selectedValue : selectedValues) {
LOG.assertTrue(((GrMethod)selectedValue).isValid());
addParameter(field, ((GrMethod)selectedValue), project);
}
}
finally {
accessToken.finish();
}
}
}, GroovyIntentionsBundle.message("create.parameter.for.field.intention.name"), null);
}
}).createPopup().showInBestPositionFor(editor);
}
private static void performForConstructor(PsiElement element, final Project project, Editor editor, List<GrField> candidates) {
final GrMethod constructor = PsiTreeUtil.getParentOfType(element, GrMethod.class);
if (candidates.isEmpty()) return;
if (ApplicationManager.getApplication().isUnitTestMode()) {
for (GrField candidate : candidates) {
addParameter(candidate, constructor, project);
}
return;
}
final JList list = new JBList(candidates.toArray(new GrField[candidates.size()]));
list.setCellRenderer(new DefaultPsiElementCellRenderer());
new PopupChooserBuilder(list).setTitle(GroovyIntentionsBundle.message("create.parameter.for.field.intention.name")).
setMovable(true).
setItemChoosenCallback(new Runnable() {
@Override
public void run() {
final Object[] selectedValues = list.getSelectedValues();
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
AccessToken accessToken = WriteAction.start();
try {
for (Object selectedValue : selectedValues) {
LOG.assertTrue(((GrField)selectedValue).isValid());
addParameter(((GrField)selectedValue), constructor, project);
}
}
finally {
accessToken.finish();
}
}
}, GroovyIntentionsBundle.message("create.parameter.for.field.intention.name"), null);
}
}).createPopup().showInBestPositionFor(editor);
}
private static void addParameter(final GrField selectedValue, final GrMethod constructor, final Project project) {
List<GrParameterInfo> parameters = new ArrayList<GrParameterInfo>();
GrParameter[] constructorParameters = constructor.getParameters();
for (int i = 0; i < constructorParameters.length; i++) {
parameters.add(new GrParameterInfo(constructorParameters[i], i));
}
final String[] suggestedNames =
JavaCodeStyleManager.getInstance(project).suggestVariableName(VariableKind.PARAMETER, selectedValue.getName(), null, null).names;
final DefaultGroovyVariableNameValidator nameValidator =
new DefaultGroovyVariableNameValidator(constructor, Collections.<String>emptyList(), false);
String parameterName = ContainerUtil.find(suggestedNames, new Condition<String>() {
@Override
public boolean value(String name) {
return !nameValidator.validateName(name, false).isEmpty();
}
});
if (parameterName == null) {
parameterName = nameValidator.validateName(suggestedNames[0], true);
}
parameters.add(new GrParameterInfo(parameterName, "null", "", selectedValue.getTypeGroovy(), -1, false));
PsiClassType[] exceptionTypes = constructor.getThrowsList().getReferencedTypes();
ThrownExceptionInfo[] thrownExceptionInfos = new ThrownExceptionInfo[exceptionTypes.length];
for (int i = 0; i < exceptionTypes.length; i++) {
new JavaThrownExceptionInfo(i, exceptionTypes[i]);
}
final GrChangeInfoImpl grChangeInfo = new GrChangeInfoImpl(constructor, null, null, constructor.getName(), parameters, thrownExceptionInfos, false);
final String finalParameterName = parameterName;
final GrChangeSignatureProcessor processor = new GrChangeSignatureProcessor(project, grChangeInfo) {
@Override
protected void performRefactoring(UsageInfo[] usages) {
super.performRefactoring(usages);
final GrOpenBlock block = constructor.getBlock();
LOG.assertTrue(block != null);
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(project);
final String text;
if (StringUtil.equals(selectedValue.getName(), finalParameterName)) {
text = "this." + selectedValue.getName() + " = " + finalParameterName;
}
else {
text = selectedValue.getName() + " = " + finalParameterName;
}
final GrStatement assignment = factory.createStatementFromText(text);
final GrStatement statement = block.addStatementBefore(assignment, null);
final GrReferenceExpression ref = (GrReferenceExpression)((GrAssignmentExpression)statement).getLValue();
if (!PsiManager.getInstance(project).areElementsEquivalent(ref.resolve(), selectedValue)) {
PsiUtil.qualifyMemberReference(ref, selectedValue, selectedValue.getName());
}
}
};
processor.run();
}
@NotNull
@Override
protected PsiElementPredicate getElementPredicate() {
return new MyPredicate();
}
static class MyPredicate implements PsiElementPredicate {
@Override
public boolean satisfiedBy(PsiElement element) {
final List<GrField> candidates = findFieldCandidates(element);
if (candidates != null && !candidates.isEmpty()) return true;
final List<GrMethod> constructors = findConstructorCandidates(element);
return constructors != null && !constructors.isEmpty();
}
}
@Nullable
private static List<GrField> findFieldCandidates(PsiElement element) {
final GrMethod constructor = PsiTreeUtil.getParentOfType(element, GrMethod.class);
if (constructor == null || !constructor.isConstructor()) return null;
if (constructor.getBlock() == null) return null;
if (PsiTreeUtil.isAncestor(constructor.getBlock(), element, false)) {
return null;
}
final PsiClass clazz = constructor.getContainingClass();
if (!(clazz instanceof GrTypeDefinition)) return null;
return findCandidatesCached(constructor, (GrTypeDefinition)clazz);
}
private static List<GrField> findCandidates(GrMethod constructor, final GrTypeDefinition clazz) {
final List<GrField> usedFields = new ArrayList<GrField>();
final GrOpenBlock block = constructor.getBlock();
if (block == null) {
return usedFields;
}
final PsiManager manager = clazz.getManager();
block.accept(new GroovyRecursiveElementVisitor() {
@Override
public void visitReferenceExpression(GrReferenceExpression referenceExpression) {
super.visitReferenceExpression(referenceExpression);
final PsiElement resolved = referenceExpression.resolve();
if (resolved instanceof GrField &&
manager.areElementsEquivalent(((GrField)resolved).getContainingClass(), clazz) &&
PsiUtil.isAccessedForWriting(referenceExpression)) {
usedFields.add((GrField)resolved);
}
}
@Override
public void visitTypeDefinition(GrTypeDefinition typeDefinition) {
}
@Override
public void visitClosure(GrClosableBlock closure) {
}
});
List<GrField> fields = new ArrayList<GrField>();
for (final GrField field : clazz.getFields()) {
if (field.getInitializerGroovy() != null) continue;
if (ContainerUtil.find(usedFields, new Condition<PsiField>() {
@Override
public boolean value(PsiField o) {
return manager.areElementsEquivalent(o, field);
}
}) == null) {
fields.add(field);
}
}
return fields;
}
private static List<GrField> findCandidatesCached(final GrMethod constructor, final GrTypeDefinition clazz) {
final CachedValue<List<GrField>> value = constructor.getUserData(FIELD_CANDIDATES);
if (value != null && value.getValue() != null) return value.getValue();
final CachedValue<List<GrField>> cachedValue =
CachedValuesManager.getManager(constructor.getProject()).createCachedValue(new CachedValueProvider<List<GrField>>() {
@Override
public Result<List<GrField>> compute() {
return Result.create(findCandidates(constructor, clazz), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
}
}, false);
constructor.putUserData(FIELD_CANDIDATES, cachedValue);
return cachedValue.getValue();
}
@Nullable
private static List<GrMethod> findConstructorCandidates(PsiElement element) {
final GrField field = PsiTreeUtil.getParentOfType(element, GrField.class);
if (field == null) return null;
return findConstructorCandidates(field, (GrTypeDefinition)field.getContainingClass());
}
private static List<GrMethod> findConstructorCandidates(final GrField field, GrTypeDefinition psiClass) {
final List<GrMethod> result = new ArrayList<GrMethod>();
final PsiMethod[] constructors = psiClass.getConstructors();
final PsiManager manager = field.getManager();
for (PsiMethod constructor : constructors) {
final List<GrField> fields = findCandidatesCached(((GrMethod)constructor), psiClass);
if (ContainerUtil.find(fields, new Condition<GrField>() {
@Override
public boolean value(GrField grField) {
return manager.areElementsEquivalent(grField, field);
}
}) != null) {
result.add((GrMethod)constructor);
}
}
return result;
}
}