diff --git a/java/java-psi-impl/src/META-INF/JavaPsiPlugin.xml b/java/java-psi-impl/src/META-INF/JavaPsiPlugin.xml
index 3deaeff27640..4617dcbf891d 100644
--- a/java/java-psi-impl/src/META-INF/JavaPsiPlugin.xml
+++ b/java/java-psi-impl/src/META-INF/JavaPsiPlugin.xml
@@ -51,6 +51,8 @@
+
@@ -126,10 +128,8 @@
-
-
-
-
+
+
diff --git a/uast/uast-common/src/org/jetbrains/uast/generate/UastCodeGenerationPlugin.kt b/uast/uast-common/src/org/jetbrains/uast/generate/UastCodeGenerationPlugin.kt
new file mode 100644
index 000000000000..15432ea3cd91
--- /dev/null
+++ b/uast/uast-common/src/org/jetbrains/uast/generate/UastCodeGenerationPlugin.kt
@@ -0,0 +1,63 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.uast.generate
+
+import com.intellij.lang.Language
+import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiType
+import org.jetbrains.annotations.ApiStatus
+import org.jetbrains.uast.*
+
+@ApiStatus.Experimental
+interface UastCodeGenerationPlugin {
+ companion object {
+ val extensionPointName = ExtensionPointName("org.jetbrains.uast.generate.uastCodeGenerationPlugin")
+ private val extensions by lazy { extensionPointName.extensionList }
+
+ @JvmStatic
+ fun byLanguage(language: Language) = extensions.firstOrNull { it.language == language }
+ }
+
+ val language: Language
+
+ fun createBinaryExpression(leftOperand: UExpression, rightOperand: UExpression, operator: UastBinaryOperator): UBinaryExpression?
+
+ /**
+ * Create binary expression, and possibly remove unnecessary parenthesis, so it could become [UPolyadicExpression], e.g
+ * [createFlatBinaryExpression](1 + 2, 2, +) could produce 1 + 2 + 2, which is polyadic expression
+ */
+ @JvmDefault
+ fun createFlatBinaryExpression(leftOperand: UExpression, rightOperand: UExpression, operator: UastBinaryOperator): UPolyadicExpression? =
+ createBinaryExpression(leftOperand, rightOperand, operator)
+
+ fun createSimpleReference(name: String, project: Project): USimpleNameReferenceExpression?
+
+ fun createSimpleReference(variable: UVariable): USimpleNameReferenceExpression?
+
+ fun createParenthesizedExpression(expression: UExpression): UParenthesizedExpression?
+
+ fun createReturnExpresion(expression: UExpression, inLambda: Boolean = false): UReturnExpression?
+
+ fun createLocalVariable(name: String?, type: PsiType?, initializer: UExpression, immutable: Boolean = false): ULocalVariable?
+
+ fun createBlockExpression(expressions: List, project: Project): UBlockExpression?
+
+ fun createLambdaExpression(parameters: List, body: UExpression): ULambdaExpression?
+
+ fun createDeclarationExpression(declarations: List, project: Project): UDeclarationsExpression?
+
+ fun replace(oldElement: UElement, newElement: T, elementType: Class): T?
+}
+
+@ApiStatus.Experimental
+inline fun UElement.replace(newElement: T): T? =
+ this.getLanguagePlugin()
+ .let { UastCodeGenerationPlugin.byLanguage(it.language) }
+ ?.replace(this, newElement, T::class.java)
+
+@ApiStatus.Experimental
+inline fun T.refreshed() = sourcePsi?.toUElementOfType()
+
+val UElement.generationPlugin: UastCodeGenerationPlugin?
+ @ApiStatus.Experimental
+ get() = getLanguagePlugin().let { UastCodeGenerationPlugin.byLanguage(it.language) }
\ No newline at end of file
diff --git a/uast/uast-java/src/org/jetbrains/uast/java/generate/JavaUastCodeGenerationPlugin.kt b/uast/uast-java/src/org/jetbrains/uast/java/generate/JavaUastCodeGenerationPlugin.kt
new file mode 100644
index 000000000000..f3c7b3e6d0ee
--- /dev/null
+++ b/uast/uast-java/src/org/jetbrains/uast/java/generate/JavaUastCodeGenerationPlugin.kt
@@ -0,0 +1,200 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.uast.java.generate
+
+import com.intellij.codeInsight.BlockUtils
+import com.intellij.lang.Language
+import com.intellij.lang.java.JavaLanguage
+import com.intellij.lang.jvm.JvmModifier
+import com.intellij.openapi.project.Project
+import com.intellij.psi.*
+import com.intellij.psi.codeStyle.JavaCodeStyleManager
+import com.intellij.psi.codeStyle.VariableKind
+import com.intellij.psi.impl.source.tree.CompositeElement
+import com.intellij.psi.util.PsiUtil
+import com.siyeh.ig.psiutils.ParenthesesUtils
+import org.jetbrains.uast.*
+import org.jetbrains.uast.generate.UastCodeGenerationPlugin
+import org.jetbrains.uast.java.*
+import java.lang.AssertionError
+
+class JavaUastCodeGenerationPlugin : UastCodeGenerationPlugin {
+ override fun replace(oldElement: UElement, newElement: T, elementType: Class): T? =
+ newElement.sourcePsi?.let { oldElement.sourcePsi?.replace(it) }?.toUElementOfExpectedTypes(elementType)
+
+ override fun createDeclarationExpression(declarations: List, project: Project): UDeclarationsExpression? {
+ return JavaUDeclarationsExpression(null, declarations)
+ }
+
+ private fun PsiElementFactory.createExpresionStatement(expression: PsiExpression): PsiStatement? {
+ val statement = createStatementFromText("x;", null) as? PsiExpressionStatement ?: return null
+ statement.expression.replace(expression)
+ return statement
+ }
+
+ override fun createReturnExpresion(expression: UExpression, inLambda: Boolean): UReturnExpression? {
+ val factory = JavaPsiFacade.getElementFactory(expression.sourcePsi?.project ?: return null)
+ val returnStatement = factory.createStatementFromText("return ;", null) as? PsiReturnStatement ?: return null
+
+ (returnStatement as CompositeElement).addChild(expression.sourcePsi!!.node, returnStatement.lastChild.node)
+
+ return JavaUReturnExpression(returnStatement, null)
+ }
+
+ override fun createLocalVariable(name: String?, type: PsiType?, initializer: UExpression, immutable: Boolean): ULocalVariable? {
+ val initializerPsi = initializer.sourcePsi as? PsiExpression ?: return null
+
+ if (name == null) {
+ return createLocalVariableWithSuggestedName(type, initializer, immutable)
+ }
+
+ val factory = JavaPsiFacade.getElementFactory(initializerPsi.project)
+ val variable = (type ?: initializer.getExpressionType())?.let { variableType ->
+ factory.createVariableDeclarationStatement(name, variableType, initializerPsi,
+ initializerPsi.context).declaredElements.firstOrNull() as? PsiLocalVariable
+ } ?: return null
+
+ if (immutable && !variable.hasModifier(JvmModifier.FINAL)) {
+ PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, true)
+ }
+
+ if (!immutable && variable.hasModifier(JvmModifier.FINAL)) {
+ PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, false)
+ }
+
+ return JavaULocalVariable(variable, null)
+ }
+
+ private fun createLocalVariableWithSuggestedName(type: PsiType?, initializer: UExpression, immutable: Boolean): ULocalVariable? {
+ val initializerPsi = initializer.sourcePsi as? PsiExpression ?: return null
+ val codeStyleManager = JavaCodeStyleManager.getInstance(initializerPsi.project)
+ val resultType = type ?: initializerPsi.type ?: return null
+ var suggestedNameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, initializerPsi, resultType)
+ suggestedNameInfo = codeStyleManager.suggestUniqueVariableName(suggestedNameInfo, initializerPsi, true)
+ val name = suggestedNameInfo.names.firstOrNull() ?: return null
+ return createLocalVariable(name, type, initializer, immutable)
+ }
+
+ override fun createBlockExpression(expressions: List, project: Project): UBlockExpression? {
+ val factory = JavaPsiFacade.getElementFactory(project)
+ val blockStatement = BlockUtils.createBlockStatement(project)
+
+ for (expression in expressions) {
+ if (expression is JavaUDeclarationsExpression) {
+ for (declaration in expression.declarations) {
+ if (declaration.sourcePsi is PsiLocalVariable) {
+ blockStatement.codeBlock.add(declaration.sourcePsi?.parent as? PsiDeclarationStatement ?: return null)
+ }
+ }
+ continue
+ }
+
+ expression.sourcePsi?.let { psi ->
+ psi as? PsiStatement ?: (psi as? PsiExpression)?.let { factory.createExpresionStatement(it) }
+ }?.let { blockStatement.codeBlock.add(it) } ?: return null
+ }
+
+ return JavaUBlockExpression(blockStatement, null)
+ }
+
+ override fun createLambdaExpression(parameters: List, body: UExpression): ULambdaExpression? {
+ val factory = JavaPsiFacade.getElementFactory(body.sourcePsi?.project ?: return null)
+ val lambda = factory.createExpressionFromText(
+ buildString {
+ parameters.joinTo(this, separator = ",", prefix = "(", postfix = ")") { "x" }
+
+ append("->")
+ append("{}")
+ },
+ null
+ ) as? PsiLambdaExpression ?: return null
+
+ lambda.parameterList.parameters.forEachIndexed { i, p ->
+ p.replace(parameters[i].sourcePsi ?: return null)
+ }
+
+ if (lambda.parameterList.parameters.any { it.typeElement == null })
+ lambda.parameterList.parameters.forEach {
+ it.typeElement?.delete()
+ if (it.children.getOrNull(1) !is PsiIdentifier)
+ it.children.getOrNull(1)?.delete()
+ }
+
+ if (lambda.parameterList.parametersCount == 1 && lambda.parameterList.parameters[0].typeElement == null) {
+ lambda.parameterList.firstChild.delete()
+ lambda.parameterList.lastChild.delete()
+ }
+
+ val normalizedBody = when (val bodyPsi = body.sourcePsi) {
+ is PsiExpression -> bodyPsi
+ is PsiCodeBlock -> normalizeBlockForLambda(bodyPsi)
+ is PsiBlockStatement -> normalizeBlockForLambda(bodyPsi.codeBlock)
+ else -> return null
+ }
+
+ lambda.body?.replace(normalizedBody) ?: return null
+
+ return JavaULambdaExpression(lambda, null)
+ }
+
+ private fun normalizeBlockForLambda(block: PsiCodeBlock): PsiElement = block
+ .takeIf { block.statementCount == 1 }
+ ?.let { block.statements[0] as? PsiReturnStatement }
+ ?.let { it.returnValue } ?: block
+
+ override fun createParenthesizedExpression(expression: UExpression): UParenthesizedExpression? {
+ val factory = JavaPsiFacade.getElementFactory(expression.sourcePsi?.project ?: return null)
+ val parenthesizedExpression = factory.createExpressionFromText("()", null) as? PsiParenthesizedExpression ?: return null
+ parenthesizedExpression.children.getOrNull(1)?.replace(expression.sourcePsi ?: return null) ?: return null
+ return JavaUParenthesizedExpression(parenthesizedExpression, null)
+ }
+
+ override fun createSimpleReference(name: String, project: Project): USimpleNameReferenceExpression? {
+ val factory = JavaPsiFacade.getElementFactory(project)
+ val reference = factory.createExpressionFromText(name, null)
+ return JavaUSimpleNameReferenceExpression(reference, name, null)
+ }
+
+ override fun createSimpleReference(variable: UVariable): USimpleNameReferenceExpression? {
+ return createSimpleReference(variable.name ?: return null, variable.sourcePsi?.project ?: return null)
+ }
+
+ override val language: Language
+ get() = JavaLanguage.INSTANCE
+
+
+ override fun createBinaryExpression(leftOperand: UExpression,
+ rightOperand: UExpression,
+ operator: UastBinaryOperator): UBinaryExpression? {
+ val project = leftOperand.sourcePsi?.project ?: return null
+ val leftPsi = leftOperand.sourcePsi ?: return null
+ val rightPsi = rightOperand.sourcePsi ?: return null
+ val factory = JavaPsiFacade.getElementFactory(project)
+
+ val operatorSymbol = when (operator) {
+ UastBinaryOperator.LOGICAL_AND -> "&&"
+ else -> return null
+ }
+ val psiBinaryExpression = factory.createExpressionFromText("a $operatorSymbol b", null) as? PsiBinaryExpression
+ ?: return null
+ psiBinaryExpression.lOperand.replace(leftPsi)
+ psiBinaryExpression.rOperand?.replace(rightPsi)
+
+ return JavaUBinaryExpression(psiBinaryExpression, null)
+ }
+
+ override fun createFlatBinaryExpression(leftOperand: UExpression,
+ rightOperand: UExpression,
+ operator: UastBinaryOperator): UPolyadicExpression? {
+ val binaryExpression = createBinaryExpression(leftOperand, rightOperand, operator) ?: return null
+ val binarySourcePsi = binaryExpression.sourcePsi as? PsiBinaryExpression ?: return null
+ val factory = JavaPsiFacade.getElementFactory(binarySourcePsi.project)
+ val dummyParent = factory.createStatementFromText("a;", null)
+ dummyParent.firstChild.replace(binarySourcePsi)
+ ParenthesesUtils.removeParentheses(dummyParent.firstChild as PsiExpression, false)
+ return when(val result = dummyParent.firstChild) {
+ is PsiBinaryExpression -> JavaUBinaryExpression(result, null)
+ is PsiPolyadicExpression -> JavaUPolyadicExpression(result, null)
+ else -> throw AssertionError("Unexpected type " + result.javaClass)
+ }
+ }
+}
\ No newline at end of file
diff --git a/uast/uast-tests/test/org/jetbrains/uast/test/java/AbstractJavaUastLightTest.kt b/uast/uast-tests/test/org/jetbrains/uast/test/java/AbstractJavaUastLightTest.kt
new file mode 100644
index 000000000000..20630214e32f
--- /dev/null
+++ b/uast/uast-tests/test/org/jetbrains/uast/test/java/AbstractJavaUastLightTest.kt
@@ -0,0 +1,11 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.uast.test.java
+
+import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
+import junit.framework.TestCase
+
+abstract class AbstractJavaUastLightTest : LightJavaCodeInsightFixtureTestCase() {
+ protected fun fail(message: String): Nothing {
+ throw AssertionError(message)
+ }
+}
\ No newline at end of file
diff --git a/uast/uast-tests/test/org/jetbrains/uast/test/java/generate/JavaUastGenerationTest.kt b/uast/uast-tests/test/org/jetbrains/uast/test/java/generate/JavaUastGenerationTest.kt
new file mode 100644
index 000000000000..d5ef8a79ae8f
--- /dev/null
+++ b/uast/uast-tests/test/org/jetbrains/uast/test/java/generate/JavaUastGenerationTest.kt
@@ -0,0 +1,180 @@
+// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.uast.test.java.generate
+
+import com.intellij.lang.java.JavaLanguage
+import com.intellij.psi.*
+import junit.framework.TestCase
+import org.jetbrains.uast.*
+import org.jetbrains.uast.generate.UastCodeGenerationPlugin
+import org.jetbrains.uast.generate.refreshed
+import org.jetbrains.uast.generate.replace
+import org.jetbrains.uast.test.java.AbstractJavaUastLightTest
+
+class JavaUastGenerationTest : AbstractJavaUastLightTest() {
+
+ private val psiFactory: PsiElementFactory
+ get() = JavaPsiFacade.getElementFactory(myFixture.project)
+ private val generatePlugin: UastCodeGenerationPlugin
+ get() = UastCodeGenerationPlugin.byLanguage(JavaLanguage.INSTANCE)!!
+
+ fun `test logical and operation with simple operands`() {
+ val left = psiFactory.createExpressionFromText("true", null).toUElementOfType()
+ ?: fail("Cannot create left UExpression")
+ val right = psiFactory.createExpressionFromText("false", null).toUElementOfType()
+ ?: fail("Cannot create right UExpression")
+
+ val expression = generatePlugin.createBinaryExpression(left, right, UastBinaryOperator.LOGICAL_AND)
+ ?: fail("Cannot create expression")
+
+ TestCase.assertEquals("true && false", expression.sourcePsi?.text)
+ }
+
+ fun `test logical and operation with simple operands with parenthesis`() {
+ val left = psiFactory.createExpressionFromText("(true)", null).toUElementOfType()
+ ?: fail("Cannot create left UExpression")
+ val right = psiFactory.createExpressionFromText("(false)", null).toUElementOfType()
+ ?: fail("Cannot create right UExpression")
+
+ val expression = generatePlugin.createFlatBinaryExpression(left, right, UastBinaryOperator.LOGICAL_AND)
+ ?: fail("Cannot create expression")
+
+ TestCase.assertTrue(expression.sourcePsi is PsiBinaryExpression)
+ TestCase.assertEquals("true && false", expression.sourcePsi?.text)
+ }
+
+ fun `test logical and operation with simple operands with parenthesis polyadic`() {
+ val left = psiFactory.createExpressionFromText("(true && false)", null).toUElementOfType()
+ ?: fail("Cannot create left UExpression")
+ val right = psiFactory.createExpressionFromText("(false)", null).toUElementOfType()
+ ?: fail("Cannot create right UExpression")
+
+ val expression = generatePlugin.createFlatBinaryExpression(left, right, UastBinaryOperator.LOGICAL_AND)
+ ?: fail("Cannot create expression")
+
+ TestCase.assertTrue(expression.sourcePsi is PsiPolyadicExpression)
+ TestCase.assertEquals("true && false && false", expression.sourcePsi?.text)
+ }
+
+ fun `test simple reference creating from variable`() {
+ val variable = psiFactory.createVariableDeclarationStatement(
+ "a",
+ PsiType.INT
+ , null
+ ).declaredElements.getOrNull(0)?.toUElementOfType() ?: fail("cannot create variable")
+
+ val reference = generatePlugin.createSimpleReference(variable) ?: fail("cannot create reference")
+ TestCase.assertEquals("a", reference.identifier)
+ }
+
+ fun `test simple reference by name`() {
+ val reference = generatePlugin.createSimpleReference("a", myFixture.project) ?: fail("cannot create reference")
+ TestCase.assertEquals("a", reference.identifier)
+ }
+
+ fun `test parenthesised expression`() {
+ val expression = psiFactory.createExpressionFromText("a + b", null).toUElementOfType()
+ ?: fail("cannot create expression")
+ val parenthesizedExpression = generatePlugin.createParenthesizedExpression(expression)
+ ?: fail("cannot create parenthesized expression")
+
+ TestCase.assertEquals("(a + b)", parenthesizedExpression.sourcePsi?.text)
+ }
+
+ fun `test return expression`() {
+ val expression = psiFactory.createExpressionFromText("a + b", null).toUElementOfType()
+ ?: fail("Cannot find plugin")
+
+ val returnExpression = generatePlugin.createReturnExpresion(expression) ?: fail("cannot create return expression")
+
+ TestCase.assertEquals("return a + b;", returnExpression.sourcePsi?.text)
+ }
+
+ fun `test variable declaration without type`() {
+ val expression = psiFactory.createExpressionFromText("1 + 2", null).toUElementOfType()
+ ?: fail("cannot create variable declaration")
+
+ val declaration = generatePlugin.createLocalVariable("a", null, expression) ?: fail("cannot create variable")
+
+ TestCase.assertEquals("int a = 1 + 2;", declaration.sourcePsi?.text)
+ }
+
+ fun `test variable declaration with type`() {
+ val expression = psiFactory.createExpressionFromText("b", null).toUElementOfType()
+ ?: fail("cannot create variable declaration")
+
+ val declaration = generatePlugin.createLocalVariable("a", PsiType.DOUBLE, expression) ?: fail("cannot create variable")
+
+ TestCase.assertEquals("double a = b;", declaration.sourcePsi?.text)
+ }
+
+ fun `test final variable declaration`() {
+ val expression = psiFactory.createExpressionFromText("b", null).toUElementOfType()
+ ?: fail("cannot create variable declaration")
+
+ val declaration = generatePlugin.createLocalVariable("a", PsiType.DOUBLE, expression, true) ?: fail("cannot create variable")
+
+ TestCase.assertEquals("final double a = b;", declaration.sourcePsi?.text)
+ }
+
+ fun `test block expression`() {
+ val statement1 = psiFactory.createStatementFromText("System.out.println();", null).toUElementOfType()
+ ?: fail("cannot create statement")
+ val statement2 = psiFactory.createStatementFromText("System.out.println(2);", null).toUElementOfType()
+ ?: fail("cannot create statement")
+
+ val block = generatePlugin.createBlockExpression(listOf(statement1, statement2), myFixture.project) ?: fail("cannot create block")
+
+ TestCase.assertEquals("{" +
+ "System.out.println();" +
+ "System.out.println(2);" +
+ "}", block.sourcePsi?.text)
+ }
+
+ fun `test lambda expression`() {
+ val statement = psiFactory.createStatementFromText("System.out.println();", null).toUElementOfType()
+ ?: fail("cannot create statement")
+
+ val parameter1 = psiFactory.createParameter("a", PsiType.INT).toUElementOfType() ?: fail("cannot create parameter")
+
+ val parameter2 = psiFactory.createParameter("b", PsiType.INT).toUElementOfType() ?: fail("cannot create parameter")
+ (parameter2.sourcePsi as PsiParameter).typeElement?.delete()
+ (parameter2.sourcePsi as PsiParameter).children[1].delete()
+
+ val lambda = generatePlugin.createLambdaExpression(listOf(parameter1, parameter2), statement) ?: fail("cannot create lambda")
+
+ TestCase.assertEquals("(a,b)->System.out.println()", lambda.sourcePsi?.text)
+ }
+
+ fun `test lambda expression with simplified block body`() {
+ val block = psiFactory.createStatementFromText("{ return \"10\"; }", null).toUElementOfType()
+ ?: fail("cannot create block")
+
+ val parameter1 = psiFactory.createParameter("a", PsiType.INT).toUElementOfType() ?: fail("cannot create parameter")
+ (parameter1.sourcePsi as PsiParameter).typeElement?.delete()
+ (parameter1.sourcePsi as PsiParameter).children[1].delete()
+ val lambda = generatePlugin.createLambdaExpression(listOf(parameter1), block) ?: fail("cannot create lambda")
+ TestCase.assertEquals("""a->"10"""", lambda.sourcePsi?.text)
+ }
+
+ fun `test function argument replacement`() {
+ val expression = psiFactory.createExpressionFromText("f(a)", null).toUElementOfType()
+ ?: fail("cannot create expression")
+ val newArgument = psiFactory.createExpressionFromText("b", null).toUElementOfType()
+ ?: fail("cannot create reference")
+
+ TestCase.assertNotNull(expression.valueArguments[0].replace(newArgument))
+ val updated = expression.refreshed() ?: fail("cannot update expression")
+ TestCase.assertEquals("f", updated.methodName)
+ TestCase.assertTrue(updated.valueArguments[0] is USimpleNameReferenceExpression)
+ TestCase.assertEquals("b", (updated.valueArguments[0] as USimpleNameReferenceExpression).identifier)
+ }
+
+ fun `test suggested name`() {
+ val expression = psiFactory.createExpressionFromText("f(a) + 1", null).toUElementOfType()
+ ?: fail("cannot create expression")
+ val variable = generatePlugin.createLocalVariable(null, PsiType.INT, expression, true)
+ ?: fail("cannot create variable")
+
+ TestCase.assertEquals("final int i = f(a) + 1;", variable.sourcePsi?.text)
+ }
+}
\ No newline at end of file