blob: f4286ee1706be736d528ba25e9264032485f0e48 [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.refactoring.changeSignature;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.changeSignature.CallerChooserBase;
import com.intellij.refactoring.changeSignature.ChangeSignatureDialogBase;
import com.intellij.refactoring.changeSignature.ExceptionsTableModel;
import com.intellij.refactoring.changeSignature.ThrownExceptionInfo;
import com.intellij.refactoring.ui.CodeFragmentTableCellRenderer;
import com.intellij.refactoring.ui.JavaCodeFragmentTableCellEditor;
import com.intellij.refactoring.ui.VisibilityPanelBase;
import com.intellij.refactoring.util.CanonicalTypes;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.ToolbarDecorator;
import com.intellij.ui.table.JBTable;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
import org.jetbrains.plugins.groovy.refactoring.ui.GroovyComboboxVisibilityPanel;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author Max Medvedev
*/
public class GrChangeSignatureDialog extends ChangeSignatureDialogBase<GrParameterInfo, PsiMethod, String, GrMethodDescriptor, GrParameterTableModelItem, GrParameterTableModel > {
private static final Logger LOG = Logger.getInstance(GrChangeSignatureDialog.class);
private static final String INDENT = " ";
private ExceptionsTableModel myExceptionsModel;
public GrChangeSignatureDialog(Project project,
GrMethodDescriptor method,
boolean allowDelegation,
PsiElement defaultValueContext) {
super(project, method, allowDelegation, defaultValueContext);
}
@Override
protected LanguageFileType getFileType() {
return GroovyFileType.GROOVY_FILE_TYPE;
}
@Override
protected GrParameterTableModel createParametersInfoModel(GrMethodDescriptor method) {
final PsiParameterList parameterList = method.getMethod().getParameterList();
return new GrParameterTableModel(parameterList, myDefaultValueContext, this);
}
@Override
protected BaseRefactoringProcessor createRefactoringProcessor() {
final CanonicalTypes.Type type = getReturnType();
final ThrownExceptionInfo[] exceptionInfos = myExceptionsModel.getThrownExceptions();
final GrChangeInfoImpl info = new GrChangeInfoImpl(myMethod.getMethod(), getVisibility(), type, getMethodName(), getParameters(), exceptionInfos, isGenerateDelegate());
return new GrChangeSignatureProcessor(myProject, info);
}
@NotNull
@Override
protected List<Pair<String, JPanel>> createAdditionalPanels() {
// this method is invoked before constructor body
myExceptionsModel = new ExceptionsTableModel(myMethod.getMethod().getThrowsList());
myExceptionsModel.setTypeInfos(myMethod.getMethod());
final JBTable table = new JBTable(myExceptionsModel);
table.setStriped(true);
table.setRowHeight(20);
table.getColumnModel().getColumn(0).setCellRenderer(new CodeFragmentTableCellRenderer(myProject));
final JavaCodeFragmentTableCellEditor cellEditor = new JavaCodeFragmentTableCellEditor(myProject);
cellEditor.addDocumentListener(new DocumentAdapter() {
@Override
public void documentChanged(DocumentEvent e) {
final int row = table.getSelectedRow();
final int col = table.getSelectedColumn();
myExceptionsModel.setValueAt(cellEditor.getCellEditorValue(), row, col);
updateSignature();
}
});
table.getColumnModel().getColumn(0).setCellEditor(cellEditor);
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getSelectionModel().setSelectionInterval(0, 0);
table.setSurrendersFocusOnKeystroke(true);
/* myPropExceptionsButton = new AnActionButton( //todo propagate parameters
RefactoringBundle.message("changeSignature.propagate.exceptions.title"), null, PlatformIcons.NEW_EXCEPTION) {
@Override
public void actionPerformed(AnActionEvent e) {
final Ref<JavaCallerChooser> chooser = new Ref<JavaCallerChooser>();
Consumer<Set<PsiMethod>> callback = new Consumer<Set<PsiMethod>>() {
@Override
public void consume(Set<PsiMethod> psiMethods) {
myMethodsToPropagateExceptions = psiMethods;
myExceptionPropagationTree = chooser.get().getTree();
}
};
chooser.set(new JavaCallerChooser(myMethod.getMethod(),
myProject,
RefactoringBundle.message("changeSignature.exception.caller.chooser"),
myExceptionPropagationTree,
callback));
chooser.get().show();
}
};
myPropExceptionsButton.setShortcut(CustomShortcutSet.fromString("alt X"));*/
final JPanel panel = ToolbarDecorator.createDecorator(table).createPanel();
//.addExtraAction(myPropExceptionsButton).createPanel();
panel.setBorder(IdeBorderFactory.createEmptyBorder());
myExceptionsModel.addTableModelListener(mySignatureUpdater);
final ArrayList<Pair<String, JPanel>> result = new ArrayList<Pair<String, JPanel>>();
final String message = RefactoringBundle.message("changeSignature.exceptions.panel.border.title");
result.add(Pair.create(message, panel));
return result;
}
@Nullable
private CanonicalTypes.Type getReturnType() {
PsiType returnType = null;
try {
if (myReturnTypeCodeFragment != null) {
returnType = ((PsiTypeCodeFragment)myReturnTypeCodeFragment).getType();
}
}
catch (PsiTypeCodeFragment.TypeSyntaxException ignored) {
}
catch (PsiTypeCodeFragment.NoTypeException ignored) {
}
return returnType == null ? null : CanonicalTypes.createTypeWrapper(returnType);
}
@Override
protected PsiCodeFragment createReturnTypeCodeFragment() {
final JavaCodeFragmentFactory factory = JavaCodeFragmentFactory.getInstance(myProject);
return factory.createTypeCodeFragment(myMethod.getReturnTypeText(), myMethod.getMethod(), true, JavaCodeFragmentFactory.ALLOW_VOID);
}
@Nullable
@Override
protected CallerChooserBase<PsiMethod> createCallerChooser(String title, Tree treeToReuse, Consumer<Set<PsiMethod>> callback) {
return null; //todo next iteration
}
private boolean isGroovyMethodName(String name) {
String methodText = "def " + name + "(){}";
try {
final GrMethod method = GroovyPsiElementFactory.getInstance(getProject()).createMethodFromText(methodText);
return method != null;
}
catch (Throwable e) {
return false;
}
}
private static boolean checkType(PsiTypeCodeFragment typeCodeFragment, boolean allowEllipsis) {
try {
final PsiType type = typeCodeFragment.getType();
return allowEllipsis || !(type instanceof PsiEllipsisType);
}
catch (PsiTypeCodeFragment.TypeSyntaxException e) {
return false;
}
catch (PsiTypeCodeFragment.NoTypeException e) {
return true; //Groovy accepts methods and parameters without explicit type
}
}
@Nullable
@Override
protected String validateAndCommitData() {
if (myReturnTypeCodeFragment != null && !checkType((PsiTypeCodeFragment)myReturnTypeCodeFragment, true)) {
return GroovyRefactoringBundle.message("return.type.is.wrong");
}
List<GrParameterTableModelItem> parameterInfos = myParametersTableModel.getItems();
int newParameterCount = parameterInfos.size();
for (int i = 0; i < newParameterCount; i++) {
GrParameterTableModelItem item = parameterInfos.get(i);
String name = item.parameter.getName();
if (!StringUtil.isJavaIdentifier(name)) {
return GroovyRefactoringBundle.message("name.is.wrong", name);
}
if (!checkType((PsiTypeCodeFragment)item.typeCodeFragment, i == newParameterCount - 1)) {
return GroovyRefactoringBundle.message("type.for.parameter.is.incorrect", name);
}
try {
item.parameter.setType(((PsiTypeCodeFragment)item.typeCodeFragment).getType());
}
catch (PsiTypeCodeFragment.TypeSyntaxException e) {
LOG.error(e);
}
catch (PsiTypeCodeFragment.NoTypeException e) {
item.parameter.setType(null);
}
String defaultValue = item.defaultValueCodeFragment.getText();
final String initializer = item.initializerCodeFragment.getText();
if (item.parameter.getOldIndex() < 0 && defaultValue.trim().isEmpty() && initializer.trim().isEmpty()) {
return GroovyRefactoringBundle.message("specify.default.value", name);
}
item.parameter.setInitializer(initializer);
item.parameter.setDefaultValue(defaultValue);
}
ThrownExceptionInfo[] exceptionInfos = myExceptionsModel.getThrownExceptions();
PsiTypeCodeFragment[] typeCodeFragments = myExceptionsModel.getTypeCodeFragments();
for (int i = 0; i < exceptionInfos.length; i++) {
ThrownExceptionInfo exceptionInfo = exceptionInfos[i];
PsiTypeCodeFragment typeCodeFragment = typeCodeFragments[i];
try {
PsiType type = typeCodeFragment.getType();
if (!(type instanceof PsiClassType)) {
return GroovyRefactoringBundle.message("changeSignature.wrong.type.for.exception", typeCodeFragment.getText());
}
PsiElementFactory factory = JavaPsiFacade.getInstance(getProject()).getElementFactory();
PsiClassType throwable = factory.createTypeByFQClassName("java.lang.Throwable", myMethod.getMethod().getResolveScope());
if (!throwable.isAssignableFrom(type)) {
return GroovyRefactoringBundle.message("changeSignature.not.throwable.type", typeCodeFragment.getText());
}
exceptionInfo.setType((PsiClassType)type);
}
catch (PsiTypeCodeFragment.TypeSyntaxException e) {
return GroovyRefactoringBundle.message("changeSignature.wrong.type.for.exception", typeCodeFragment.getText());
}
catch (PsiTypeCodeFragment.NoTypeException e) {
return GroovyRefactoringBundle.message("changeSignature.no.type.for.exception");
}
}
return null;
}
private static String generateParameterText(GrParameterInfo info) {
StringBuilder builder = new StringBuilder();
String typeText = info.getTypeText();
if (typeText.isEmpty()) typeText = GrModifier.DEF;
builder.append(typeText).append(' ');
builder.append(info.getName());
String initializer = info.getDefaultInitializer();
if (!StringUtil.isEmpty(initializer)) {
builder.append(" = ").append(initializer);
}
return builder.toString();
}
@Override
protected String calculateSignature() {
String type = myReturnTypeCodeFragment != null ? myReturnTypeCodeFragment.getText().trim() : "";
StringBuilder builder = new StringBuilder();
builder.append(myVisibilityPanel.getVisibility()).append(' ');
if (!type.isEmpty()) {
builder.append(type).append(' ');
}
builder.append(GrChangeSignatureUtil.getNameWithQuotesIfNeeded(getMethodName(), getProject()));
builder.append('(');
final List<GrParameterInfo> infos = getParameters();
if (!infos.isEmpty()) {
final List<String> paramsText = ContainerUtil.map(infos, new Function<GrParameterInfo, String>() {
@Override
public String fun(GrParameterInfo info) {
return generateParameterText(info);
}
});
builder.append("\n").append(INDENT);
builder.append(StringUtil.join(paramsText, ",\n" + INDENT));
builder.append('\n');
}
builder.append(')');
final PsiTypeCodeFragment[] exceptions = myExceptionsModel.getTypeCodeFragments();
if (exceptions.length > 0) {
builder.append("\nthrows\n");
final List<String> exceptionNames = ContainerUtil.map(exceptions, new Function<PsiTypeCodeFragment, String>() {
@Override
public String fun(PsiTypeCodeFragment fragment) {
return fragment.getText();
}
});
builder.append(INDENT).append(StringUtil.join(exceptionNames, ",\n" + INDENT));
}
return builder.toString();
}
@Override
protected VisibilityPanelBase<String> createVisibilityControl() {
return new GroovyComboboxVisibilityPanel();
}
}