/*
* 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.codegenerators;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import com.motorola.studio.android.codeutils.i18n.CodeUtilsNLS;
import com.motorola.studio.android.generateviewbylayout.JavaModifierBasedOnLayout;
import com.motorola.studio.android.generateviewbylayout.model.CheckboxNode;
import com.motorola.studio.android.generateviewbylayout.model.CodeGeneratorDataBasedOnLayout;
import com.motorola.studio.android.generateviewbylayout.model.EditTextNode;
import com.motorola.studio.android.generateviewbylayout.model.LayoutNode;
import com.motorola.studio.android.generateviewbylayout.model.LayoutNode.ViewProperties;
import com.motorola.studio.android.generateviewbylayout.model.RadioButtonNode;
import com.motorola.studio.android.generateviewbylayout.model.SeekBarNode;
import com.motorola.studio.android.generateviewbylayout.model.SpinnerNode;
/**
* Generate code to save/restore GUI state (e.g.: EditText, ComboBox, etc)
*/
public class SaveStateCodeGenerator extends AbstractLayoutCodeGenerator
{
public static LayoutNode[] saveStateNodeTypes = new LayoutNode[]
{
new CheckboxNode(), new RadioButtonNode(), new EditTextNode(), new SpinnerNode(),
new SeekBarNode()
};
/*
* Constants (types, methods and variable names)
*/
private static final String COMMIT = "commit";
private static final String ANDROID_CONTENT_IMPORT = "android.content.*";
private static final String MODE_PRIVATE = "MODE_PRIVATE";
private static final String GET_PREFERENCES = "getPreferences";
private static final String PREFERENCES = "preferences";
private static final String EDITOR_CAPITAL_LETTER = "Editor";
private static final String SHARED_PREFERENCES = "SharedPreferences";
private static final String EDITOR = "editor";
private static final String EDIT = "edit";
private static final String TO_STRING = "toString";
private static final String PROTECTED_VOID_ON_PAUSE = "protected void onPause()"; //$NON-NLS-1$
private static final String PROTECTED_VOID_ON_RESUME = "protected void onResume()"; //$NON-NLS-1$
private static final String ON_PAUSE = "onPause"; //$NON-NLS-1$
private static final String ON_RESUME = "onResume"; //$NON-NLS-1$
/**
* @param codeGeneratorData
* @param onCreateDeclaration
* @param typeDeclaration
*/
public SaveStateCodeGenerator(CodeGeneratorDataBasedOnLayout codeGeneratorData,
MethodDeclaration onCreateDeclaration, TypeDeclaration typeDeclaration)
{
super(codeGeneratorData, onCreateDeclaration, typeDeclaration);
}
@SuppressWarnings("unchecked")
@Override
public void generateCode(IProgressMonitor monitor) throws JavaModelException
{
SubMonitor subMonitor = SubMonitor.convert(monitor);
subMonitor.beginTask(CodeUtilsNLS.SaveStateCodeGenerator_AddingCodeSaveRestoreUIState,
codeGeneratorData.getGuiItems().size());
boolean alreadyFoundItemToSaveState = false;
MethodDeclaration onPauseFoundMethod = null;
MethodDeclaration onResumeFoundMethod = null;
SimpleName editorVarName = null;
SimpleName preferencesVarName = null;
for (LayoutNode node : codeGeneratorData.getGUIItems(false))
{
if (canGenerateSaveStateCode(node) && node.getSaveState()
&& (node.isAlreadyDeclaredInCode() || node.shouldInsertCode()))
{
if (!alreadyFoundItemToSaveState)
{
onPauseFoundMethod = insertOnPause();
onResumeFoundMethod = insertOnResume();
String superMethodNameOnPause = ON_PAUSE;
insertSuperInvocation(onPauseFoundMethod, superMethodNameOnPause, null);
String superMethodNameOnResume = ON_RESUME;
insertSuperInvocation(onResumeFoundMethod, superMethodNameOnResume, null);
JavaModifierBasedOnLayout.IMPORT_LIST.add(ANDROID_CONTENT_IMPORT);
preferencesVarName = getPreferenceVariable(onPauseFoundMethod);
editorVarName = insertPreferencesEditor(onPauseFoundMethod, preferencesVarName);
getPreferenceVariable(onResumeFoundMethod);
//need to add method declaration and variables only once
alreadyFoundItemToSaveState = true;
}
String putMethodName = node.getProperty(ViewProperties.PreferenceSetMethod);
String getUIStateMethodName = node.getProperty(ViewProperties.ViewStateGetMethod);
String getter = node.getProperty(ViewProperties.PreferenceGetMethod);
String setMethodName = node.getProperty(ViewProperties.ViewStateSetMethod);
MethodInvocation getGUIStateInvocation = null;
if (node.getNodeType().equals(LayoutNode.LayoutNodeViewType.EditText.name()))
{
//Add code for save state: savedInstanceState.putString("MyEditTextId", edittext.getText());
getGUIStateInvocation = onPauseFoundMethod.getAST().newMethodInvocation();
String getGUIStateToStringName = TO_STRING; //$NON-NLS-1$
MethodInvocation editTextMethod =
addMethodToRetrieveUIState(onPauseFoundMethod, node,
getUIStateMethodName);
SimpleName toStringName =
onPauseFoundMethod.getAST().newSimpleName(getGUIStateToStringName);
getGUIStateInvocation.setName(toStringName);
getGUIStateInvocation.setExpression(editTextMethod);
insertPutMethod(onPauseFoundMethod, node, putMethodName,
editorVarName.getIdentifier(), getGUIStateInvocation);
//Add code for restore state: edittext.setText(savedInstanceState.getString("MyEditTextId"));
MethodInvocation getBundleStateInvocation =
onResumeFoundMethod.getAST().newMethodInvocation();
SimpleName getName = onPauseFoundMethod.getAST().newSimpleName(getter);
getBundleStateInvocation.setName(getName);
SimpleName getExpr =
onPauseFoundMethod.getAST().newSimpleName(
preferencesVarName.getIdentifier());
getBundleStateInvocation.setExpression(getExpr);
StringLiteral id = onPauseFoundMethod.getAST().newStringLiteral();
id.setLiteralValue(node.getNodeId());
getBundleStateInvocation.arguments().add(id);
StringLiteral defaultValue = onPauseFoundMethod.getAST().newStringLiteral();
defaultValue.setLiteralValue("");
getBundleStateInvocation.arguments().add(defaultValue);
insertSetMethod(onResumeFoundMethod, node, setMethodName,
getBundleStateInvocation);
}
else
{
//Add code to save state: savedInstanceState.putBoolean("MyCheckboxId", checkbox.getEnabled());
//Add code to restore state: checkbox.setEnabled(savedInstanceState.getBoolean("MyCheckbox"));
insertSaveRestoreCode(onPauseFoundMethod, onResumeFoundMethod,
editorVarName.getIdentifier(), preferencesVarName.getIdentifier(),
node, putMethodName, setMethodName, getter, getUIStateMethodName);
}
}
subMonitor.worked(1);
}
if (alreadyFoundItemToSaveState)
{
//Add code: editor.commit();
invokeMethod(onPauseFoundMethod, EDITOR, COMMIT);
}
}
/**
* @param node layout node being analyzed
* @return true if it is in the array of saveStateNodeTypes (the current supported view items to save/restore state), false otherwise
*/
public static boolean canGenerateSaveStateCode(LayoutNode node)
{
boolean canSaveCode = false;
if ((node != null) && (node.getNodeType() != null))
{
int i = 0;
while (!canSaveCode && (i < saveStateNodeTypes.length))
{
if (node.getNodeType().equals(saveStateNodeTypes[i].getNodeType()))
{
canSaveCode = true;
}
i++;
}
}
return canSaveCode;
}
/**
* Inserts SharedPreferences.Editor statement into onPause method
* @param onPauseFoundMethod
* @param preferencesVarName
* @return
*/
@SuppressWarnings("unchecked")
public SimpleName insertPreferencesEditor(MethodDeclaration onPauseFoundMethod,
SimpleName preferencesVarName)
{
SimpleName editorVarName = onPauseFoundMethod.getAST().newSimpleName(EDITOR);
boolean alreadyAddedVariable = false;
if (onPauseFoundMethod.getBody() != null)
{
outer: for (Object s : onPauseFoundMethod.getBody().statements())
{
if (s instanceof VariableDeclarationStatement)
{
VariableDeclarationStatement variableDeclarationStatement =
(VariableDeclarationStatement) s;
if (variableDeclarationStatement.getType().toString()
.equals(SHARED_PREFERENCES + "." + EDITOR_CAPITAL_LETTER))
{
for (Object f : variableDeclarationStatement.fragments())
{
VariableDeclarationFragment frag = (VariableDeclarationFragment) f;
if (frag.getName().toString().equals(EDITOR))
{
alreadyAddedVariable = true;
break outer;
}
}
}
}
}
}
if (!alreadyAddedVariable)
{
VariableDeclarationFragment getEditorPreference =
onPauseFoundMethod.getAST().newVariableDeclarationFragment();
MethodInvocation getEditorInvoke = onPauseFoundMethod.getAST().newMethodInvocation();
SimpleName editInvokeName = onPauseFoundMethod.getAST().newSimpleName(EDIT);
getEditorInvoke.setName(editInvokeName);
SimpleName editInvokeVariable =
onPauseFoundMethod.getAST().newSimpleName(preferencesVarName.getIdentifier());
getEditorInvoke.setExpression(editInvokeVariable);
getEditorPreference.setInitializer(getEditorInvoke);
getEditorPreference.setName(editorVarName);
VariableDeclarationStatement getEditorVariableDeclarationStatement =
onPauseFoundMethod.getAST()
.newVariableDeclarationStatement(getEditorPreference);
SimpleName sharedPreferencesName1 =
onPauseFoundMethod.getAST().newSimpleName(SHARED_PREFERENCES);
SimpleType type = onPauseFoundMethod.getAST().newSimpleType(sharedPreferencesName1);
SimpleName sharedPreferencesName2 =
onPauseFoundMethod.getAST().newSimpleName(EDITOR_CAPITAL_LETTER);
QualifiedType editorPreferencesType =
onPauseFoundMethod.getAST().newQualifiedType(type, sharedPreferencesName2);
getEditorVariableDeclarationStatement.setType(editorPreferencesType);
onPauseFoundMethod.getBody().statements().add(getEditorVariableDeclarationStatement);
}
return editorVarName;
}
/**
* Creates onResume method if it does not exist yet
* @return {@link MethodDeclaration} for void onResume() method
*/
@SuppressWarnings("unchecked")
public MethodDeclaration insertOnResume()
{
//Add protected void onResume()
ModifierKeyword keyword = ModifierKeyword.PUBLIC_KEYWORD;
if (getCodeGeneratorData().getAssociatedType().equals(
CodeGeneratorDataBasedOnLayout.TYPE.FRAGMENT))
{
keyword = ModifierKeyword.PUBLIC_KEYWORD;
}
else if (getCodeGeneratorData().getAssociatedType().equals(
CodeGeneratorDataBasedOnLayout.TYPE.ACTIVITY))
{
keyword = ModifierKeyword.PROTECTED_KEYWORD;
}
MethodDeclaration onResumeMethodDeclaration =
addMethodDeclaration(keyword, ON_RESUME, PrimitiveType.VOID,
new ArrayList<SingleVariableDeclaration>());
Block onResumeMethodBlock = onCreateDeclaration.getAST().newBlock();
MethodDeclaration onResumeFoundMethod =
isMethodAlreadyDeclared(onResumeMethodDeclaration, PROTECTED_VOID_ON_RESUME);
if (onResumeFoundMethod == null)
{
//add method onRestore if it does not exist yet
onResumeMethodDeclaration.setBody(onResumeMethodBlock);
typeDeclaration.bodyDeclarations().add(onResumeMethodDeclaration);
onResumeFoundMethod = onResumeMethodDeclaration;
}
return onResumeFoundMethod;
}
/**
* Creates onPause method if it does not exist yet
* @return {@link MethodDeclaration} for void onPause() method
*/
@SuppressWarnings("unchecked")
public MethodDeclaration insertOnPause()
{
//Add protected void onPause()
ModifierKeyword keyword = ModifierKeyword.PUBLIC_KEYWORD;
if (getCodeGeneratorData().getAssociatedType().equals(
CodeGeneratorDataBasedOnLayout.TYPE.FRAGMENT))
{
keyword = ModifierKeyword.PUBLIC_KEYWORD;
}
else if (getCodeGeneratorData().getAssociatedType().equals(
CodeGeneratorDataBasedOnLayout.TYPE.ACTIVITY))
{
keyword = ModifierKeyword.PROTECTED_KEYWORD;
}
MethodDeclaration onPauseMethodDeclaration =
addMethodDeclaration(keyword, ON_PAUSE, PrimitiveType.VOID,
new ArrayList<SingleVariableDeclaration>());
Block onPauseMethodBlock = onCreateDeclaration.getAST().newBlock();
MethodDeclaration onPauseFoundMethod =
isMethodAlreadyDeclared(onPauseMethodDeclaration, PROTECTED_VOID_ON_PAUSE);
if (onPauseFoundMethod == null)
{
//add method onPause if it does not exist yet
onPauseMethodDeclaration.setBody(onPauseMethodBlock);
typeDeclaration.bodyDeclarations().add(onPauseMethodDeclaration);
onPauseFoundMethod = onPauseMethodDeclaration;
}
return onPauseFoundMethod;
}
/**
* @param method
* @return {@link SimpleName} for the variable with SharedPreferences type inside the method body
*/
@SuppressWarnings("unchecked")
public SimpleName getPreferenceVariable(MethodDeclaration method)
{
SimpleName preferencesVarName = method.getAST().newSimpleName(PREFERENCES);
boolean alreadyAddedVariable = false;
if (method.getBody() != null)
{
outer: for (Object s : method.getBody().statements())
{
if (s instanceof VariableDeclarationStatement)
{
VariableDeclarationStatement variableDeclarationStatement =
(VariableDeclarationStatement) s;
if (variableDeclarationStatement.getType().toString()
.equals(SHARED_PREFERENCES))
{
for (Object f : variableDeclarationStatement.fragments())
{
VariableDeclarationFragment frag = (VariableDeclarationFragment) f;
if (frag.getName().toString().equals(PREFERENCES))
{
alreadyAddedVariable = true;
break outer;
}
}
}
}
}
}
if (!alreadyAddedVariable)
{
VariableDeclarationFragment getPreferencefragment =
method.getAST().newVariableDeclarationFragment();
MethodInvocation invoke = method.getAST().newMethodInvocation();
SimpleName invokeName = method.getAST().newSimpleName(GET_PREFERENCES);
invoke.setName(invokeName);
if (getCodeGeneratorData().getAssociatedType().equals(
CodeGeneratorDataBasedOnLayout.TYPE.ACTIVITY))
{
SimpleName invokeMode = method.getAST().newSimpleName(MODE_PRIVATE);
invoke.arguments().add(invokeMode);
}
else if (getCodeGeneratorData().getAssociatedType().equals(
CodeGeneratorDataBasedOnLayout.TYPE.FRAGMENT))
{
SimpleName invokeMode = method.getAST().newSimpleName(MODE_PRIVATE);
SimpleName activityRef = method.getAST().newSimpleName("Activity");
QualifiedName qName = method.getAST().newQualifiedName(activityRef, invokeMode);
MethodInvocation activityInvoke = method.getAST().newMethodInvocation();
SimpleName activityMethodName = method.getAST().newSimpleName("getActivity");
activityInvoke.setName(activityMethodName);
invoke.setExpression(activityInvoke);
invoke.arguments().add(qName);
}
getPreferencefragment.setInitializer(invoke);
getPreferencefragment.setName(preferencesVarName);
VariableDeclarationStatement variableDeclarationStatement =
method.getAST().newVariableDeclarationStatement(getPreferencefragment);
SimpleName sharedPreferencesName = method.getAST().newSimpleName(SHARED_PREFERENCES);
SimpleType sharedPreferencesType = method.getAST().newSimpleType(sharedPreferencesName);
variableDeclarationStatement.setType(sharedPreferencesType);
method.getBody().statements().add(variableDeclarationStatement);
}
return preferencesVarName;
}
/**
* Add code to save state: $bundleNameOnSaveMethod.$putMethodName("$nodeId", $nodeId.$getUIStateMethodName());
* Add code to restore state: $nodeId.$setMethodName($bundleNameOnRestoreMethod.$getBundleState("$nodeId"));
*/
@SuppressWarnings("unchecked")
public void insertSaveRestoreCode(MethodDeclaration onSaveInstanceStateFoundMethod,
MethodDeclaration onRestoreInstanceFoundMethod, String bundleNameOnSaveMethod,
String bundleNameOnRestoreMethod, LayoutNode node, String putMethodName,
String setMethodName, String getBundleState, String getUIStateMethodName)
{
MethodInvocation getGUIStateInvocation;
getGUIStateInvocation =
addMethodToRetrieveUIState(onSaveInstanceStateFoundMethod, node,
getUIStateMethodName);
insertPutMethod(onSaveInstanceStateFoundMethod, node, putMethodName,
bundleNameOnSaveMethod, getGUIStateInvocation);
MethodInvocation getBundleStateInvocation =
onRestoreInstanceFoundMethod.getAST().newMethodInvocation();
SimpleName getName = onSaveInstanceStateFoundMethod.getAST().newSimpleName(getBundleState);
getBundleStateInvocation.setName(getName);
SimpleName getExpr =
onSaveInstanceStateFoundMethod.getAST().newSimpleName(bundleNameOnRestoreMethod);
getBundleStateInvocation.setExpression(getExpr);
StringLiteral id = onSaveInstanceStateFoundMethod.getAST().newStringLiteral();
id.setLiteralValue(node.getNodeId());
getBundleStateInvocation.arguments().add(id);
String stateType = node.getProperty(ViewProperties.ViewStateValueType);
if (stateType != null)
{
if (stateType.equals(Integer.class.toString()))
{
NumberLiteral defaultValue =
onSaveInstanceStateFoundMethod.getAST().newNumberLiteral();
getBundleStateInvocation.arguments().add(defaultValue);
}
else if (stateType.equals(Boolean.class.toString()))
{
BooleanLiteral defaultValue =
onSaveInstanceStateFoundMethod.getAST().newBooleanLiteral(false);
getBundleStateInvocation.arguments().add(defaultValue);
}
}
insertSetMethod(onRestoreInstanceFoundMethod, node, setMethodName, getBundleStateInvocation);
}
/**
* Insert method in the format $nodeId.$getUIStateMethodName()
* @param onSaveInstanceStateFoundMethod
* @param node
* @param getUIStateMethodName
* @return
*/
public MethodInvocation addMethodToRetrieveUIState(
MethodDeclaration onSaveInstanceStateFoundMethod, LayoutNode node,
String getUIStateMethodName)
{
MethodInvocation retrieveUIStateMethod =
onSaveInstanceStateFoundMethod.getAST().newMethodInvocation();
SimpleName guiId = onSaveInstanceStateFoundMethod.getAST().newSimpleName(node.getNodeId());
retrieveUIStateMethod.setExpression(guiId);
SimpleName getText =
onSaveInstanceStateFoundMethod.getAST().newSimpleName(getUIStateMethodName);
retrieveUIStateMethod.setName(getText);
return retrieveUIStateMethod;
}
/**
* Add method in the format savedInstanceState.putXXXX($nodeid, $getGUIStateInvocation);
* @param onSaveInstanceStateFoundMethod
* @param node
* @param methodName
* @param bundleName
* @param getGUIStateInvocation
*/
@SuppressWarnings("unchecked")
public void insertPutMethod(MethodDeclaration onSaveInstanceStateFoundMethod, LayoutNode node,
String methodName, String bundleName, MethodInvocation getGUIStateInvocation)
{
MethodInvocation putMethod = onSaveInstanceStateFoundMethod.getAST().newMethodInvocation();
SimpleName putMethodName =
onSaveInstanceStateFoundMethod.getAST().newSimpleName(methodName);
putMethod.setName(putMethodName);
SimpleName bundle = onSaveInstanceStateFoundMethod.getAST().newSimpleName(bundleName);
putMethod.setExpression(bundle);
StringLiteral id = onSaveInstanceStateFoundMethod.getAST().newStringLiteral();
id.setLiteralValue(node.getNodeId());
putMethod.arguments().add(id);
putMethod.arguments().add(getGUIStateInvocation);
ExpressionStatement exprSt =
onSaveInstanceStateFoundMethod.getAST().newExpressionStatement(putMethod);
int commitPosition =
findMethodInvocation(onSaveInstanceStateFoundMethod.getBody().statements(), bundle,
"commit");
onSaveInstanceStateFoundMethod.getBody().statements().add(commitPosition, exprSt);
}
private int findMethodInvocation(List<?> statements, final SimpleName bundle,
final String methodName)
{
int position = -1;
int i = 0;
while ((i < statements.size()) && (position == -1))
{
Statement statement = (Statement) statements.get(i);
if (statement instanceof ExpressionStatement)
{
ExpressionStatement expressionSt = (ExpressionStatement) statement;
Expression expression = expressionSt.getExpression();
if (expression instanceof MethodInvocation)
{
MethodInvocation method = (MethodInvocation) expression;
if (method.getName().getIdentifier().equals(methodName)
&& (method.getExpression() instanceof SimpleName))
{
SimpleName name = (SimpleName) method.getExpression();
if (name.getIdentifier().equals(bundle.getIdentifier()))
{
position = i;
}
}
}
}
i++;
}
if (position == -1)
{
position = i;
}
return position;
}
/**
* Add method in the format $nodeId.setXXXXX($getBundleStateInvocation);
* @param onRestoreInstanceStateFoundMethod
* @param node
* @param methodName
* @param bundleName
* @param getBundleStateInvocation
*/
@SuppressWarnings("unchecked")
public void insertSetMethod(MethodDeclaration onRestoreInstanceStateFoundMethod,
LayoutNode node, String methodName, MethodInvocation getBundleStateInvocation)
{
MethodInvocation setMethod =
onRestoreInstanceStateFoundMethod.getAST().newMethodInvocation();
SimpleName setMethodName =
onRestoreInstanceStateFoundMethod.getAST().newSimpleName(methodName);
setMethod.setName(setMethodName);
SimpleName id = onRestoreInstanceStateFoundMethod.getAST().newSimpleName(node.getNodeId());
setMethod.setExpression(id);
//add in the end of the method
setMethod.arguments().add(getBundleStateInvocation);
ExpressionStatement exprSt =
onRestoreInstanceStateFoundMethod.getAST().newExpressionStatement(setMethod);
onRestoreInstanceStateFoundMethod.getBody().statements().add(exprSt);
}
}