/* * Copyright 2011 Google Inc. All Rights Reserved. * * 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 com.google.devtools.j2objc.translate; import com.google.devtools.j2objc.ast.AbstractTypeDeclaration; import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration; import com.google.devtools.j2objc.ast.Assignment; import com.google.devtools.j2objc.ast.Block; import com.google.devtools.j2objc.ast.BodyDeclaration; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.ConstructorInvocation; import com.google.devtools.j2objc.ast.EnumDeclaration; import com.google.devtools.j2objc.ast.ExpressionStatement; import com.google.devtools.j2objc.ast.FieldDeclaration; import com.google.devtools.j2objc.ast.Initializer; import com.google.devtools.j2objc.ast.MethodDeclaration; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.Statement; import com.google.devtools.j2objc.ast.SuperConstructorInvocation; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.TypeDeclaration; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.ast.VariableDeclarationFragment; import com.google.devtools.j2objc.util.CaptureInfo; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.TypeUtil; import com.google.devtools.j2objc.util.UnicodeUtils; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.lang.model.element.TypeElement; import org.eclipse.jdt.core.dom.Modifier; /** * Modifies initializers to be more iOS like. Static initializers are * combined into a static initialize method, instance initializer * statements are injected into constructors. If a class doesn't have * any constructors but does have instance initialization statements, * a default constructor is added to run them. * * @author Tom Ball */ public class InitializationNormalizer extends UnitTreeVisitor { private final CaptureInfo captureInfo; public InitializationNormalizer(CompilationUnit unit) { super(unit); captureInfo = unit.getEnv().captureInfo(); } @Override public void endVisit(TypeDeclaration node) { new TypeNormalizer(node).normalizeMembers(); } @Override public void endVisit(EnumDeclaration node) { new TypeNormalizer(node).normalizeMembers(); } @Override public void endVisit(AnnotationTypeDeclaration node) { new TypeNormalizer(node).normalizeMembers(); } private class TypeNormalizer { private final AbstractTypeDeclaration node; private final TypeElement type; private final List<Statement> initStatements; private final List<Statement> classInitStatements; private int constInitIdx = 0; private TypeNormalizer(AbstractTypeDeclaration node) { this.node = node; type = node.getTypeElement(); initStatements = new ArrayList<>(); classInitStatements = node.getClassInitStatements(); } private void normalizeMembers() { // Scan class, gathering initialization statements in declaration order. Iterator<BodyDeclaration> iterator = node.getBodyDeclarations().iterator(); while (iterator.hasNext()) { BodyDeclaration member = iterator.next(); switch (member.getKind()) { case INITIALIZER: addInitializer((Initializer) member); iterator.remove(); break; case FIELD_DECLARATION: addFieldInitializer((FieldDeclaration) member); break; default: // Fall-through. } } // Update any primary constructors with init statements. if (type.getKind().isClass()) { for (MethodDeclaration methodDecl : TreeUtil.getMethodDeclarations(node)) { if (isDesignatedConstructor(methodDecl)) { TreeUtil.copyList(initStatements, getInitLocation(methodDecl)); addCaptureAssignments(methodDecl, type); } } } } /** * Add a static or instance init block's statements to the appropriate list * of initialization statements. */ private void addInitializer(Initializer initializer) { List<Statement> list = Modifier.isStatic(initializer.getModifiers()) ? classInitStatements : initStatements; list.add(TreeUtil.remove(initializer.getBody())); } /** * Strip field initializers, convert them to assignment statements, and * add them to the appropriate list of initialization statements. */ private void addFieldInitializer(FieldDeclaration field) { for (VariableDeclarationFragment frag : field.getFragments()) { if (frag.getInitializer() != null) { handleFieldInitializer(frag); } } } /** * Moves the field initializer to the appropriate list of initializer statements. */ private void handleFieldInitializer(VariableDeclarationFragment frag) { if (ElementUtil.isInstanceVar(frag.getVariableElement())) { // always initialize instance variables, since they can't be constants initStatements.add(makeAssignmentStatement(frag)); return; } Object constantValue = frag.getInitializer().getConstantValue(); if (constantValue == null) { // The initializer does not have a constant value. Move it to the class initializer. classInitStatements.add(makeAssignmentStatement(frag)); return; } if (constantValue instanceof String && !UnicodeUtils.hasValidCppCharacters((String) constantValue)) { // String constant that can't be an ObjC literal. Move it to the class initializer but order // constants such as this before other init statements. classInitStatements.add(constInitIdx++, makeAssignmentStatement(frag)); return; } // The initializer has a constant value, convert it to a literal. (as javac would do) frag.setInitializer(TreeUtil.newLiteral(constantValue, typeUtil)); } } private ExpressionStatement makeAssignmentStatement(VariableDeclarationFragment fragment) { return new ExpressionStatement(new Assignment( new SimpleName(fragment.getVariableElement()), TreeUtil.remove(fragment.getInitializer()))); } /** * Finds the location in a constructor where init statements should be added. */ private List<Statement> getInitLocation(MethodDeclaration node) { List<Statement> stmts = node.getBody().getStatements(); if (!stmts.isEmpty() && stmts.get(0) instanceof SuperConstructorInvocation) { return stmts.subList(0, 1); } // java.lang.Object supertype is null. All other types should have a super() call. assert TypeUtil.isNone( ElementUtil.getDeclaringClass(node.getExecutableElement()).getSuperclass()) : "Constructor didn't have a super() call."; return stmts.subList(0, 0); } private void addCaptureAssignments(MethodDeclaration constructor, TypeElement type) { List<Statement> statements = constructor.getBody().getStatements().subList(0, 0); for (CaptureInfo.Capture capture : captureInfo.getCaptures(type)) { if (capture.hasField()) { statements.add(new ExpressionStatement(new Assignment( new SimpleName(capture.getField()), new SimpleName(capture.getParam())))); } } } /** * Returns true if this is a constructor that doesn't call "this(...)". This constructors are * skipped so initializers aren't run more than once per instance creation. */ public static boolean isDesignatedConstructor(MethodDeclaration node) { if (!node.isConstructor()) { return false; } Block body = node.getBody(); if (body == null) { return false; } List<Statement> stmts = body.getStatements(); return (stmts.isEmpty() || !(stmts.get(0) instanceof ConstructorInvocation)); } }