/*
* 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.motorolamobility.preflighting.core.internal.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.nio.CharBuffer;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
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.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.osgi.util.NLS;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import com.motorolamobility.preflighting.core.applicationdata.ApplicationData;
import com.motorolamobility.preflighting.core.applicationdata.Element;
import com.motorolamobility.preflighting.core.applicationdata.Element.Type;
import com.motorolamobility.preflighting.core.applicationdata.FolderElement;
import com.motorolamobility.preflighting.core.applicationdata.ResourcesFolderElement;
import com.motorolamobility.preflighting.core.applicationdata.SourceFolderElement;
import com.motorolamobility.preflighting.core.applicationdata.StringsElement;
import com.motorolamobility.preflighting.core.applicationdata.XMLElement;
import com.motorolamobility.preflighting.core.exception.PreflightingToolException;
import com.motorolamobility.preflighting.core.i18n.PreflightingCoreNLS;
import com.motorolamobility.preflighting.core.logging.PreflightingLogger;
import com.motorolamobility.preflighting.core.sdk.SdkUtils;
import com.motorolamobility.preflighting.core.source.model.Constant;
import com.motorolamobility.preflighting.core.source.model.Field;
import com.motorolamobility.preflighting.core.source.model.Invoke;
import com.motorolamobility.preflighting.core.source.model.Method;
import com.motorolamobility.preflighting.core.source.model.SourceFileElement;
import com.motorolamobility.preflighting.core.source.model.Variable;
import com.motorolamobility.preflighting.core.validation.Parameter;
import com.motorolamobility.preflighting.core.validation.ValidationManager;
import com.motorolamobility.preflighting.core.verbose.DebugVerboseOutputter;
import com.motorolamobility.preflighting.core.verbose.DebugVerboseOutputter.VerboseLevel;
public final class ProjectUtils
{
//Constants
private static final String ANDROID_VERSION_API_LEVEL = "AndroidVersion.ApiLevel";
private static final String SOURCE_PROPERTIES = "source.properties";
private static final String ANDROID_JAR = "android.jar";
private static final String PLATFORMS = "platforms";
private static final String ANDROID = "android-";
private static final String TARGET = "target";
private static final String DEFAULT_PROPERTIES = "default.properties";
private static final String PROJECT_PROPERTIES = "project.properties";
private static final String R_LAYOUT = "R.layout"; //$NON-NLS-1$
private static final String R_STRING = "R.string"; //$NON-NLS-1$
// Constants for validate the android project structure
private static final String ANDROID_APK_NAMESPACE_URI =
"http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
private static final String ANDROID_SCHEME_ATT = "xmlns:android"; //$NON-NLS-1$
private static final String TAG_RESOURCES = "resources"; //$NON-NLS-1$
private static final String TAG_MANIFEST = "manifest"; //$NON-NLS-1$
private static final String ANDROID_MANIFEST_NAME = "AndroidManifest.xml"; //$NON-NLS-1$
private static final String XML_FILE_EXTENSION = "xml"; //$NON-NLS-1$
private static final String FOLDER_DRAWABLE = "drawable"; //$NON-NLS-1$
private static final String FOLDER_VALUES = "values"; //$NON-NLS-1$
private static final String FOLDER_LAYOUT = "layout"; //$NON-NLS-1$
private static final String FOLDER_DIST = "dist"; //$NON-NLS-1$
private static final String FOLDER_SRC = "src"; //$NON-NLS-1$
private static final String FOLDER_SMALI = "smali"; //$NON-NLS-1$
private static final String FOLDER_GEN = "gen"; //$NON-NLS-1$
private static final String FOLDER_RES = "res"; //$NON-NLS-1$
private static final String FOLDER_LIB = "lib"; //$NON-NLS-1$
public static final String LINE_NUMBER_KEY = "line_number"; //$NON-NLS-1$
public static final String JAVA_FILE_PROPERTY = "java_file";
public static final FileNameMap fileNameMap = URLConnection.getFileNameMap();
// mapping names of the android folders structure and theirs element types
private static HashMap<String, Type> foldersName = new HashMap<String, Type>();
/**
* Returns in appData the tree of elements of a given project or package
*
* @param project
* @param appData
* @throws IOException
*/
public static void populateAplicationData(List<Parameter> globalParameters,
ApplicationData appData) throws PreflightingToolException
{
Parameter applicationPathPrm = null;
for (Parameter param : globalParameters)
{
if (ValidationManager.InputParameter.APPLICATION_PATH.getAlias().equals(
param.getParameterType()))
{
applicationPathPrm = param;
break;
}
}
String applicationPath = applicationPathPrm.getValue();
appData.setApplicationPath(applicationPath);
File file = null;
if (applicationPath != null)
{
// mapping android project folders structure
foldersName.put(FOLDER_SRC, Element.Type.FOLDER_SRC);
foldersName.put(FOLDER_SMALI, Element.Type.FOLDER_SRC);
foldersName.put(FOLDER_GEN, Element.Type.FOLDER_SRC);
foldersName.put(FOLDER_RES, Element.Type.FOLDER_RES);
foldersName.put(FOLDER_LIB, Element.Type.FOLDER_LIB);
foldersName.put(FOLDER_DRAWABLE, Element.Type.FOLDER_DRAWABLE);
foldersName.put(FOLDER_VALUES, Element.Type.FOLDER_VALUES);
foldersName.put(FOLDER_LAYOUT, Element.Type.FOLDER_LAYOUT);
file = new File(applicationPath);
if ((file != null) && file.canRead())
{
if (file.isDirectory())
{
Element element = new Element(file.getName(), null, Element.Type.ROOT);
boolean isFolderAProject = validateProjectFolder(file);
if (!isFolderAProject)
{
throw new PreflightingToolException(
PreflightingCoreNLS.ProjectUtils_InvalidPathErrorMessage);
}
boolean isProject = true;
appData.setRootElement(element);
appData.setRootElementPath(file.getAbsolutePath());
appData.setIsProject(isProject);
appData.setName(file.getName());
populateApplicationDataRecursively(file, appData.getRootElement(), isProject,
globalParameters);
}
else
// it is a file, could be an android package
{
Parameter sdkPathPrm = null;
for (Parameter param : globalParameters)
{
if (ValidationManager.InputParameter.SDK_PATH.getAlias().equals(
param.getParameterType()))
{
sdkPathPrm = param;
break;
}
}
String sdkPath = sdkPathPrm.getValue();
String tempSdkPath = SdkUtils.getLatestAAPTToolPath(sdkPath);
if (tempSdkPath != null)
{
sdkPath = tempSdkPath;
}
// the apk should be converted to an android project
// structure and
// this project will be converted to a tree
// apktool or aapt may be used
File projectFile = ApkUtils.extractProjectFromAPK(file, sdkPath);
if ((projectFile != null) && projectFile.canRead())
{
Element element =
new Element(projectFile.getName(), null, Element.Type.ROOT);
appData.setRootElement(element);
appData.setRootElementPath(projectFile.getAbsolutePath());
IPath path = new Path(file.getName());
appData.setName(path.removeFileExtension().toString());
// extract certificate info from APK
try
{
List<Certificate> certificateChain = ApkUtils.populateCertificate(file);
appData.setCertificateChain(certificateChain);
}
catch (Exception e)
{
PreflightingLogger.error(ProjectUtils.class,
PreflightingCoreNLS.ProjectUtils_ErrorReadingCertificate, e); //$NON-NLS-1$
throw new PreflightingToolException(
PreflightingCoreNLS.ProjectUtils_ErrorReadingCertificate);
}
populateApplicationDataRecursively(projectFile, appData.getRootElement(),
false, globalParameters);
}
}
}
else
{
throw new PreflightingToolException(
PreflightingCoreNLS.ProjectUtils_InvalidPathErrorMessage);
}
}
}
/**
* Verifies if the given path contains an Android project. It just checks if
* there's an AndroidManifest.xml on the root dir.
*
* @param dir
* the path
* @return true if the folder contains a project, false otherwise.
* @throws PreflightingToolException
*/
public static boolean validateProjectFolder(File dir)
{
File[] androidManifest = dir.listFiles(new FilenameFilter()
{
public boolean accept(File directory, String fileName)
{
return fileName.equalsIgnoreCase(ANDROID_MANIFEST_NAME);
}
});
return androidManifest.length > 0;
}
/**
* Build the tree of elements by categorizing each element, creating the
* proper element object and adding the element on the proper node of the
* tree.
*
* @param project
* @param appElement
*/
private static void populateApplicationDataRecursively(File project, Element appElement,
boolean isProject, List<Parameter> globalParameters)
{
Element element = null;
File[] files = project.listFiles();
// the XML Document fulfilled by checkElementType method when the
// element is a XML file
Document[] xmlDoc = new Document[1];
for (int i = 0; i < files.length; i++)
{
File file = files[i];
Type elementType = checkElementType(file, appElement, xmlDoc);
if (file.isFile())
{
if (xmlDoc[0] != null)
{
if (elementType == Type.FILE_STRINGS)
{
element = new StringsElement(file.getName(), appElement);
}
else if (elementType == Type.FILE_LAYOUT)
{
element = new XMLElement(file.getName(), appElement, elementType);
}
else
{
element = new XMLElement(file.getName(), appElement, elementType);
}
// fill each node of xmlDoc with line number information
if (file.getName().equals(ANDROID_MANIFEST_NAME))
{
populateLineNumber(file, xmlDoc);
}
((XMLElement) element).setDocument(xmlDoc[0]);
}
else
{
element = new Element(file.getName(), appElement, elementType);
}
element.setFile(file);
appElement.addChild(element);
}
else
{
// dist folder is not scanned
if (file.getName().equals(FOLDER_DIST))
{
continue;
}
else if (elementType.equals(Element.Type.FOLDER_RES))
{
if (!file.getAbsolutePath().endsWith(
File.separator + "bin" + File.separator + "res"))
{
// WARNING: the if above is necessary since ADT R14
// do not consider /bin/res (otherwise it will break
// several conditions)
element = new ResourcesFolderElement(file, appElement);
}
}
else if (elementType.equals(Element.Type.FOLDER_SRC))
{
try
{
if (!isProject)
{
element = ApktoolUtils.extractJavaModel(file, appElement, file);
}
else
{
element =
ProjectUtils.createCompilationUnits(appElement, file, project,
globalParameters);
}
}
catch (Exception e)
{
element = new FolderElement(file, appElement, elementType);
PreflightingLogger.error(ProjectUtils.class,
PreflightingCoreNLS.ProjectUtils_ErrorReadingJavaModel, e); //$NON-NLS-1$
}
}
else
{
element = new FolderElement(file, appElement, elementType);
}
if (element != null)
{
appElement.addChild(element);
populateApplicationDataRecursively(file, element, isProject, globalParameters);
}
}
}
}
/*
* Sets each DOM with line number information
*
* @param xmlFile
*/
private static void populateLineNumber(File xmlFile, Document[] xmlDoc)
{
ArrayList<Node> nodesSequencialList = new ArrayList<Node>();
xmlDoc[0].getDocumentElement().normalize();
populateSequencialNodesList(xmlDoc[0], nodesSequencialList, xmlDoc);
SAXParserFactory saxfac = SAXParserFactory.newInstance();
SAXParser saxParser;
try
{
saxParser = saxfac.newSAXParser();
saxParser.parse(xmlFile, new XmlLineHandler(nodesSequencialList));
}
catch (Exception e)
{
PreflightingLogger.warn(ProjectUtils.class,
"Line number information will not be added to " + xmlFile.getName(), e); //$NON-NLS-1$
// do nothing - line number information will not be added
}
nodesSequencialList.clear();
}
/**
* Find the proper Element.Type for the given fileElement.
*
* @param fileElement
* @param appElement
* @return elementType
*/
private static Type checkElementType(final File fileElement, Element appElement,
Document[] xmlDoc)
{
Type elementType = null;
if (fileElement.isDirectory())
{
// try to find the current folder on the android project default
// structure
elementType = foldersName.get(fileElement.getName().toLowerCase());
if (elementType == null)
{
// this other folders are part of the android project default
// structure but can have minor variations
// such as: drawable-hdpi, drawable-ldpi, values-pt
if (fileElement.getName().toLowerCase().startsWith(FOLDER_DRAWABLE))
{
elementType = foldersName.get(FOLDER_DRAWABLE);
}
else if (fileElement.getName().toLowerCase().startsWith(FOLDER_VALUES))
{
elementType = foldersName.get(FOLDER_VALUES);
}
else if (fileElement.getName().toLowerCase().startsWith(FOLDER_LAYOUT))
{
elementType = foldersName.get(FOLDER_LAYOUT);
}
else
{
elementType = Element.Type.FOLDER_UNKNOWN;
}
}
}
else
// element is a file
{
// it probably is a XML file
if (fileElement.getName().endsWith(XML_FILE_EXTENSION))
{
// try to convert the possible xml file into a Document
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = null;
try
{
db = dbf.newDocumentBuilder();
db.setErrorHandler(new ErrorHandler()
{
public void warning(SAXParseException saxException) throws SAXException
{
DebugVerboseOutputter.printVerboseMessage(NLS.bind(
PreflightingCoreNLS.ProjectUtils_Error_Parsing_Manifest_INFO,
fileElement.getName()), VerboseLevel.v1);
DebugVerboseOutputter.printVerboseMessage(NLS.bind(
PreflightingCoreNLS.ProjectUtils_Error_Parsing_Manifest_DEBUG,
new String[]
{
fileElement.getName(),
Integer.toString(saxException.getLineNumber()),
saxException.getLocalizedMessage()
}), VerboseLevel.v2);
PreflightingLogger.warn(
ProjectUtils.class,
"Could not parse " //$NON-NLS-1$
+ fileElement.getName()
+ ":" + saxException.getLineNumber() + " - " + saxException.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ $NON-NLS-2$
}
public void fatalError(SAXParseException saxException) throws SAXException
{
DebugVerboseOutputter.printVerboseMessage(NLS.bind(
PreflightingCoreNLS.ProjectUtils_Error_Parsing_Manifest_INFO,
fileElement.getName()), VerboseLevel.v1);
DebugVerboseOutputter.printVerboseMessage(NLS.bind(
PreflightingCoreNLS.ProjectUtils_Error_Parsing_Manifest_DEBUG,
new String[]
{
fileElement.getName(),
Integer.toString(saxException.getLineNumber()),
saxException.getLocalizedMessage()
}), VerboseLevel.v2);
PreflightingLogger.fatal(
ProjectUtils.class,
"Could not parse " //$NON-NLS-1$
+ fileElement.getName()
+ ":" + saxException.getLineNumber() + " - " + saxException.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ $NON-NLS-2$
}
public void error(SAXParseException saxException) throws SAXException
{
DebugVerboseOutputter.printVerboseMessage(NLS.bind(
PreflightingCoreNLS.ProjectUtils_Error_Parsing_Manifest_INFO,
fileElement.getName()), VerboseLevel.v1);
DebugVerboseOutputter.printVerboseMessage(NLS.bind(
PreflightingCoreNLS.ProjectUtils_Error_Parsing_Manifest_DEBUG,
new String[]
{
fileElement.getName(),
Integer.toString(saxException.getLineNumber()),
saxException.getLocalizedMessage()
}), VerboseLevel.v2);
PreflightingLogger.error(
ProjectUtils.class,
"Could not parse " //$NON-NLS-1$
+ fileElement.getName()
+ ":" + saxException.getLineNumber() + " - " + saxException.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ $NON-NLS-2$
}
});
xmlDoc[0] = db.parse(fileElement);
}
catch (Exception e)
{
PreflightingLogger.warn(ProjectUtils.class, "Could not read the " //$NON-NLS-1$
+ fileElement.getName() + " file.", e); //$NON-NLS-1$
}
if (xmlDoc[0] != null) // it really is a valid XML file
{
if (appElement.getType().equals(Element.Type.ROOT)
&& fileElement.getName().equals(ANDROID_MANIFEST_NAME))
{
// the AndroidManifest file should be under the project
// root
// Validating the attribute
// xmlns:android="http://schemas.android.com/apk/res/android
// to check if the file really is an AndroidManifest
if (xmlDoc[0].getDocumentElement().getAttribute(ANDROID_SCHEME_ATT)
.equals(ANDROID_APK_NAMESPACE_URI)
&& xmlDoc[0].getDocumentElement().getNodeName()
.equals(TAG_MANIFEST))
{
elementType = Element.Type.FILE_MANIFEST;
}
}
else if (appElement.getType().equals(Element.Type.FOLDER_LAYOUT)) // the parent is the
// layout folder
{
// Checking if the xml file is an android layout file by
// validating the attribute
// xmlns:android="http://schemas.android.com/apk/res/android"
if (xmlDoc[0].getDocumentElement().getAttribute(ANDROID_SCHEME_ATT)
.equals(ANDROID_APK_NAMESPACE_URI))
{
elementType = Element.Type.FILE_LAYOUT;
}
}
else if (appElement.getType().equals(Element.Type.FOLDER_VALUES)) // the parent is the
// values folder
{
// Check the tag indicating if it really is a strings
// file
if (xmlDoc[0].getDocumentElement().getNodeName().equals(TAG_RESOURCES))
{
elementType = Element.Type.FILE_STRINGS;
}
}
else
{
elementType = Element.Type.FILE_XML;
}
}
}
else if (appElement.getType().equals(Element.Type.FOLDER_DRAWABLE)) // the parent is the
// drawable folder
{
// check if the file is a valid image
String mimeType = fileNameMap.getContentTypeFor(fileElement.getAbsolutePath());
if ((mimeType != null) && mimeType.startsWith("image/"))
{
elementType = Element.Type.FILE_DRAWABLE;
}
}
if (elementType == null)
{
elementType = Element.Type.FILE_UNKNOWN;
}
}
return elementType;
}
public static SourceFolderElement createCompilationUnits(Element parent, File srcDir,
File projectDir, List<Parameter> globalParameters) throws IOException,
PreflightingToolException
{
SourceFolderElement model = new SourceFolderElement(srcDir, parent, false);
IPath projectPath = Path.fromOSString(srcDir.getParent());
List<CompilationUnit> list = new ArrayList<CompilationUnit>();
List<File> classPathFiles = readLibPathsFromClasspathEntries(projectDir);
File androidTarget = getAndroidTargetPathForProject(projectDir, globalParameters);
classPathFiles.add(androidTarget);
visitFolderToIdentifyClasses(srcDir, list, projectPath, classPathFiles);
for (CompilationUnit compilationUnit : list)
{
SourceFileElement sourceFileElement = ProjectUtils.readFromJava(compilationUnit, model);
sourceFileElement.setCompilationUnit(compilationUnit);
model.addChild(sourceFileElement);
model.getSourceFileElements().add(sourceFileElement);
}
return model;
}
/*
* Read all .java files inside src folder and create its ASTs objects.
*
* @param sourceFile
*
* @param list List of ASTs which is updated at each recursive call
*
* @param projectPath
*
* @throws PreflightingToolException
*/
private static void visitFolderToIdentifyClasses(File sourceFile, List<CompilationUnit> list,
IPath projectPath, List<File> classPathFiles) throws PreflightingToolException
{
try
{
if (sourceFile.isFile())
{
// is a java file
if (sourceFile.getName().endsWith(".java"))
{
FileReader reader = null;
CharBuffer cb = null;
try
{
reader = new FileReader(sourceFile);
cb = CharBuffer.allocate((int) sourceFile.length());
int count = reader.read(cb);
cb.flip();
// verify if all bytes were read
if (count == sourceFile.length())
{
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource(cb.array());
Map options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_1_5, options);
parser.setCompilerOptions(options);
List<String> classPathList =
new ArrayList<String>(classPathFiles.size());
for (File file : classPathFiles)
{
classPathList.add(file.getAbsolutePath());
}
String[] classpathEntries =
classPathList.toArray(new String[classPathFiles.size()]);
File srcFolder = new File(projectPath.toFile(), "src");
File genFolder = new File(projectPath.toFile(), "gen");
int sourcepathEntriesSize = 0;
if (srcFolder.exists())
{
sourcepathEntriesSize++;
}
if (genFolder.exists())
{
sourcepathEntriesSize++;
}
String[] sourcepathEntries = new String[sourcepathEntriesSize];
if (sourcepathEntriesSize == 1)
{
sourcepathEntries[0] = srcFolder.getAbsolutePath();
}
if (sourcepathEntriesSize == 2)
{
sourcepathEntries[0] = srcFolder.getAbsolutePath();
sourcepathEntries[1] = genFolder.getAbsolutePath();
}
parser.setEnvironment(classpathEntries, sourcepathEntries, null, true);
parser.setUnitName(computeRelativePath(projectPath,
sourceFile.getAbsolutePath()));
parser.setResolveBindings(true);
ASTNode nodes = parser.createAST(null);
if (nodes.getNodeType() == ASTNode.COMPILATION_UNIT)
{
CompilationUnit cu = (CompilationUnit) nodes;
cu.setProperty(JAVA_FILE_PROPERTY, sourceFile);
list.add(cu);
}
}
else
{
DebugVerboseOutputter.printVerboseMessage(
PreflightingCoreNLS.ProjectUtils_ErrorReadingSourceFile
+ sourceFile.getName(), VerboseLevel.v1);
}
}
// syntax error
catch (Exception syntaxException)
{
DebugVerboseOutputter.printVerboseMessage(
PreflightingCoreNLS.ProjectUtils_ErrorReadingSourceFile
+ sourceFile.getName(), VerboseLevel.v1);
}
finally
{
if (cb != null)
{
cb.clear();
}
if (reader != null)
{
reader.close();
}
}
}
}
else if (sourceFile.isDirectory())
{
File[] subDirs = sourceFile.listFiles();
for (int i = 0; i < subDirs.length; i++)
{
visitFolderToIdentifyClasses(subDirs[i], list, projectPath, classPathFiles);
}
}
}
catch (Exception e)
{
throw new PreflightingToolException(
PreflightingCoreNLS.ProjectUtils_ErrorReadingSourceFile, e);
}
}
/**
* Returns a relative path e.g. /Project/src/package/name.java
*
* @param projectPath
* @param sourcePath
* @return
*/
private static String computeRelativePath(IPath projectPath, String sourcePath)
{
IPath relativePath = Path.fromOSString(sourcePath).makeRelativeTo(projectPath);
return File.separator + projectPath.lastSegment() + File.separator
+ relativePath.toOSString();
}
/**
* Reads lib paths from project .classpath file
*
* @param projectDir
* @return list of files for libraries used inside Android project
* @throws PreflightingToolException
* problem to read .classpath file
*/
private static List<File> readLibPathsFromClasspathEntries(File projectDir)
throws PreflightingToolException
{
List<File> libPaths = new ArrayList<File>();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = null;
try
{
db = dbf.newDocumentBuilder();
File classPathFile = new File(projectDir, ".classpath");
if (classPathFile.exists())
{
Document doc = db.parse(classPathFile);
doc.getDocumentElement().normalize();
NodeList nodeLst = doc.getElementsByTagName("classpathentry");
for (int s = 0; s < nodeLst.getLength(); s++)
{
Node node = nodeLst.item(s);
if (node.getNodeType() == Node.ELEMENT_NODE)
{
NamedNodeMap attrs = node.getAttributes();
Node kindNode = attrs.getNamedItem("kind");
Node pathNode = attrs.getNamedItem("path");
if ((kindNode != null) && (pathNode != null)
&& (kindNode.getNodeValue() != null)
&& (pathNode.getNodeValue() != null))
{
if (kindNode.getNodeValue().equals("lib"))
{
String pathValue = pathNode.getNodeValue();
File f = new File(pathValue);
if (f.exists())
{
libPaths.add(f);
}
else
{
PreflightingLogger.debug(ProjectUtils.class,
"Could not find lib path: " //$NON-NLS-1$
+ f.getAbsolutePath());
}
}
}
}
}
}
}
catch (Exception e)
{
throw new PreflightingToolException(
PreflightingCoreNLS.ProjectUtils_ErrorReadingClasspathFile, e);
}
return libPaths;
}
/**
* Retrieves the path to android.jar file inside platform/$target$, where
* $target$ is defined inside default.properties file
*
* @param projectDir
* @param globalParameters
* parameters where SDK_PATH is included
* @return file with path to android.jar
* @throws PreflightingToolException
* problem to read default.properties
*/
private static File getAndroidTargetPathForProject(File projectDir,
List<Parameter> globalParameters) throws PreflightingToolException
{
File androidJarTarget = null;
Properties properties = new Properties();
// changed from default.properties to project.properties after R14
File defaultPropertiesFile = new File(projectDir, PROJECT_PROPERTIES);
if (!defaultPropertiesFile.exists())
{
// WARNING: do not remove statement below assigning
// default.properties file to keep compatibility with projects
// created with ADTs before R14
defaultPropertiesFile = new File(projectDir, DEFAULT_PROPERTIES);
}
if (defaultPropertiesFile.exists())
{
try
{
FileInputStream fileInputStream = null;
try
{
fileInputStream = new FileInputStream(defaultPropertiesFile);
properties.load(fileInputStream);
}
finally
{
try
{
fileInputStream.close();
}
catch (Exception e)
{
//Do Nothing.
}
}
if (properties.containsKey(TARGET))
{
String targetValue = properties.getProperty(TARGET);
if (targetValue != null)
{
if (!targetValue.startsWith(ANDROID))
{
try
{
// add-on => <name>:<model>:<version>
int colonIndex = targetValue.lastIndexOf(":");
if (colonIndex >= 0)
{
targetValue = ANDROID + targetValue.substring(colonIndex + 1);
}
}
catch (Exception e)
{
PreflightingLogger.debug("Unable to set target value.");
}
}
}
Parameter sdkPathPrm = null;
for (Parameter param : globalParameters)
{
if (ValidationManager.InputParameter.SDK_PATH.getAlias().equals(
param.getParameterType()))
{
sdkPathPrm = param;
break;
}
}
String sdkPath = getSdkPath(sdkPathPrm);
if (sdkPath != null)
{
// found sdk path
androidJarTarget =
new File(sdkPath + File.separator + PLATFORMS + File.separator
+ targetValue + File.separator + ANDROID_JAR);
if (!androidJarTarget.exists())
{
//if not found the exact version, then look for one version of android that is greater than target value (and retrieve android.jar)
File baseFolder = new File(sdkPath + File.separator + PLATFORMS);
File[] androidPlatforms = baseFolder.listFiles();
boolean foundJar = false;
if (androidPlatforms.length > 0)
{
for (File androidPlatform : androidPlatforms)
{
File sourcePropsFile =
new File(androidPlatform, SOURCE_PROPERTIES);
File jar = new File(androidPlatform, ANDROID_JAR);
if (sourcePropsFile.exists() && jar.exists())
{
Properties sourceProperties = new Properties();
try
{
fileInputStream = new FileInputStream(sourcePropsFile);
sourceProperties.load(fileInputStream);
}
finally
{
if (fileInputStream != null)
{
fileInputStream.close();
}
}
//platform api level
String apiLevel =
sourceProperties
.getProperty(ANDROID_VERSION_API_LEVEL);
int index = targetValue.indexOf("-");
if ((index >= 0) && (apiLevel != null))
{
//project target declared
String versionName = targetValue.substring(index + 1);
try
{
Integer version = Integer.valueOf(versionName);
Integer apiLevelVersion = Integer.valueOf(apiLevel);
if (apiLevelVersion >= version)
{
//found a compatible platform
foundJar = true;
androidJarTarget = jar;
break;
}
}
catch (NumberFormatException nfe)
{
//ignore this folder (add-on or preview)
}
}
}
}
if (!foundJar)
{
throw new PreflightingToolException(
androidJarTarget.getAbsolutePath()
+ "not found, check your sdk or your application target platform.");
}
}
}
}
else
{
throw new PreflightingToolException("Sdk path not found.");
}
}
}
catch (IOException e)
{
throw new PreflightingToolException(
PreflightingCoreNLS.ProjectUtils_ErrorReadingDefaultPropertiesFile, e);
}
}
else
{
throw new PreflightingToolException("default.properties file not found.");
}
return androidJarTarget;
}
/**
* Returns the path for Android SDK
*
* @param sdkPathPrm
* parameter with the path or "aapt" (if it is taking the value
* from PATH environment variable)
* @return resolved path to Android SDK
*/
public static String getSdkPath(Parameter sdkPathPrm)
{
String sdkPath = null;
if (sdkPathPrm.getValue().equals("aapt"))
{
// sdk was not defined - take from environment variable
String pathVariable = System.getenv("PATH");
String pathSeparator = System.getProperty("path.separator");
String subPath = null;
File checkedPath = null;
String[] folderList = null;
StringTokenizer token = new StringTokenizer(pathVariable, pathSeparator);
while (token.hasMoreTokens())
{
subPath = token.nextToken();
checkedPath = new File(subPath);
if (checkedPath.isDirectory())
{
folderList = checkedPath.list();
for (String s : folderList)
{
if (s.equals("emulator") || s.equals("emulator.exe") || s.equals("adb")
|| s.equals("adb.exe"))
{
File root = checkedPath.getParentFile();
sdkPath = root.getAbsolutePath();
break;
}
}
}
}
}
else
{
// sdk was defined on execution
sdkPath = sdkPathPrm.getValue();
}
return sdkPath;
}
/*
* Returns a list of nodes at document order.
*/
private static void populateSequencialNodesList(Node node, ArrayList<Node> nodesSequencialList,
Document[] xmlDoc)
{
if (node instanceof Document)
{
xmlDoc[0] = (Document) node;
}
else if (node instanceof org.w3c.dom.Element)
{
nodesSequencialList.add(node);
}
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++)
{
populateSequencialNodesList(children.item(i), nodesSequencialList, xmlDoc);
}
}
/**
* This method deletes the directory, all files and all subdirectories under
* it. If a deletion fails, the method stops attempting to delete and
* returns false.
*
* @param directory
* The directory to be deleted
* @return Returns true if all deletions were successful. If the directory
* doesn't exist returns false.
* @throws IOException
* When the parameter isn't a directory
*/
public static boolean deleteDirRecursively(File directory) throws IOException
{
String dirName = ""; //$NON-NLS-1$
boolean success = true;
if (directory.exists())
{
if (directory.isDirectory())
{
dirName = directory.getName();
File[] children = directory.listFiles();
for (File element : children)
{
if (element.isFile())
{
element.deleteOnExit();
success = success && element.delete();
}
else
{
success = success && deleteDirRecursively(element);
}
}
directory.deleteOnExit();
success = success && directory.delete();
}
else
{
String errorMessage = directory.getName() + " is not a diretory."; //$NON-NLS-1$
PreflightingLogger.error(errorMessage);
throw new IOException(errorMessage);
}
}
else
{
String errorMessage = "The directory does not exist."; //$NON-NLS-1$
PreflightingLogger.error(errorMessage);
success = false;
throw new IOException(errorMessage);
}
if (success && !dirName.equals("")) //$NON-NLS-1$
{
PreflightingLogger.info("The directory " + dirName + "was successfully deleted."); //$NON-NLS-1$ //$NON-NLS-2$
}
return success;
}
/**
* Get the application version
*
* @param appData
* @return an integer representing the version
*/
public static Integer getApplicationVersionCode(XMLElement manifest)
{
Integer versionCode = 0;
if (manifest != null)
{
Document doc = manifest.getDocument();
if (doc != null)
{
NodeList manifestList = doc.getElementsByTagName("manifest"); //$NON-NLS-1$
org.w3c.dom.Element manifestElement = (org.w3c.dom.Element) manifestList.item(0);
if (manifestElement != null)
{
String strVersion = manifestElement.getAttribute("android:versionCode"); //$NON-NLS-1$
try
{
versionCode = Integer.parseInt(strVersion);
}
catch (NumberFormatException nfe)
{
// do nothing
}
}
}
}
return versionCode;
}
/**
* Extract all needed information from a CompilationUnit and fill the
* internal source file model (SourceFileElement). Fill all invoked Methods
* and resource constants
*
* @param javaCompilationUnit
* JDT model of a .java source file.
* @param parent
* SourceFolderElement, the source folder model containing this
* compilation unit.
* @return
*/
public static SourceFileElement readFromJava(final CompilationUnit javaCompilationUnit,
Element parent)
{
File file = (File) javaCompilationUnit.getProperty(JAVA_FILE_PROPERTY);
final SourceFileElement sourceFileElement = new SourceFileElement(file, parent);
/* Fill type information */
Object type = javaCompilationUnit.types().get(0); /*
* Grab the class
* declaration
*/
if (type instanceof TypeDeclaration)
{
TypeDeclaration typeDeclaration = (TypeDeclaration) type;
ITypeBinding superClassType = typeDeclaration.resolveBinding().getSuperclass();
String superClass = superClassType != null ? superClassType.getQualifiedName() : null;
sourceFileElement.setSuperclassName(superClass);
String typeFullName = typeDeclaration.resolveBinding().getQualifiedName();
sourceFileElement.setClassFullPath(typeFullName);
}
javaCompilationUnit.accept(new ASTVisitor()
{
/*
* Visit method declaration, searching for instructions.
*/
@Override
public boolean visit(MethodDeclaration node)
{
// Fill Method information
Method method = new Method();
SimpleName name = node.getName();
method.setMethodName(name.getFullyQualifiedName());
int modifiers = node.getModifiers();
boolean methodType = isMethodVirtual(modifiers);
method.setStatic(ProjectUtils.isStatic(modifiers));
/*
* Extract information regarding method parameters
*/
List<SingleVariableDeclaration> parameters = node.parameters();
for (SingleVariableDeclaration param : parameters)
{
ITypeBinding typeBinding = param.getType().resolveBinding();
if (typeBinding != null)
{
String paramTypeName = typeBinding.getName();
method.getParameterTypes().add(paramTypeName);
}
}
method.setConstructor(node.isConstructor());
IMethodBinding binding = node.resolveBinding();
if (binding != null)
{
String returnTypeStr = binding.getReturnType().getName();
method.setReturnType(returnTypeStr);
}
Block body = node.getBody();
int lineNumber =
body != null ? javaCompilationUnit.getLineNumber(body.getStartPosition())
: javaCompilationUnit.getLineNumber(node.getStartPosition());
method.setLineNumber(lineNumber);
// Navigate through statements...
if (body != null)
{
analizeBody(javaCompilationUnit, method, body);
}
sourceFileElement.addMethod(getMethodTypeString(methodType), method);
return super.visit(node);
}
/*
* Visit field declaration, only for R.java file. Extracting
* declared constants.
*/
@Override
public boolean visit(FieldDeclaration node)
{
if (sourceFileElement.getName().equals("R.java"))
{
String typeName = node.getType().resolveBinding().getName();
int modifiers = node.getModifiers();
boolean isStatic = isStatic(modifiers);
Field field = new Field();
field.setType(typeName);
field.setStatic(isStatic);
field.setVisibility(getVisibility(modifiers));
field.setFinal(isFinal(modifiers));
if (isStatic)
{
List<VariableDeclarationFragment> fragments = node.fragments(); // TODO Verify what to do when
// there's more than one
// fragment... enum?
for (VariableDeclarationFragment fragment : fragments)
{
IVariableBinding binding = fragment.resolveBinding();
String name = binding.getName();
String declaringClassName = binding.getDeclaringClass().getName();
field.setName(declaringClassName + "." + name);
Expression initializer = fragment.getInitializer();
if (initializer != null)
{
if (initializer instanceof NumberLiteral)
{
NumberLiteral numberInitializer = (NumberLiteral) initializer;
String value = numberInitializer.getToken();
field.setValue(value);
}
}
}
sourceFileElement.getStaticFields().add(field);
}
}
return super.visit(node);
}
@Override
public boolean visit(QualifiedName node)
{
//visit to recognize R.string or R.layout usage
if ((node.getQualifier() != null) && node.getQualifier().isQualifiedName())
{
if (node.getQualifier().toString().equals(R_STRING))
{
sourceFileElement.getUsedStringConstants().add(node.getName().toString());
}
else if (node.getQualifier().toString().equals(R_LAYOUT))
{
sourceFileElement.getUsedLayoutConstants().add(node.getName().toString());
}
}
return super.visit(node);
}
});
return sourceFileElement;
}
/*
* All methods are virtual on Java, except those that are either private or
* final.
*/
private static boolean isMethodVirtual(int methodModifiers)
{
boolean isVirtual = (methodModifiers & (Modifier.PRIVATE | Modifier.FINAL)) == 0;
return isVirtual;
}
private static String getMethodTypeString(boolean isVirtual)
{
return isVirtual ? Method.VIRTUAL : Method.DIRECT;
}
/*
* Verify if modifiers flags contains the 'Static' bit on
*/
private static boolean isStatic(int modifiers)
{
return (modifiers & Modifier.STATIC) != 0;
}
/*
* Verify if modifiers flags contains the 'Final' bit on
*/
private static boolean isFinal(int modifiers)
{
return (modifiers & Modifier.FINAL) != 0;
}
/*
* Search on modifiers flags and retrieve the visibility keyword
* corresponding to the flag bit
*/
private static String getVisibility(int modifiers)
{
boolean isPublic = (modifiers & Modifier.PUBLIC) != 0;
boolean isPrivate = (modifiers & Modifier.PRIVATE) != 0;
boolean isProtected = (modifiers & Modifier.PROTECTED) != 0;
if (isPublic)
{
return ModifierKeyword.PUBLIC_KEYWORD.toString();
}
if (isPrivate)
{
return ModifierKeyword.PRIVATE_KEYWORD.toString();
}
if (isProtected)
{
return ModifierKeyword.PROTECTED_KEYWORD.toString();
}
return "";
}
/*
* Navigate in a Block and extract called methods and all R constants used
* as Method parameters.
*/
private static void analizeBody(final CompilationUnit javaCompilationUnit, final Method method,
Block body)
{
body.accept(new ASTVisitor()
{
@Override
public boolean visit(VariableDeclarationFragment node)
{
String varName = node.getName().getIdentifier();
ITypeBinding typeBinding = node.resolveBinding().getType();
String typeQualifiedName = typeBinding.getQualifiedName();
int modifiers = typeBinding.getModifiers();
boolean isFinal = isFinal(modifiers);
boolean isStatic = isStatic(modifiers);
String value = null;
Expression initializer = node.getInitializer();
if (initializer != null)
{
value = initializer.toString();
}
int lineNumber = javaCompilationUnit.getLineNumber(node.getStartPosition());
Variable variable = new Variable();
variable.setName(varName);
variable.setType(typeQualifiedName);
variable.setFinal(isFinal);
variable.setStatic(isStatic);
variable.setValue(value);
variable.setLineNumber(lineNumber);
method.addVariable(variable);
return super.visit(node);
}
@Override
public boolean visit(MethodInvocation node)
{
// Fill invoked method model.
MethodInvocation invoked = node;
IMethodBinding methodBinding = invoked.resolveMethodBinding();
if (methodBinding != null)
{
IMethodBinding methodDeclaration = methodBinding.getMethodDeclaration();
ITypeBinding declaringClass = methodDeclaration.getDeclaringClass();
String declaringClassName = "";
if (declaringClass != null)
{
declaringClassName = declaringClass.getQualifiedName();
}
String methodSimpleName = methodBinding.getName();
int lineNumber = javaCompilationUnit.getLineNumber(invoked.getStartPosition());
String returnType = methodBinding.getReturnType().getQualifiedName();
int methodModifiers = methodBinding.getModifiers();
boolean isVirtual = isMethodVirtual(methodModifiers);
String sourceFileFullPath =
((File) javaCompilationUnit.getProperty(JAVA_FILE_PROPERTY))
.getAbsolutePath();
// Retrieve parameter types and look for R constants used
// within method arguments
List arguments = invoked.arguments();
List<String> parameterTypes = new ArrayList<String>(arguments.size());
List<String> parameterNames = new ArrayList<String>(arguments.size());
for (Object argument : arguments)
{
Expression argumentExpression = (Expression) argument;
ITypeBinding typeBinding = argumentExpression.resolveTypeBinding();
String parameterType = "";
String parameterName = "";
if (typeBinding != null)
{
parameterType = typeBinding.getName();
parameterName = argumentExpression.toString();
}
else
{
continue;
}
parameterTypes.add(parameterType);
parameterNames.add(parameterName);
if (argumentExpression instanceof QualifiedName) /*
* Can
* be a
* constant
* access
*/
{
QualifiedName qualifiedName = (QualifiedName) argumentExpression;
String fullQualifiedName =
qualifiedName.getQualifier().getFullyQualifiedName();
if (fullQualifiedName.startsWith("R.")) /*
* Accessing
* a R
* constant
*/
{
Constant constant = new Constant();
constant.setSourceFileFullPath(sourceFileFullPath);
constant.setLine(lineNumber);
constant.setType(parameterType);
Object constantExpressionValue =
qualifiedName.resolveConstantExpressionValue();
if (constantExpressionValue != null)
{
String constantValueHex = constantExpressionValue.toString();
if (constantExpressionValue instanceof Integer)
{
Integer integerValue = (Integer) constantExpressionValue;
constantValueHex = Integer.toHexString(integerValue);
}
constant.setValue(constantValueHex);
method.getInstructions().add(constant);
}
}
}
}
// Get the name of the object who owns the method being
// called.
Expression expression = invoked.getExpression();
String objectName = null;
if ((expression != null) && (expression instanceof SimpleName))
{
SimpleName simpleName = (SimpleName) expression;
objectName = simpleName.getIdentifier();
}
// Get the variable, if any, that received the method
// returned value
ASTNode parent = invoked.getParent();
String assignedVariable = null;
if (parent instanceof VariableDeclarationFragment)
{
VariableDeclarationFragment variableDeclarationFragment =
(VariableDeclarationFragment) parent;
assignedVariable = variableDeclarationFragment.getName().getIdentifier();
}
else if (parent instanceof Assignment)
{
Assignment assignment = (Assignment) parent;
Expression leftHandSide = assignment.getLeftHandSide();
if (leftHandSide instanceof SimpleName)
{
SimpleName name = (SimpleName) leftHandSide;
assignedVariable = name.getIdentifier();
}
}
// Fill Invoke object and add to the method model.
Invoke invoke = new Invoke();
invoke.setLine(lineNumber);
invoke.setMethodName(methodSimpleName);
invoke.setObjectName(objectName);
invoke.setType(getMethodTypeString(isVirtual));
invoke.setReturnType(returnType);
invoke.setClassCalled(declaringClassName);
invoke.setParameterTypes(parameterTypes);
invoke.setParameterNames(parameterNames);
invoke.setSourceFileFullPath(sourceFileFullPath);
invoke.setAssignedVariable(assignedVariable);
method.getInstructions().add(invoke);
}
return super.visit(node);
}
@Override
public boolean visit(VariableDeclarationExpression node)
{
return super.visit(node);
}
@Override
public boolean visit(Assignment node)
{
Expression lhs = node.getLeftHandSide();
String name = "";
if (lhs instanceof SimpleName)
{
SimpleName simpleName = (SimpleName) lhs;
name = simpleName.getIdentifier();
}
ITypeBinding typeBinding = lhs.resolveTypeBinding();
String type = typeBinding.getName();
// method.addAssigment(assignment);
// TODO Auto-generated method stub
return super.visit(node);
}
});
}
}
class XmlLineHandler extends DefaultHandler
{
private int index = 0;
private ArrayList<Node> nodesSequencialList = null;
private Locator locator;
public XmlLineHandler(ArrayList<Node> nodesSequencialList)
{
this.nodesSequencialList = nodesSequencialList;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException
{
setCurrentNodeLineNumber(nodesSequencialList.get(index), locator.getLineNumber());
index++;
}
@Override
public void setDocumentLocator(Locator locator)
{
this.locator = locator;
}
/*
* adds line number info to DOM node.
*/
private void setCurrentNodeLineNumber(Node node, Integer lineNumber)
{
node.setUserData(ProjectUtils.LINE_NUMBER_KEY, lineNumber, new EmptyDataHandler());
}
}
class EmptyDataHandler implements UserDataHandler
{
public void handle(short operation, String key, Object data, Node src, Node dst)
{
// Do nothing
}
}