--- /dev/null
+<spot>class MyClass {
+ int a
+ int b
+}</spot>
+def foo = <spot>new MyClass(a:2, b:3)</spot>
\ No newline at end of file
--- /dev/null
+def foo = <spot>[a:2, b:3]</spot>
\ No newline at end of file
--- /dev/null
+<html>
+<body>
+<span style="font-family: verdana,serif; font-size: smaller;">Converts native maps to class with fields</span>
+</body>
+</html>
\ No newline at end of file
<categoryKey>intention.category.groovy/intention.category.conversions</categoryKey>
<className>org.jetbrains.plugins.groovy.intentions.conversions.RemoveUnnecessaryBracesInGStringIntention</className>
</intentionAction>
+ <intentionAction>
+ <bundleName>org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle</bundleName>
+ <categoryKey>intention.category.groovy/intention.category.conversions</categoryKey>
+ <className>org.jetbrains.plugins.groovy.intentions.conversions.ConvertMapToClassIntention</className>
+ </intentionAction>
<intentionAction>
<bundleName>org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle</bundleName>
<categoryKey>intention.category.groovy/intention.category.conversions</categoryKey>
/**
* @author ilyas
*/
-abstract class CreateClassActionBase implements IntentionAction {
+public abstract class CreateClassActionBase implements IntentionAction {
protected final GrReferenceElement myRefElement;
private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.annotator.intentions.CreateClassActionBase");
return true;
}
- static PsiClass createClassByType(final PsiDirectory directory,
+ public static PsiClass createClassByType(final PsiDirectory directory,
final String name,
final PsiManager manager,
final PsiElement contextElement) {
});
}
- static Editor putCursor(Project project, @NotNull PsiFile targetFile, PsiElement element) {
+ public static Editor putCursor(Project project, @NotNull PsiFile targetFile, PsiElement element) {
TextRange range = element.getTextRange();
int textOffset = range.getStartOffset();
cannot.suggest.parameter.name=Cannot suggest parameter name
create.parameter.for.field=Add parameter for field ''{0}''
create.parameter.for.fields=Add parameter for field...
-closure.used.as.variable=Closure is passed as argument. Refactoring can break semantic.
\ No newline at end of file
+closure.used.as.variable=Closure is passed as argument. Refactoring can break semantic.
+
+convert.map.to.class.intention.name = Convert to class
+convert.map.to.class.intention.family.name = Convert Groovy native map to class instances
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.plugins.groovy.intentions.conversions.ConvertMapToClassDialog">
+ <grid id="cbd77" binding="contentPane" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="10" left="10" bottom="10" right="10"/>
+ <constraints>
+ <xy x="48" y="54" width="436" height="297"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <grid id="e3588" layout-manager="GridLayoutManager" row-count="5" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="2" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="d67d5" class="com.intellij.ui.EditorTextField">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <vspacer id="e0f54">
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <component id="22822" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Class &name:"/>
+ </properties>
+ </component>
+ <component id="be289" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Packa&ge name:"/>
+ </properties>
+ </component>
+ <component id="cead8" class="com.intellij.refactoring.ui.PackageNameReferenceEditorCombo" binding="myPackageNameReferenceEditorCombo" custom-create="true" default-binding="true">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ </children>
+ </grid>
+</form>
--- /dev/null
+/*
+ * Copyright 2000-2010 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.openapi.project.Project;
+import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
+import com.intellij.refactoring.ui.RefactoringDialog;
+
+import javax.swing.*;
+
+public class ConvertMapToClassDialog extends RefactoringDialog {
+ private JPanel contentPane;
+ private PackageNameReferenceEditorCombo myPackageNameReferenceEditorCombo;
+ private JButton buttonOK;
+
+ public ConvertMapToClassDialog(Project project) {
+ super(project, true);
+ }
+
+ @Override
+ protected void doAction() {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ protected JComponent createCenterPanel() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ private void createUIComponents() {
+// myPackageNameReferenceEditorCombo = new PackageNameReferenceEditorCombo("", getProject(), PackageNameReferenceEditorCombo.);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 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.daemon.impl.quickfix.CreateClassKind;
+import com.intellij.codeInsight.intention.impl.CreateClassDialog;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.module.ModuleUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiType;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.GroovyBundle;
+import org.jetbrains.plugins.groovy.annotator.intentions.CreateClassActionBase;
+import org.jetbrains.plugins.groovy.intentions.base.Intention;
+import org.jetbrains.plugins.groovy.intentions.base.PsiElementPredicate;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
+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.GrVariable;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
+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.impl.statements.expressions.TypesUtil;
+import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
+
+/**
+ * @author Maxim.Medvedev
+ */
+public class ConvertMapToClassIntention extends Intention {
+ private static Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.intentions.conversions.ConvertMapToClassIntention");
+
+ @Override
+ protected void processIntention(@NotNull PsiElement element, Project project, Editor editor) throws IncorrectOperationException {
+ final GrListOrMap map = (GrListOrMap)element;
+ final GrNamedArgument[] namedArguments = map.getNamedArguments();
+ LOG.assertTrue(map.getInitializers().length == 0);
+ final String packageName = ((GroovyFileBase)map.getContainingFile()).getPackageName();
+
+ final CreateClassDialog dialog =
+ new CreateClassDialog(project, GroovyBundle.message("create.class.family.name"), "", packageName, CreateClassKind.CLASS, true,
+ ModuleUtil.findModuleForPsiElement(element));
+ dialog.show();
+ if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) return;
+
+ 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);
+ final PsiClass replaced = (PsiClass)generatedClass.replace(typeDefinition);
+ replaceMapWithClass(project, map, replaced);
+ }
+
+ public static void replaceMapWithClass(Project project, GrListOrMap map, PsiClass generatedClass) {
+ PsiUtil.shortenReferences((GroovyPsiElement)generatedClass);
+ CreateClassActionBase.putCursor(project, generatedClass.getContainingFile(), 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));
+ 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) {
+ ((GrVariable)parent).setType(replacedNewExpression.getType());
+ }
+ PsiUtil.shortenReferences(replacedNewExpression);
+ }
+
+ public static GrTypeDefinition createClass(Project project, GrNamedArgument[] namedArguments, String packageName, String className) {
+ StringBuilder classText = new StringBuilder();
+ if (packageName.length() > 0) {
+ 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;
+ }
+}
return getConstructorCandidates(context, new GroovyResolveResult[]{grResult}, argTypes);
}
- public static PsiElement skipParentheses(PsiElement element, boolean up) {
+ @Nullable
+ public static PsiElement skipParentheses(@Nullable PsiElement element, boolean up) {
+ if (element == null) return null;
if (up) {
PsiElement parent;
while ((parent=element.getParent()) instanceof GrParenthesizedExpression) {
--- /dev/null
+/*
+ * Copyright 2000-2010 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;
+
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import org.jetbrains.plugins.groovy.intentions.conversions.ConvertMapToClassIntention;
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
+import org.jetbrains.plugins.groovy.util.TestUtils;
+
+import java.util.List;
+
+/**
+ * @author Maxim.Medvedev
+ */
+public class ConvertMapToClassTest extends GrIntentionTestCase {
+ @Override
+ protected String getBasePath() {
+ return TestUtils.getTestDataPath() + "intentions/convertMapToClass/";
+ }
+
+ public void testSimple() {
+ doTest();
+ }
+
+ public void testVariableTypeReplace() {
+ doTest();
+ }
+
+ public void testBadCase() {
+ doTest(false);
+ }
+
+ private void doTest() {
+ doTest(true);
+ }
+
+ private void doTest(boolean exists) {
+ myFixture.configureByFile(getTestName(true) + "/Test.groovy");
+ String hint = GroovyIntentionsBundle.message("convert.map.to.class.intention.name");
+ final List<IntentionAction> list = myFixture.filterAvailableIntentions(hint);
+ if (!exists) {
+ assertEmpty(list);
+ return;
+ }
+ assertOneElement(list);
+ final PsiElement element = myFixture.getFile().findElementAt(myFixture.getEditor().getCaretModel().getOffset());
+ final GrListOrMap map = PsiTreeUtil.getParentOfType(element, GrListOrMap.class);
+ assertNotNull(map);
+ final GrTypeDefinition foo = ConvertMapToClassIntention.createClass(getProject(), map.getNamedArguments(), "", "Foo");
+ myFixture.addFileToProject(getTestName(true) + "/Foo.groovy", foo.getContainingFile().getText());
+ final PsiClass psiClass = myFixture.findClass("Foo");
+ ConvertMapToClassIntention.replaceMapWithClass(getProject(), map, psiClass);
+ myFixture.checkResultByFile(getTestName(true) + "/Foo.groovy", getTestName(true) + "/Expected.groovy", true);
+ myFixture.checkResultByFile(getTestName(true) + "/Test_after.groovy", true);
+ }
+}
--- /dev/null
+def list = [1, 2, <caret>3 ]
\ No newline at end of file
--- /dev/null
+class Foo {
+int a
+int b
+}
\ No newline at end of file
--- /dev/null
+def map = [a:3, b<caret>:4]
+
+print map.a
\ No newline at end of file
--- /dev/null
+def map = new Foo(a: 3, b: 4)
+
+print map.a
\ No newline at end of file
--- /dev/null
+class Foo {
+String a
+Date b
+}
\ No newline at end of file
--- /dev/null
+Map map = [<caret>a:"d", b: new Date()]
+
+print map.b
\ No newline at end of file
--- /dev/null
+Foo map = new Foo(a: "d", b: new Date())
+
+print map.b
\ No newline at end of file