/* * Copyright (C) 2012 The Android Open Source Project * * 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.motorola.studio.android.generateviewbylayout; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import com.motorola.studio.android.generatecode.JDTUtils; import com.motorola.studio.android.generateviewbylayout.model.CodeGeneratorDataBasedOnLayout; /** * Visitor responsible to search method invocations inside a method body * and set the layout name associated with the activity / fragment */ public class MethodBodyVisitor extends ASTVisitor { /* * Constants */ private static final String INFLATE = "inflate"; //$NON-NLS-1$ private static final String VIEW = "View"; //$NON-NLS-1$ private static final String ACTIVITY_METHOD_TO_SET_LAYOUT = "setContentView"; //$NON-NLS-1$ private static final String FRAGMENT_METHOD_TO_SET_LAYOUT = INFLATE; private String layoutName; /** * If type is fragment, there may be an inflated view name. * This will be used to call findViewById inside fragments */ private String inflatedViewName; private CodeGeneratorDataBasedOnLayout.TYPE typeAssociatedToLayout; private Set<String> declaredViewIds = new HashSet<String>(); public String getLayoutName() { return layoutName; } public void setLayoutName(String layoutName) { this.layoutName = layoutName; } /** * Visits statements to find inflate method called on fragments * to get the name of the view declared (this info will be used on findViewById, * for fragments only) */ @Override public boolean visit(VariableDeclarationStatement node) { if (node.getType().toString().equals(VIEW)) { VariableDeclarationFragment frag = (VariableDeclarationFragment) node.fragments().get(0); if (frag.getInitializer() instanceof MethodInvocation) { MethodInvocation invoke = (MethodInvocation) frag.getInitializer(); String invokeName = invoke.getName().toString(); if (invoke.getExpression() instanceof SimpleName) { if ((invokeName != null) && invokeName.equals(INFLATE)) { inflatedViewName = frag.getName().getFullyQualifiedName(); } } } } return super.visit(node); } /** * Visit method invocations to find layout name set on activity/fragment */ @Override public boolean visit(MethodInvocation node) { //Fill invoked method model. MethodInvocation invoked = node; IMethodBinding methodBinding = invoked.resolveMethodBinding(); if (methodBinding != null) { String methodSimpleName = methodBinding.getName(); //Retrieve parameter types and look for R constants used within method arguments List<?> arguments = invoked.arguments(); for (Object argument : arguments) { Expression argumentExpression = (Expression) argument; if (argumentExpression instanceof QualifiedName) /*Can be a constant access*/ { QualifiedName qualifiedName = (QualifiedName) argumentExpression; String layoutName = qualifiedName.getName().getIdentifier(); if (methodSimpleName != null) { if (methodSimpleName.equals(ACTIVITY_METHOD_TO_SET_LAYOUT)) { typeAssociatedToLayout = CodeGeneratorDataBasedOnLayout.TYPE.ACTIVITY; setLayoutName(layoutName); } else if (methodSimpleName.equals(FRAGMENT_METHOD_TO_SET_LAYOUT)) { typeAssociatedToLayout = CodeGeneratorDataBasedOnLayout.TYPE.FRAGMENT; setLayoutName(layoutName); checkInflatedViewNameOnFields(node); } else if (methodSimpleName .equals(JavaViewBasedOnLayoutModifierConstants.FIND_VIEW_BY_ID) || methodSimpleName .equals(JavaViewBasedOnLayoutModifierConstants.FIND_FRAGMENT_BY_ID)) //$NON-NLS-1$ { //findViewById String viewId = qualifiedName.getName().toString(); getDeclaredViewIds().add(viewId); } } } } } return super.visit(node); } private void checkInflatedViewNameOnFields(MethodInvocation node) { //check if this method invocation is binded to an assignment ASTNode nodeParent = node.getParent(); while ((nodeParent != null) && (inflatedViewName == null)) { if (nodeParent instanceof Assignment) { Assignment assignment = (Assignment) nodeParent; Expression lhs = assignment.getLeftHandSide(); ITypeBinding binding = lhs.resolveTypeBinding(); IJavaElement javaElement = binding.getJavaElement(); if ((javaElement != null) && (lhs instanceof SimpleName)) { IType type = (IType) javaElement.getAdapter(IType.class); if (type != null) { try { if (JDTUtils.isSubclass(type, "android.view.View")) { inflatedViewName = ((SimpleName) lhs).getFullyQualifiedName(); } } catch (JavaModelException e) { // do nothing } } } } nodeParent = nodeParent.getParent(); } } /** * @return the typeAssociatedToLayout */ public CodeGeneratorDataBasedOnLayout.TYPE getTypeAssociatedToLayout() { return typeAssociatedToLayout; } /** * @return the inflatedViewName */ public String getInflatedViewName() { return inflatedViewName; } /** * @param inflatedViewName the inflatedViewName to set */ public void setInflatedViewName(String inflatedViewName) { this.inflatedViewName = inflatedViewName; } /** * @return the declaredLayoutIds */ public synchronized Set<String> getDeclaredViewIds() { return declaredViewIds; } }