blob: e4b0dd014d1d9beb2cf4d371d3296dd807cf9166 [file] [log] [blame]
/*
* 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;
}
}