diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/GroovyCoreEnvironment.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/GroovyCoreEnvironment.java index 0492b241473a..3a90f63ba359 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/GroovyCoreEnvironment.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/GroovyCoreEnvironment.java @@ -93,7 +93,10 @@ import org.jetbrains.plugins.groovy.lang.psi.stubs.index.*; import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.*; import org.jetbrains.plugins.groovy.lang.psi.util.GroovyConstantExpressionEvaluator; import org.jetbrains.plugins.groovy.lang.resolve.*; -import org.jetbrains.plugins.groovy.lang.resolve.ast.*; +import org.jetbrains.plugins.groovy.lang.resolve.ast.AutoExternalizeContributor; +import org.jetbrains.plugins.groovy.lang.resolve.ast.ConstructorAnnotationsProcessor; +import org.jetbrains.plugins.groovy.lang.resolve.ast.InheritConstructorContributor; +import org.jetbrains.plugins.groovy.lang.resolve.ast.LoggingContributor; import org.jetbrains.plugins.groovy.lang.resolve.ast.builder.strategy.DefaultBuilderStrategySupport; import org.jetbrains.plugins.groovy.lang.resolve.ast.builder.strategy.ExternalBuilderStrategySupport; import org.jetbrains.plugins.groovy.lang.resolve.ast.builder.strategy.InitializerBuilderStrategySupport; @@ -108,6 +111,7 @@ import org.jetbrains.plugins.groovy.swingBuilder.SwingBuilderNamedArgumentProvid import org.jetbrains.plugins.groovy.swingBuilder.SwingBuilderNonCodeMemberContributor; import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport; import org.jetbrains.plugins.groovy.transformations.impl.*; +import org.jetbrains.plugins.groovy.transformations.impl.autoClone.AutoCloneTransformationSupport; /** * Upsource @@ -143,7 +147,7 @@ public class GroovyCoreEnvironment { CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), AstTransformationSupport.EP_NAME, AstTransformationSupport.class); appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new AutoExternalizeContributor()); - appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new AutoCloneContributor()); + appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new AutoCloneTransformationSupport()); appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new ConstructorAnnotationsProcessor()); appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new InheritConstructorContributor()); appEnvironment.addExtension(AstTransformationSupport.EP_NAME, new DefaultBuilderStrategySupport()); diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyCommonClassNames.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyCommonClassNames.java index 55b68723c0c7..1170174f466f 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyCommonClassNames.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyCommonClassNames.java @@ -46,7 +46,6 @@ public final class GroovyCommonClassNames { public static final String GROOVY_LANG_REFERENCE = "groovy.lang.Reference"; public static final String JAVA_UTIL_LINKED_HASH_MAP = "java.util.LinkedHashMap"; public static final String GROOVY_TRANSFORM_AUTO_EXTERNALIZE = "groovy.transform.AutoExternalize"; - public static final String GROOVY_TRANSFORM_AUTO_CLONE = "groovy.transform.AutoClone"; public static final String GROOVY_LANG_CATEGORY = "groovy.lang.Category"; public static final String GROOVY_LANG_MIXIN = "groovy.lang.Mixin"; public static final String GROOVY_UTIL_TEST_CASE = "groovy.util.GroovyTestCase"; diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/AutoCloneContributor.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/AutoCloneContributor.java deleted file mode 100644 index c8572cfa476f..000000000000 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/AutoCloneContributor.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2000-2016 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.lang.resolve.ast; - -import com.intellij.psi.PsiModifier; -import com.intellij.psi.impl.light.LightMethodBuilder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; -import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport; -import org.jetbrains.plugins.groovy.transformations.TransformationContext; - -/** - * @author Max Medvedev - */ -public class AutoCloneContributor implements AstTransformationSupport { - - @Override - public void applyTransformation(@NotNull TransformationContext context) { - if (!context.hasAnnotation(GroovyCommonClassNames.GROOVY_TRANSFORM_AUTO_CLONE)) return; - - final LightMethodBuilder clone = new LightMethodBuilder(context.getManager(), "clone"); - clone.addModifier(PsiModifier.PUBLIC); - clone.addException(CloneNotSupportedException.class.getName()); - clone.setOriginInfo("created by @AutoClone"); - context.addMethod(clone); - } -} diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/TransformationUtil.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/TransformationUtil.kt index 277dc9056ff7..95e7d91e5bfb 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/TransformationUtil.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/TransformationUtil.kt @@ -59,3 +59,5 @@ fun isUnderTransformation(clazz: PsiClass?): Boolean { false } } + +infix operator fun TransformationContext.plusAssign(method: PsiMethod) = addMethod(method) diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/autoClone/AutoCloneTransformationSupport.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/autoClone/AutoCloneTransformationSupport.kt new file mode 100644 index 000000000000..ebc1235b6d47 --- /dev/null +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/impl/autoClone/AutoCloneTransformationSupport.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2016 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.transformations.impl.autoClone + +import com.intellij.psi.CommonClassNames +import com.intellij.psi.PsiEnumConstant +import com.intellij.psi.PsiModifier +import com.intellij.psi.PsiType +import com.intellij.psi.impl.light.LightMethodBuilder +import org.jetbrains.plugins.groovy.GroovyLanguage +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil +import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport +import org.jetbrains.plugins.groovy.transformations.TransformationContext +import org.jetbrains.plugins.groovy.transformations.plusAssign + +class AutoCloneTransformationSupport : AstTransformationSupport { + + private companion object { + val AUTO_CLONE_FQN = "groovy.transform.AutoClone" + val ORIGIN_INFO = "created by @AutoClone" + val CNSE_FQN = "java.lang.CloneNotSupportedException" + } + + override fun applyTransformation(context: TransformationContext) { + val annotation = context.getAnnotation(AUTO_CLONE_FQN) ?: return + + context.addInterface(CommonClassNames.JAVA_LANG_CLONEABLE) + + // public T clone() throws CloneNotSupportedException + context += LightMethodBuilder(context.manager, "clone").apply { + addModifier(PsiModifier.PUBLIC) + setMethodReturnType(TypesUtil.createType(context.codeClass)) + addException(CNSE_FQN) + navigationElement = annotation + originInfo = ORIGIN_INFO + } + + val value = annotation.findDeclaredAttributeValue("style") as? GrReferenceExpression ?: return + val constant = value.resolve() as? PsiEnumConstant ?: return + if (constant.containingClass?.qualifiedName != "groovy.transform.AutoCloneStyle") return + when (constant.name) { + "COPY_CONSTRUCTOR" -> { + if (context.codeClass.codeConstructors.size == 0) { + context += LightMethodBuilder(context.codeClass, GroovyLanguage).apply { + isConstructor = true + addModifier(PsiModifier.PUBLIC) + navigationElement = context.codeClass + } + } + + // protected T(T other) + context += LightMethodBuilder(context.codeClass, GroovyLanguage).apply { + isConstructor = true + addModifier(PsiModifier.PROTECTED) + addParameter("other", TypesUtil.createType(context.codeClass)) + navigationElement = context.codeClass + originInfo = ORIGIN_INFO + } + } + "SIMPLE" -> { + // protected void cloneOrCopyMembers(T other) throws CloneNotSupportedException + context += LightMethodBuilder(context.manager, "cloneOrCopyMembers").apply { + addModifier(PsiModifier.PROTECTED) + setMethodReturnType(PsiType.VOID) + addParameter("other", TypesUtil.createType(context.codeClass)) + addException(CNSE_FQN) + navigationElement = annotation + originInfo = ORIGIN_INFO + } + } + } + } +} diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml index 33e18b57861e..5915c5c783c9 100644 --- a/plugins/groovy/src/META-INF/plugin.xml +++ b/plugins/groovy/src/META-INF/plugin.xml @@ -118,7 +118,7 @@ - + diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy index beb5c64d5629..4245e37df048 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy @@ -730,12 +730,6 @@ class Foo { assertNotNull(resolve('a.groovy')) } - void testAutoClone() { - def element = resolve('a.groovy', PsiMethod) - assertTrue element.containingClass.name == 'Foo' - assertSize 1, element.throwsList.referencedTypes - } - void testDontUseQualifierScopeInDGM() { assertNull resolve('a.groovy') } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/GrAutoCloneTransformationSupportTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/GrAutoCloneTransformationSupportTest.groovy new file mode 100644 index 000000000000..f8b2af19aff8 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/GrAutoCloneTransformationSupportTest.groovy @@ -0,0 +1,108 @@ +/* + * Copyright 2000-2016 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.transformations + +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor +import org.jetbrains.plugins.groovy.LightGroovyTestCase +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression + +class GrAutoCloneTransformationSupportTest extends LightGroovyTestCase { + + GroovyLightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST + + void 'test clone() return type'() { + doExpressionTypeTest '''\ +@groovy.transform.AutoClone +class A1 {} +new A1().clone() +''', 'A1' + } + + void 'test clone() return type overridden'() { + doExpressionTypeTest '''\ +@groovy.transform.AutoClone +class A1 {} +@groovy.transform.AutoClone +class A2 extends A1 {} + +new A2().clone() +''', 'A2' + } + + void 'test clone() usage from java'() { + myFixture.with { + addFileToProject 'Pogo.groovy', '''\ +@groovy.transform.AutoClone +class Pogo {} +''' + configureByText 'Main.java', '''\ +class Main { + void foo() { + new Pogo().clone();; + try { + Pogo pogo = new Pogo().clone(); + } catch (java.lang.CloneNotSupportedException e) {} + } +} +''' + checkHighlighting() + } + } + + void 'test copy constructor usage from java'() { + myFixture.with { + addFileToProject 'Pogo.groovy', '''\ +@groovy.transform.AutoClone(style=groovy.transform.AutoCloneStyle.COPY_CONSTRUCTOR) +class Pogo {} +''' + configureByText 'Main.java', '''\ +class Main { + void foo() { + Pogo a = new Pogo(); + Pogo b = new Pogo(a); + } +} +''' + checkHighlighting() + } + } + + void 'test cloneOrCopyMembers() usage from java'() { + myFixture.with { + addFileToProject 'Pogo.groovy', '''\ +@groovy.transform.AutoClone(style=groovy.transform.AutoCloneStyle.SIMPLE) +class Pogo {} +''' + configureByText 'Pojo.java', '''\ +class Pojo extends Pogo { + void foo() { + Pogo pogo = new Pogo(); + cloneOrCopyMembers(pogo); + cloneOrCopyMembers(); + } +} +''' + checkHighlighting() + } + } + + private void doExpressionTypeTest(String text, String expectedType) { + def file = myFixture.configureByText('_.groovy', text) + def ref = file.findReferenceAt(myFixture.editor.caretModel.offset) as GrReferenceExpression + def actual = ref.type + assertType(expectedType, actual) + } +} diff --git a/plugins/groovy/testdata/resolve/method/autoClone/a.groovy b/plugins/groovy/testdata/resolve/method/autoClone/a.groovy deleted file mode 100644 index 45da515d48e2..000000000000 --- a/plugins/groovy/testdata/resolve/method/autoClone/a.groovy +++ /dev/null @@ -1,8 +0,0 @@ -import groovy.transform.AutoClone - -@AutoClone -class Foo { - def foo -} - -print new Foo().clone()