blob: 70581983a0b27a293a52efbe89cc5594c5898998 [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.refactoring.convertToJava;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeInspection.noReturnMethod.MissingReturnInspection;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.lang.lexer.TokenSets;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrCondition;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
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.blocks.GrCodeBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrAssertStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrFlowInterruptingStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrThrowStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrForClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrForInClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrTraditionalForClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrApplicationStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
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.api.util.GrStatementOwner;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
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.GroovyCommonClassNames;
import java.util.Collection;
import java.util.Set;
/**
* @author Maxim.Medvedev
*/
public class CodeBlockGenerator extends Generator {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.refactoring.convertToJava.CodeBlockGenerator");
private static final boolean IN_TEST = ApplicationManager.getApplication().isUnitTestMode();
private final StringBuilder builder;
private final ExpressionContext context;
private final Set<GrStatement> myExitPoints;
public CodeBlockGenerator(StringBuilder builder, ExpressionContext context) {
this(builder, context, null);
}
public CodeBlockGenerator(StringBuilder builder, ExpressionContext context, @Nullable Collection<GrStatement> exitPoints) {
this.builder = builder;
this.context = context;
myExitPoints = new HashSet<GrStatement>();
if (exitPoints != null) {
myExitPoints.addAll(exitPoints);
}
}
@Override
public StringBuilder getBuilder() {
return builder;
}
@Override
public ExpressionContext getContext() {
return context;
}
public void generateMethodBody(GrMethod method) {
final GrOpenBlock block = method.getBlock();
boolean shouldInsertReturnNull;
myExitPoints.clear();
PsiType returnType = context.typeProvider.getReturnType(method);
if (!method.isConstructor() && returnType != PsiType.VOID) {
myExitPoints.addAll(ControlFlowUtils.collectReturns(block));
shouldInsertReturnNull = block != null &&
!(returnType instanceof PsiPrimitiveType) &&
MissingReturnInspection.methodMissesSomeReturns(block, MissingReturnInspection.ReturnStatus.getReturnStatus(
method));
}
else {
shouldInsertReturnNull = false;
}
if (block != null) {
generateCodeBlock(block, shouldInsertReturnNull);
}
}
@Override
public void visitMethod(GrMethod method) {
LOG.error("don't invoke it!!!");
}
@Override
public void visitOpenBlock(GrOpenBlock block) {
generateCodeBlock(block, false);
}
public void generateCodeBlock(GrCodeBlock block, boolean shouldInsertReturnNull) {
builder.append("{");
GrParameter[] parameters;
if (block.getParent() instanceof GrMethod) {
GrMethod method = (GrMethod)block.getParent();
parameters = method.getParameters();
}
else if (block instanceof GrClosableBlock) {
parameters = ((GrClosableBlock)block).getAllParameters();
}
else {
parameters = GrParameter.EMPTY_ARRAY;
}
for (GrParameter parameter : parameters) {
if (context.analyzedVars.toWrap(parameter)) {
StringBuilder typeText = new StringBuilder().append(GroovyCommonClassNames.GROOVY_LANG_REFERENCE);
GenerationUtil.writeTypeParameters(typeText, new PsiType[]{context.typeProvider.getParameterType(parameter)}, parameter,
new GeneratorClassNameProvider());
builder.append("final ").append(typeText).append(' ').append(context.analyzedVars.toVarName(parameter))
.append(" = new ").append(typeText).append('(').append(parameter.getName()).append(");\n");
}
}
visitStatementOwner(block, shouldInsertReturnNull);
builder.append("}\n");
}
public void visitStatementOwner(GrStatementOwner owner, boolean shouldInsertReturnNull) {
boolean hasLineFeed = false;
for (PsiElement e = owner.getFirstChild(); e != null; e = e.getNextSibling()) {
if (e instanceof GrStatement) {
((GrStatement)e).accept(this);
hasLineFeed = false;
}
else if (TokenSets.COMMENT_SET.contains(e.getNode().getElementType())) {
builder.append(e.getText());
}
else if (org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil.isLineFeed(e)) {
hasLineFeed = true;
if (IN_TEST) {
builder.append(genSameLineFeed(e.getText()));
}
else {
builder.append(e.getText());
}
}
}
if (shouldInsertReturnNull) {
if (!hasLineFeed) {
builder.append('\n');
}
builder.append("return null;\n");
}
}
private static String genSameLineFeed(String text) {
final int count = StringUtil.countChars(text, '\n');
return StringUtil.repeatSymbol('\n', count);
}
private void writeStatement(@Nullable GrStatement statement, @NotNull StatementWriter writer) {
GenerationUtil.writeStatement(builder, context, statement, writer);
}
private static void writeExpression(@NotNull GrExpression expression, @NotNull StringBuilder builder, @NotNull ExpressionContext context) {
expression.accept(new ExpressionGenerator(builder, context));
}
@Override
public void visitConstructorInvocation(final GrConstructorInvocation invocation) {
writeStatement(invocation, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
final GrReferenceExpression thisOrSuperKeyword = invocation.getInvokedExpression();
final GrArgumentList argumentList = invocation.getArgumentList();
final GroovyResolveResult resolveResult = invocation.advancedResolve();
if (thisOrSuperKeyword.getQualifier() == null) {
builder.append(thisOrSuperKeyword.getReferenceName());
}
else {
writeExpression(thisOrSuperKeyword, builder, context);
}
new ArgumentListGenerator(builder, context).generate(
GrClosureSignatureUtil.createSignature(resolveResult),
argumentList.getExpressionArguments(),
argumentList.getNamedArguments(),
invocation.getClosureArguments(),
invocation
);
builder.append(';');
}
});
}
@Override
public void visitStatement(GrStatement statement) {
LOG.error("all statements must be overloaded");
}
@Override
public void visitFlowInterruptStatement(GrFlowInterruptingStatement statement) {
builder.append(statement.getStatementText());
final String name = statement.getLabelName();
if (name != null) {
builder.append(' ').append(name); //todo check incorrect labels
}
builder.append(';');
}
@Override
public void visitReturnStatement(final GrReturnStatement returnStatement) {
final GrExpression returnValue = returnStatement.getReturnValue();
if (returnValue == null) {
builder.append("return;\n");
return;
}
writeStatement(returnStatement, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
writeReturn(builder, context, returnValue);
}
});
}
@Override
public void visitAssertStatement(final GrAssertStatement assertStatement) {
final GrExpression assertion = assertStatement.getAssertion();
final GrExpression message = assertStatement.getErrorMessage();
if (assertion != null) {
writeStatement(assertStatement, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
builder.append("assert ");
writeExpression(assertion, builder, context);
if (message != null) {
builder.append(" : ");
writeExpression(message, builder, context);
}
builder.append(';');
}
});
}
else if (message != null) {
writeStatement(assertStatement, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
builder.append("assert : ");
writeExpression(message, builder, context);
builder.append(';');
}
});
}
else {
builder.append("assert;");
}
}
@Override
public void visitThrowStatement(GrThrowStatement throwStatement) {
final GrExpression exception = throwStatement.getException();
if (exception == null) {
builder.append("throw ;");
return;
}
writeStatement(throwStatement, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
builder.append("throw ");
writeExpression(exception, builder, context); //todo add exception to method 'throws' list
builder.append(';');
}
});
}
@Override
public void visitLabeledStatement(final GrLabeledStatement labeledStatement) {
writeStatement(labeledStatement, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
final String label = labeledStatement.getName();
final GrStatement statement = labeledStatement.getStatement();
builder.append(label).append(": ");
if (statement != null) {
statement.accept(new CodeBlockGenerator(builder, context, myExitPoints));
}
}
});
}
@Override
public void visitExpression(final GrExpression expression) {
writeStatement(expression, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
if (myExitPoints.contains(expression) && isRealExpression(expression)) {
writeReturn(builder, context, expression);
}
else {
writeExpression(expression, builder, context);
builder.append(';');
}
}
private boolean isRealExpression(GrExpression expression) {
final PsiType type = expression.getType();
if (type == PsiType.VOID) return false; //statement
if (type == PsiType.NULL) return !org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil.isVoidMethodCall(expression);
return true;
}
});
}
private static void writeReturn(StringBuilder builder, ExpressionContext context, final GrExpression expression) {
builder.append("return ");
final PsiType expectedReturnType = PsiImplUtil.inferReturnType(expression);
final PsiType nnReturnType = expectedReturnType == null || expectedReturnType == PsiType.VOID ? TypesUtil.getJavaLangObject(expression) : expectedReturnType;
GenerationUtil.wrapInCastIfNeeded(builder, nnReturnType, expression.getNominalType(), expression, context, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
writeExpression(expression, builder, context);
}
});
builder.append(';');
}
@Override
public void visitApplicationStatement(GrApplicationStatement applicationStatement) {
visitExpression(applicationStatement);
}
@Override
public void visitTypeDefinition(GrTypeDefinition typeDefinition) {
//todo ???????
}
@Override
public void visitIfStatement(final GrIfStatement ifStatement) {
writeStatement(ifStatement, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
final GrExpression condition = ifStatement.getCondition();
final GrStatement thenBranch = ifStatement.getThenBranch();
final GrStatement elseBranch = ifStatement.getElseBranch();
builder.append("if (");
if (condition != null) {
final PsiType type = condition.getType();
if (TypesUtil.unboxPrimitiveTypeWrapper(type) == PsiType.BOOLEAN) {
writeExpression(condition, builder, context);
}
else {
GenerationUtil.invokeMethodByName(
condition,
"asBoolean",
GrExpression.EMPTY_ARRAY, GrNamedArgument.EMPTY_ARRAY, GrClosableBlock.EMPTY_ARRAY,
new ExpressionGenerator(builder, context), ifStatement);
}
}
builder.append(')');
if (thenBranch != null) thenBranch.accept(new CodeBlockGenerator(builder, context.extend(), myExitPoints));
if (ifStatement.getElseKeyword() != null) builder.append(" else ");
if (elseBranch != null) elseBranch.accept(new CodeBlockGenerator(builder, context.extend(), myExitPoints));
}
});
}
@Override
public void visitForStatement(GrForStatement forStatement) {
//final StringBuilder builder = new StringBuilder();
builder.append("for(");
final GrForClause clause = forStatement.getClause();
ExpressionContext forContext = context.extend();
if (clause instanceof GrForInClause) {
final GrExpression expression = ((GrForInClause)clause).getIteratedExpression();
final GrVariable declaredVariable = clause.getDeclaredVariable();
LOG.assertTrue(declaredVariable != null);
writeVariableWithoutSemicolonAndInitializer(builder, declaredVariable, context);
builder.append(" : ");
if (expression != null) {
final ExpressionContext context = forContext.copy();
writeExpression(expression, builder, context);
}
}
else if (clause instanceof GrTraditionalForClause) {
final GrTraditionalForClause cl = (GrTraditionalForClause)clause;
final GrCondition initialization = cl.getInitialization();
final GrExpression condition = cl.getCondition();
final GrExpression update = cl.getUpdate();
if (initialization instanceof GrParameter) {
StringBuilder partBuilder = new StringBuilder();
writeVariableWithoutSemicolonAndInitializer(partBuilder, (GrParameter)initialization, context);
final GrExpression initializer = ((GrParameter)initialization).getInitializerGroovy();
if (initializer != null) {
final ExpressionContext partContext = forContext.copy();
partBuilder.append(" = ");
writeExpression(initializer, partBuilder, partContext);
for (String statement : partContext.myStatements) {
builder.append(statement).append(", ");
}
builder.append(partBuilder);
}
}
else if (initialization != null) {
StringBuilder partBuilder = new StringBuilder();
final ExpressionContext partContext = forContext.copy();
genForPart(builder, initialization, new CodeBlockGenerator(partBuilder, partContext, null));
}
builder.append(';');
if (condition != null) {
genForPart(builder, condition, forContext.copy()); //todo???
}
builder.append(';');
if (update != null) {
genForPart(builder, update, forContext.copy());
}
}
builder.append(')');
final GrStatement body = forStatement.getBody();
if (body != null) {
body.accept(new CodeBlockGenerator(builder, forContext, null));
}
}
private static void genForPart(StringBuilder builder, GrExpression part, final ExpressionContext context) {
genForPart(builder, part, new ExpressionGenerator(new StringBuilder(), context));
}
private static void genForPart(StringBuilder builder, GroovyPsiElement part, final Generator visitor) {
part.accept(visitor);
for (String statement : visitor.getContext().myStatements) {
builder.append(statement).append(", ");
}
builder.append(visitor.getBuilder());
}
private static void writeVariableWithoutSemicolonAndInitializer(StringBuilder builder, GrVariable var, ExpressionContext context) {
ModifierListGenerator.writeModifiers(builder, var.getModifierList());
TypeWriter.writeType(builder, context.typeProvider.getVarType(var), var);
builder.append(' ').append(var.getName());
}
@Override
public void visitWhileStatement(final GrWhileStatement whileStatement) {
writeStatement(whileStatement, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
final GrExpression condition = whileStatement.getCondition();
final GrStatement body = whileStatement.getBody();
builder.append("while (");
if (condition != null) {
writeExpression(condition, builder, context);
}
builder.append(" )");
if (body != null) {
body.accept(new CodeBlockGenerator(builder, context.extend(), null));
}
}
});
}
@Override
public void visitSwitchStatement(final GrSwitchStatement switchStatement) {
writeStatement(switchStatement, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
SwitchStatementGenerator.generate(builder, context, switchStatement);
}
});
}
@Override
public void visitTryStatement(GrTryCatchStatement tryCatchStatement) {
final GrOpenBlock tryBlock = tryCatchStatement.getTryBlock();
final GrCatchClause[] catchClauses = tryCatchStatement.getCatchClauses();
final GrFinallyClause finallyClause = tryCatchStatement.getFinallyClause();
builder.append("try");
tryBlock.accept(this);
for (GrCatchClause catchClause : catchClauses) {
catchClause.accept(this);
}
if (finallyClause != null) {
finallyClause.accept(this);
}
}
@Override
public void visitCatchClause(GrCatchClause catchClause) {
final GrParameter parameter = catchClause.getParameter();
builder.append("catch (");
writeVariableWithoutSemicolonAndInitializer(builder, parameter, context);
builder.append(") ");
final GrOpenBlock body = catchClause.getBody();
if (body != null) {
body.accept(this);
}
}
@Override
public void visitFinallyClause(GrFinallyClause finallyClause) {
builder.append("finally ");
final GrOpenBlock body = finallyClause.getBody();
if (body != null) {
body.accept(this);
}
}
@Override
public void visitBlockStatement(GrBlockStatement blockStatement) {
blockStatement.getBlock().accept(this);
}
@Override
public void visitSynchronizedStatement(final GrSynchronizedStatement statement) {
writeStatement(statement, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
final GrExpression monitor = statement.getMonitor();
final GrOpenBlock body = statement.getBody();
builder.append("synchronized(");
if (monitor != null ) {
writeExpression(monitor, builder, context);
}
builder.append(')');
if (body != null) {
body.accept(new CodeBlockGenerator(builder, context.extend(), myExitPoints));
}
}
});
}
@Override
public void visitVariableDeclaration(final GrVariableDeclaration variableDeclaration) {
writeStatement(variableDeclaration, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
if (variableDeclaration.isTuple()) {
writeTupleDeclaration(variableDeclaration, builder, context);
}
else {
GenerationUtil.writeSimpleVarDeclaration(variableDeclaration, builder, context);
}
}
});
}
private void writeTupleDeclaration(GrVariableDeclaration variableDeclaration,
StringBuilder builder,
ExpressionContext expressionContext) {
GrVariable[] variables = variableDeclaration.getVariables();
final GrExpression tupleInitializer = variableDeclaration.getTupleInitializer();
if (tupleInitializer instanceof GrListOrMap) {
for (GrVariable variable : variables) {
GenerationUtil.writeVariableSeparately(variable, builder, expressionContext);
builder.append(";\n");
}
}
else if (tupleInitializer != null) {
GroovyResolveResult iteratorMethodResult =
GenerationUtil
.resolveMethod(tupleInitializer, "iterator", GrExpression.EMPTY_ARRAY, GrNamedArgument.EMPTY_ARRAY, GrClosableBlock.EMPTY_ARRAY,
variableDeclaration);
final PsiType iteratorType = inferIteratorType(iteratorMethodResult, tupleInitializer);
final String iteratorName = genIteratorVar(variableDeclaration, builder, expressionContext, tupleInitializer, iteratorType,
iteratorMethodResult);
final GrModifierList modifierList = variableDeclaration.getModifierList();
PsiType iterableTypeParameter = PsiUtil.extractIterableTypeParameter(iteratorType, false);
for (final GrVariable v : variables) {
ModifierListGenerator.writeModifiers(builder, modifierList);
final PsiType type = context.typeProvider.getVarType(v);
TypeWriter.writeType(builder, type, variableDeclaration);
builder.append(' ').append(v.getName());
builder.append(" = ");
GenerationUtil.wrapInCastIfNeeded(builder, type, iterableTypeParameter, tupleInitializer, expressionContext, new StatementWriter() {
@Override
public void writeStatement(StringBuilder builder, ExpressionContext context) {
builder.append(iteratorName).append(".hasNext() ? ").append(iteratorName).append(".next() : null");
}
});
builder.append(";\n");
}
}
else {
GenerationUtil.writeSimpleVarDeclaration(variableDeclaration, builder, expressionContext);
}
}
private static String genIteratorVar(GrVariableDeclaration variableDeclaration,
StringBuilder builder,
ExpressionContext expressionContext,
@NotNull GrExpression tupleInitializer,
PsiType iteratorType,
GroovyResolveResult iteratorMethodResult) {
final String iteratorName = GenerationUtil.suggestVarName(iteratorType, variableDeclaration, expressionContext);
builder.append("final ");
TypeWriter.writeType(builder, iteratorType, variableDeclaration);
builder.append(' ').append(iteratorName).append(" = ");
GenerationUtil
.invokeMethodByResolveResult(tupleInitializer, iteratorMethodResult, "iterator", GrExpression.EMPTY_ARRAY, GrNamedArgument.EMPTY_ARRAY,
GrClosableBlock.EMPTY_ARRAY, new ExpressionGenerator(builder, expressionContext), variableDeclaration);
builder.append(";\n");
return iteratorName;
}
private PsiType inferIteratorType(GroovyResolveResult iteratorMethodResult, GrExpression tupleInitializer) {
PsiElement method = iteratorMethodResult.getElement();
if (method instanceof PsiMethod) {
return iteratorMethodResult.getSubstitutor().substitute(((PsiMethod)method).getReturnType());
}
else {
PsiType initializerType = tupleInitializer.getType();
PsiType iterableParam = PsiUtil.extractIterableTypeParameter(initializerType, false);
JavaPsiFacade facade = JavaPsiFacade.getInstance(context.project);
PsiClass iterableClass = facade.findClass(CommonClassNames.JAVA_UTIL_ITERATOR, tupleInitializer.getResolveScope());
if (iterableClass != null && iterableParam != null) {
return facade.getElementFactory().createType(iterableClass, iterableParam);
}
else {
return facade.getElementFactory().createTypeFromText(CommonClassNames.JAVA_UTIL_ITERATOR, tupleInitializer);
}
}
}
@Override
public void visitVariable(GrVariable variable) {
super.visitVariable(variable); //To change body of overridden methods use File | Settings | File Templates.
}
}