/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.google.dart.java2dart.processor;
import com.google.common.collect.Lists;
import com.google.dart.engine.ast.Block;
import com.google.dart.engine.ast.BlockFunctionBody;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.ConstructorInitializer;
import com.google.dart.engine.ast.EmptyFunctionBody;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.ExpressionStatement;
import com.google.dart.engine.ast.FormalParameter;
import com.google.dart.engine.ast.FunctionBody;
import com.google.dart.engine.ast.MethodInvocation;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.RedirectingConstructorInvocation;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.SuperConstructorInvocation;
import com.google.dart.engine.ast.visitor.GeneralizingAstVisitor;
import com.google.dart.java2dart.Context;
import com.google.dart.java2dart.Context.ConstructorDescription;
import static com.google.dart.java2dart.util.AstFactory.emptyFunctionBody;
import static com.google.dart.java2dart.util.AstFactory.identifier;
import static com.google.dart.java2dart.util.AstFactory.redirectingConstructorInvocation;
import static com.google.dart.java2dart.util.AstFactory.simpleFormalParameter;
import static com.google.dart.java2dart.util.AstFactory.typeName;
import org.eclipse.jdt.core.dom.IMethodBinding;
import java.util.Iterator;
import java.util.List;
/**
* Simplifies generated Dart constructors.
* <ul>
* <li>If exactly one constructor that is default and just calls super, then remove it.</li>
* <li>If constructor has block body with no statements, then removes the body.</li>
* <li>If constructor has redirection marker invocation and no other statements, then replace the
* marker with the actual redirecting constructor invocation and removes the body.</li>
* <li>If constructor is an enum constructor with redirecting constructor invocation, then make its
* "name" and "ordinal" field formal initializing parameters the normal formal parameters and pass
* them into the redirecting constructor invocation.</li>
* </ul>
*/
public class ConstructorSemanticProcessor extends SemanticProcessor {
/**
* @return {@code true} if the given {@link ConstructorDeclaration} has no or empty body.
*/
private static boolean hasEmptyBody(ConstructorDeclaration constructor) {
FunctionBody body = constructor.getBody();
// no body at all
if (body == null) {
return true;
}
if (body instanceof EmptyFunctionBody) {
return true;
}
// block body without statements
if (body instanceof BlockFunctionBody) {
Block block = ((BlockFunctionBody) body).getBlock();
return block.getStatements().isEmpty();
}
// expression body (probably never happens)
return false;
}
/**
* @return {@code true} if the constructor is default, has empty body, no initializers or one
* initializer calling default super constructor.
*/
private static boolean hasNoParamAndOnlyCallsSuper(ConstructorDeclaration constructor) {
// no parameters
if (!constructor.getParameters().getParameters().isEmpty()) {
return false;
}
// empty body
if (!hasEmptyBody(constructor)) {
return false;
}
// at most one initializer allowed
NodeList<ConstructorInitializer> initializers = constructor.getInitializers();
if (initializers.size() == 0) {
return true;
}
if (initializers.size() > 1) {
return false;
}
// check that the only initializer is "super" constructor invocation
ConstructorInitializer initializer = initializers.get(0);
if (!(initializer instanceof SuperConstructorInvocation)) {
return false;
}
SuperConstructorInvocation superInitializer = (SuperConstructorInvocation) initializer;
if (!superInitializer.getArgumentList().getArguments().isEmpty()) {
return false;
}
// OK, there is only default "super" constructor invocation
return true;
}
public ConstructorSemanticProcessor(Context context) {
super(context);
}
@Override
public void process(CompilationUnit unit) {
unit.accept(new GeneralizingAstVisitor<Void>() {
List<ConstructorDeclaration> allConstructors = Lists.newArrayList();;
@Override
public Void visitClassDeclaration(ClassDeclaration node) {
allConstructors.clear();
super.visitClassDeclaration(node);
// remove constructor if it is default and calls default super constructor
if (allConstructors.size() == 1) {
ConstructorDeclaration constructor = allConstructors.get(0);
if (hasNoParamAndOnlyCallsSuper(constructor)) {
node.getMembers().remove(allConstructors.remove(0));
}
}
// done
return null;
}
@Override
public Void visitConstructorDeclaration(ConstructorDeclaration node) {
allConstructors.add(node);
replaceThisInvocationMarkerWithRedirection(node);
// remove empty body
if (hasEmptyBody(node)) {
node.setBody(emptyFunctionBody());
}
// done
return null;
}
});
}
/**
* If the given {@link ConstructorDeclaration} has only statement with marker for "this"
* constructor redirection, then replace it with the real redirection. This redirection will still
* have temporary name, will be replace with the real name during constructors rename step.
*/
private void replaceThisInvocationMarkerWithRedirection(ConstructorDeclaration node) {
// prepare statements
FunctionBody body = node.getBody();
if (!(body instanceof BlockFunctionBody)) {
return;
}
Block block = ((BlockFunctionBody) body).getBlock();
NodeList<Statement> statements = block.getStatements();
// we support here only one statement
if (statements.size() != 1) {
return;
}
Statement statement = statements.get(0);
// the statement should be "thisConstructorRedirection" marker invocation
if (!(statement instanceof ExpressionStatement)) {
return;
}
Expression expression = ((ExpressionStatement) statement).getExpression();
if (!(expression instanceof MethodInvocation)) {
return;
}
MethodInvocation methodInvocation = (MethodInvocation) expression;
if (methodInvocation.getTarget() != null
|| !methodInvocation.getMethodName().getName().equals("thisConstructorRedirection")) {
return;
}
// add redirecting constructor invocation
List<ConstructorInitializer> initializers = node.getInitializers();
RedirectingConstructorInvocation redirect = redirectingConstructorInvocation(
"thisConstructorRedirection",
methodInvocation.getArgumentList().getArguments());
initializers.add(redirect);
// remove speculative "super" constructor invocation
for (Iterator<ConstructorInitializer> iter = initializers.iterator(); iter.hasNext();) {
ConstructorInitializer initializer = iter.next();
if (initializer instanceof SuperConstructorInvocation) {
iter.remove();
}
}
// remove body
node.setBody(emptyFunctionBody());
// record constructor invocation
IMethodBinding binding = (IMethodBinding) context.getNodeBinding(methodInvocation);
ConstructorDescription description = context.getConstructorDescription(binding);
description.redirectingInvocations.add(redirect);
// tweak enum constructor
if (description.isEnum) {
List<Expression> arguments = redirect.getArgumentList().getArguments();
updateRedirectingEnumConstructorParameters(node, arguments);
}
}
/**
* When we translate Java enum constructor, we generate field formal parameters "name" and
* "ordinal". However if constructor is actually redirecting constructor, there parameters should
* not be field parameters, they should be normal ones and passed into
* {@link RedirectingConstructorInvocation}.
*/
private void updateRedirectingEnumConstructorParameters(ConstructorDeclaration node,
List<Expression> arguments) {
NodeList<FormalParameter> parameters = node.getParameters().getParameters();
parameters.set(0, simpleFormalParameter(typeName("String"), identifier("name")));
parameters.set(1, simpleFormalParameter(typeName("int"), identifier("ordinal")));
arguments.add(0, identifier("name"));
arguments.add(1, identifier("ordinal"));
}
}