/* * 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.Set; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import com.motorola.studio.android.codeutils.i18n.CodeUtilsNLS; import com.motorola.studio.android.generatecode.BasicCodeVisitor; import com.motorola.studio.android.generateviewbylayout.model.CodeGeneratorDataBasedOnLayout; import com.motorola.studio.android.generateviewbylayout.model.LayoutNode; /** * Visitor for class method declarations to find onCreate methods inside activity / fragment. * It calls BodyVisitor to continue extracting information about the Android code. */ public class GenerateCodeBasedOnLayoutVisitor extends BasicCodeVisitor { /* * Constants */ private static final String ACTIVITY_ON_CREATE_DECLARATION = "void onCreate(android.os.Bundle)"; //$NON-NLS-1$ private static final String FRAGMENT_ON_CREATE_DECLARATION = "android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)"; //$NON-NLS-1$ private static final String ACTIVITY_ON_CREATE = "onCreate"; //$NON-NLS-1$ private static final String FRAGMENT_ON_CREATE = "onCreateView"; //$NON-NLS-1$ private static final String ACTIVITY_ON_PAUSE_DECLARATION = "void onPause()"; private static final String ACTIVITY_ON_RESUME_DECLARATION = "void onResume()"; private static final String ACTIVITY_ON_PAUSE = "onPause"; private static final String ACTIVITY_ON_RESUME = "onResume"; private MethodDeclaration onCreateDeclaration; private CodeGeneratorDataBasedOnLayout.TYPE typeAssociatedToLayout; /** * If type is fragment, there may be an inflated view name. * This will be used to call findViewById inside fragments */ private String inflatedViewName; private final Set<String> declaredViewIds = new HashSet<String>(); private final Set<String> savedViewIds = new HashSet<String>(); private final Set<String> restoredViewIds = new HashSet<String>(); private String layoutName; /** * @return method declaration reference if there an onCreate method declared, null if not found */ public MethodDeclaration getOnCreateDeclaration() { return onCreateDeclaration; } public void setOnCreateDeclaration(MethodDeclaration onCreateDeclaration) { this.onCreateDeclaration = onCreateDeclaration; } /** * @return name of the layout being visited */ public String getLayoutName() { return layoutName; } public void setLayoutName(String layoutName) { this.layoutName = layoutName; } /** * Visit method declaration, searching for instructions * onCreate for activity or fragment */ @Override public boolean visit(MethodDeclaration node) { //Fill Method information SimpleName name = node.getName(); if (name.getIdentifier().equals(ACTIVITY_ON_CREATE) || name.getIdentifier().equals(FRAGMENT_ON_CREATE)) { IMethodBinding binding = node.resolveBinding(); if (binding != null) { if (binding.toString().trim().contains(ACTIVITY_ON_CREATE_DECLARATION)) { visitMethodBodyToIdentifyLayout(node); } else if (binding.toString().trim().contains(FRAGMENT_ON_CREATE_DECLARATION)) { if (node.getBody().statements().size() <= 1) { throw new IllegalArgumentException( CodeUtilsNLS.MethodVisitor_InvalidFormatForFragmentOnCreateView); } else { visitMethodBodyToIdentifyLayout(node); } } else { //for each method visit to identify views already declared visitToIdentifyViewsAlreadyDeclared(node); } } } else if (name.getIdentifier().equals(ACTIVITY_ON_PAUSE) || name.getIdentifier().equals(ACTIVITY_ON_RESUME)) { IMethodBinding binding = node.resolveBinding(); if (binding != null) { //find declared save state if (binding.toString().trim().contains(ACTIVITY_ON_PAUSE_DECLARATION)) { findSavedViews(node); } //find declared restore state else if (binding.toString().trim().contains(ACTIVITY_ON_RESUME_DECLARATION)) { findRestoredViews(node); } } } else { //for each method visit to identify views already declared visitToIdentifyViewsAlreadyDeclared(node); } return super.visit(node); } private void findRestoredViews(MethodDeclaration node) { SaveStateVisitor visitor = new SaveStateVisitor(); node.accept(visitor); restoredViewIds.addAll(visitor.getViewIds()); } private void findSavedViews(MethodDeclaration node) { SaveStateVisitor visitor = new SaveStateVisitor(); node.accept(visitor); savedViewIds.addAll(visitor.getViewIds()); } /** * @param node */ protected synchronized void visitToIdentifyViewsAlreadyDeclared(MethodDeclaration node) { Block body = node.getBody(); if (body != null) { MethodBodyVisitor visitor = new MethodBodyVisitor(); visitAndUpdateDeclaredViewsBasedOnFindViewById(body, visitor); } } /** * Visit method body from onCreate declaration to identify layout used * (it also verifies views already declared) * @param node * @throws JavaModelException */ protected void visitMethodBodyToIdentifyLayout(MethodDeclaration node) { //Navigate through statements... setOnCreateDeclaration(node); Block body = node.getBody(); if (body != null) { identifyLayout(body); } } /** * Navigates in a Block and extract layout name, if class associated to layout is activity or fragment, * and, in case of fragment only, the name of the view inflated. */ private void identifyLayout(Block body) { MethodBodyVisitor visitor = new MethodBodyVisitor(); visitAndUpdateDeclaredViewsBasedOnFindViewById(body, visitor); setLayoutName(visitor.getLayoutName()); typeAssociatedToLayout = visitor.getTypeAssociatedToLayout(); setInflatedViewName(visitor.getInflatedViewName()); } /** * Visit method body to identify view ids already declared * @param body * @param visitor */ public void visitAndUpdateDeclaredViewsBasedOnFindViewById(Block body, MethodBodyVisitor visitor) { body.accept(visitor); synchronized (declaredViewIds) { declaredViewIds.addAll(visitor.getDeclaredViewIds()); } } /** * Check if there is an attribute already declared with the name given. * @param node * @param considerType false, if must not consider the type in the analysis * @return true if there a variable declared with the node.getNodeId() independent on variable type, * false otherwise */ public boolean checkIfAttributeAlreadyDeclared(LayoutNode node, boolean considerType) { boolean containFieldDeclared = false; if (typeDeclaration.bodyDeclarations() != null) { //check if attribute already declared for (Object bd : typeDeclaration.bodyDeclarations()) { if (bd instanceof FieldDeclaration) { FieldDeclaration fd = (FieldDeclaration) bd; if (fd.getParent() instanceof TypeDeclaration) { TypeDeclaration type = (TypeDeclaration) fd.getParent(); if (typeDeclaration.equals(type)) { //only considers attributes from main class inside the file for (Object fragment : fd.fragments()) { if (fragment instanceof VariableDeclarationFragment) { VariableDeclarationFragment frag = (VariableDeclarationFragment) fragment; if ((frag.getName() != null) && frag.getName().toString().equals(node.getNodeId())) { if (considerType) { if ((fd.getType() != null) && !fd.getType().toString() .equals(node.getNodeType())) { containFieldDeclared = true; break; } } else { containFieldDeclared = true; break; } } } } } } } } } return containFieldDeclared; } /** * @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 list of declared layout ids (as specified in layout.xml under android:id attribute) */ public synchronized Set<String> getDeclaredViewIds() { return declaredViewIds; } /** * @return the list of view ids that have code to restore state (using SharedPreferences) */ public Set<String> getRestoredViewIds() { return restoredViewIds; } /** * @return the list of view ids that have code to save state (using SharedPreferences) */ public Set<String> getSavedViewIds() { return savedViewIds; } }