blob: e7c30b32dd9169f165bb6216b60f5b3547bae54a [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.conversions;
import com.intellij.codeInsight.intention.impl.CreateClassDialog;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyBundle;
import org.jetbrains.plugins.groovy.actions.GroovyTemplates;
import org.jetbrains.plugins.groovy.annotator.intentions.CreateClassActionBase;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.codeStyle.GrReferenceAdjuster;
import org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle;
import org.jetbrains.plugins.groovy.intentions.base.Intention;
import org.jetbrains.plugins.groovy.intentions.base.IntentionUtils;
import org.jetbrains.plugins.groovy.intentions.base.PsiElementPredicate;
import org.jetbrains.plugins.groovy.lang.GrCreateClassKind;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier;
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.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
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.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import java.util.List;
import java.util.Map;
/**
* @author Maxim.Medvedev
*/
public class ConvertMapToClassIntention extends Intention {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.intentions.conversions.ConvertMapToClassIntention");
@Override
protected void processIntention(@NotNull PsiElement element, final Project project, Editor editor) throws IncorrectOperationException {
final GrListOrMap map = (GrListOrMap)element;
final GrNamedArgument[] namedArguments = map.getNamedArguments();
LOG.assertTrue(map.getInitializers().length == 0);
final PsiFile file = map.getContainingFile();
final String packageName = file instanceof GroovyFileBase ? ((GroovyFileBase)file).getPackageName() : "";
final CreateClassDialog dialog =
new CreateClassDialog(project, GroovyBundle.message("create.class.family.name"), "", packageName, GrCreateClassKind.CLASS, true,
ModuleUtilCore.findModuleForPsiElement(element));
dialog.show();
if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) return;
boolean replaceReturnType = checkForReturnFromMethod(map);
boolean variableDeclaration = checkForVariableDeclaration(map);
final GrParameter methodParameter = checkForMethodParameter(map);
final String qualifiedClassName = dialog.getClassName();
final String selectedPackageName = StringUtil.getPackageName(qualifiedClassName);
final String shortName = StringUtil.getShortName(qualifiedClassName);
final GrTypeDefinition typeDefinition = createClass(project, namedArguments, selectedPackageName, shortName);
final PsiClass generatedClass = CreateClassActionBase.createClassByType(
dialog.getTargetDirectory(), typeDefinition.getName(), PsiManager.getInstance(project), map, GroovyTemplates.GROOVY_CLASS, true);
final PsiClass replaced = (PsiClass)generatedClass.replace(typeDefinition);
replaceMapWithClass(project, map, replaced, replaceReturnType, variableDeclaration, methodParameter);
}
public static void replaceMapWithClass(Project project,
final GrListOrMap map,
PsiClass generatedClass,
boolean replaceReturnType,
boolean variableDeclaration,
GrParameter parameter) {
JavaCodeStyleManager.getInstance(project).shortenClassReferences(generatedClass);
final String text = map.getText();
int begin = 0;
int end = text.length();
if (text.startsWith("[")) begin++;
if (text.endsWith("]")) end--;
final GrExpression newExpression = GroovyPsiElementFactory.getInstance(project)
.createExpressionFromText("new " + generatedClass.getQualifiedName() + "(" + text.substring(begin, end) + ")");
final GrExpression replacedNewExpression = ((GrExpression)map.replace(newExpression));
if (replaceReturnType) {
final PsiType type = replacedNewExpression.getType();
final GrMethod method = PsiTreeUtil.getParentOfType(replacedNewExpression, GrMethod.class, true, GrClosableBlock.class);
LOG.assertTrue(method != null);
GrReferenceAdjuster.shortenAllReferencesIn(method.setReturnType(type));
}
if (variableDeclaration) {
final PsiElement parent = PsiUtil.skipParentheses(replacedNewExpression.getParent(), true);
((GrVariable)parent).setType(replacedNewExpression.getType());
}
if (parameter != null) {
parameter.setType(newExpression.getType());
}
JavaCodeStyleManager.getInstance(project).shortenClassReferences(replacedNewExpression);
IntentionUtils.positionCursor(project, generatedClass.getContainingFile(), generatedClass);
}
public static boolean checkForReturnFromMethod(GrExpression replacedNewExpression) {
final PsiElement parent = PsiUtil.skipParentheses(replacedNewExpression.getParent(), true);
final GrMethod method = PsiTreeUtil.getParentOfType(replacedNewExpression, GrMethod.class, true, GrClosableBlock.class);
if (method == null) return false;
if (!(parent instanceof GrReturnStatement)) { //check for return expression
final List<GrStatement> returns = ControlFlowUtils.collectReturns(method.getBlock());
final PsiElement expr = PsiUtil.skipParentheses(replacedNewExpression, true);
if (!(returns.contains(expr))) return false;
}
return !(!ApplicationManager.getApplication().isUnitTestMode() && Messages.showYesNoDialog(replacedNewExpression.getProject(),
GroovyIntentionsBundle.message(
"do.you.want.to.change.method.return.type",
method.getName()),
GroovyIntentionsBundle
.message(
"convert.map.to.class.intention.name"),
Messages.getQuestionIcon()) != Messages.YES);
}
public static boolean checkForVariableDeclaration(GrExpression replacedNewExpression) {
final PsiElement parent = PsiUtil.skipParentheses(replacedNewExpression.getParent(), true);
if (parent instanceof GrVariable &&
!(parent instanceof GrField) &&
!(parent instanceof GrParameter) &&
((GrVariable)parent).getDeclaredType() != null &&
replacedNewExpression.getType() != null) {
if (ApplicationManager.getApplication().isUnitTestMode() || Messages.showYesNoDialog(replacedNewExpression.getProject(),
GroovyIntentionsBundle.message(
"do.you.want.to.change.variable.type",
((GrVariable)parent).getName()),
GroovyIntentionsBundle.message(
"convert.map.to.class.intention.name"),
Messages.getQuestionIcon()) ==
Messages.YES) {
return true;
}
}
return false;
}
@Nullable
private static GrParameter getParameterByArgument(GrExpression arg) {
PsiElement parent = PsiUtil.skipParentheses(arg.getParent(), true);
if (!(parent instanceof GrArgumentList)) return null;
final GrArgumentList argList = (GrArgumentList)parent;
parent = parent.getParent();
if (!(parent instanceof GrMethodCall)) return null;
final GrMethodCall methodCall = (GrMethodCall)parent;
final GrExpression expression = methodCall.getInvokedExpression();
if (!(expression instanceof GrReferenceExpression)) return null;
final GroovyResolveResult resolveResult = ((GrReferenceExpression)expression).advancedResolve();
if (resolveResult == null) return null;
GrClosableBlock[] closures = methodCall.getClosureArguments();
final Map<GrExpression, Pair<PsiParameter, PsiType>> mapToParams = GrClosureSignatureUtil
.mapArgumentsToParameters(resolveResult, arg, false, false, argList.getNamedArguments(), argList.getExpressionArguments(), closures);
if (mapToParams == null) return null;
final Pair<PsiParameter, PsiType> parameterPair = mapToParams.get(arg);
final PsiParameter parameter = parameterPair == null ? null : parameterPair.getFirst();
return parameter instanceof GrParameter? ((GrParameter)parameter):null;
}
@Nullable
public static GrParameter checkForMethodParameter(GrExpression map) {
final GrParameter parameter = getParameterByArgument(map);
if (parameter == null) return null;
final PsiElement parent = parameter.getParent().getParent();
if (!(parent instanceof PsiMethod)) return null;
final PsiMethod method = (PsiMethod)parent;
if (ApplicationManager.getApplication().isUnitTestMode() ||
Messages.showYesNoDialog(map.getProject(), GroovyIntentionsBundle
.message("do.you.want.to.change.type.of.parameter.in.method", parameter.getName(), method.getName()),
GroovyIntentionsBundle.message("convert.map.to.class.intention.name"), Messages.getQuestionIcon()) == Messages.YES) {
return parameter;
}
return null;
}
public static GrTypeDefinition createClass(Project project, GrNamedArgument[] namedArguments, String packageName, String className) {
StringBuilder classText = new StringBuilder();
if (!packageName.isEmpty()) {
classText.append("package ").append(packageName).append('\n');
}
classText.append("class ").append(className).append(" {\n");
for (GrNamedArgument argument : namedArguments) {
final String fieldName = argument.getLabelName();
final GrExpression expression = argument.getExpression();
LOG.assertTrue(expression != null);
final PsiType type = TypesUtil.unboxPrimitiveTypeWrapper(expression.getType());
if (type != null) {
classText.append(type.getCanonicalText());
}
else {
classText.append(GrModifier.DEF);
}
classText.append(' ').append(fieldName).append('\n');
}
classText.append('}');
return GroovyPsiElementFactory.getInstance(project).createTypeDefinition(classText.toString());
}
@NotNull
@Override
protected PsiElementPredicate getElementPredicate() {
return new MyPredicate();
}
}
class MyPredicate implements PsiElementPredicate {
@Override
public boolean satisfiedBy(PsiElement element) {
if (!(element instanceof GrListOrMap)) return false;
final GrListOrMap map = (GrListOrMap)element;
final GrNamedArgument[] namedArguments = map.getNamedArguments();
final GrExpression[] initializers = map.getInitializers();
if (initializers.length != 0) return false;
for (GrNamedArgument argument : namedArguments) {
final GrArgumentLabel label = argument.getLabel();
final GrExpression expression = argument.getExpression();
if (label == null || expression == null) return false;
if (label.getName() == null) return false;
}
return true;
}
}