| /* |
| * 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; |
| } |
| } |