com.android.sdklib.internal.project.ProjectCreator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sdklib Show documentation
Show all versions of sdklib Show documentation
A library to parse and download the Android SDK.
/*
* Copyright (C) 2007 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.android.sdklib.internal.project;
import com.android.SdkConstants;
import com.android.io.FileWrapper;
import com.android.io.FolderWrapper;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
import com.android.utils.ILogger;
import com.android.xml.AndroidManifest;
import com.android.xml.AndroidXPathFactory;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
/**
* Creates the basic files needed to get an Android project up and running.
*/
public class ProjectCreator {
/** Version of the build.xml. Stored in version-tag */
private static final int MIN_BUILD_VERSION_TAG = 1;
/** Package path substitution string used in template files, i.e. "PACKAGE_PATH" */
private static final String PH_PACKAGE_PATH = "PACKAGE_PATH";
/** Package name substitution string used in template files, i.e. "PACKAGE" */
private static final String PH_PACKAGE = "PACKAGE";
/** Activity name substitution string used in template files, i.e. "ACTIVITY_NAME".
* @deprecated This is only used for older templates. For new ones see
* {@link #PH_ACTIVITY_ENTRY_NAME}, and {@link #PH_ACTIVITY_CLASS_NAME}. */
@Deprecated
private static final String PH_ACTIVITY_NAME = "ACTIVITY_NAME";
/** Activity name substitution string used in manifest templates, i.e. "ACTIVITY_ENTRY_NAME".*/
private static final String PH_ACTIVITY_ENTRY_NAME = "ACTIVITY_ENTRY_NAME";
/** Activity name substitution string used in class templates, i.e. "ACTIVITY_CLASS_NAME".*/
private static final String PH_ACTIVITY_CLASS_NAME = "ACTIVITY_CLASS_NAME";
/** Activity FQ-name substitution string used in class templates, i.e. "ACTIVITY_FQ_NAME".*/
private static final String PH_ACTIVITY_FQ_NAME = "ACTIVITY_FQ_NAME";
/** Original Activity class name substitution string used in class templates, i.e.
* "ACTIVITY_TESTED_CLASS_NAME".*/
private static final String PH_ACTIVITY_TESTED_CLASS_NAME = "ACTIVITY_TESTED_CLASS_NAME";
/** Project name substitution string used in template files, i.e. "PROJECT_NAME". */
public static final String PH_PROJECT_NAME = "PROJECT_NAME";
/** Application icon substitution string used in the manifest template */
private static final String PH_ICON = "ICON";
/** Version tag name substitution string used in template files, i.e. "VERSION_TAG". */
private static final String PH_VERSION_TAG = "VERSION_TAG";
/** Target name substitution string used in template files, i.e. "TARGET". */
private static final String PH_TARGET = "TARGET";
/** Gradle plugin substitution string used in the build.gradle template */
private static final String PH_PLUGIN = "PLUGIN";
/** Gradle artifact version substitution string used in the build.gradle template */
private static final String PH_ARTIFACT_VERSION = "ARTIFACT_VERSION";
/** Build tool revision substitution string used in the build.gradle template */
private static final String PH_BUILD_TOOL_REV = "BUILD_TOOL_REV";
/** The xpath to find a project name in an Ant build file. */
private static final String XPATH_PROJECT_NAME = "/project/@name";
/** Pattern for characters accepted in a project name. Since this will be used as a
* directory name, we're being a bit conservative on purpose: dot and space cannot be used. */
public static final Pattern RE_PROJECT_NAME = Pattern.compile("[a-zA-Z0-9_]+");
/** List of valid characters for a project name. Used for display purposes. */
public static final String CHARS_PROJECT_NAME = "a-z A-Z 0-9 _";
/** Pattern for characters accepted in a package name. A package is list of Java identifier
* separated by a dot. We need to have at least one dot (e.g. a two-level package name).
* A Java identifier cannot start by a digit. */
public static final Pattern RE_PACKAGE_NAME =
Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+");
/** List of valid characters for a project name. Used for display purposes. */
public static final String CHARS_PACKAGE_NAME = "a-z A-Z 0-9 _";
/** Pattern for characters accepted in an activity name, which is a Java identifier. */
public static final Pattern RE_ACTIVITY_NAME =
Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*");
/** List of valid characters for a project name. Used for display purposes. */
public static final String CHARS_ACTIVITY_NAME = "a-z A-Z 0-9 _";
/** Gradle plugin to use with standard projects */
private static final String PLUGIN_PROJECT = "android";
/** Gradle plugin to use with library projects */
private static final String PLUGIN_LIB_PROJECT = "android-library";
public enum OutputLevel {
/** Silent mode. Project creation will only display errors. */
SILENT,
/** Normal mode. Project creation will display what's being done, display
* error but not warnings. */
NORMAL,
/** Verbose mode. Project creation will display what's being done, errors and warnings. */
VERBOSE
}
/**
* Exception thrown when a project creation fails, typically because a template
* file cannot be written.
*/
private static class ProjectCreateException extends Exception {
/** default UID. This will not be serialized anyway. */
private static final long serialVersionUID = 1L;
@SuppressWarnings("unused")
ProjectCreateException(String message) {
super(message);
}
ProjectCreateException(Throwable t, String format, Object... args) {
super(format != null ? String.format(format, args) : format, t);
}
ProjectCreateException(String format, Object... args) {
super(String.format(format, args));
}
}
/** The {@link OutputLevel} verbosity. */
private final OutputLevel mLevel;
/** Logger for errors and output. Cannot be null. */
private final ILogger mLog;
/** The OS path of the SDK folder. */
private final String mSdkFolder;
/** The {@link SdkManager} instance. */
private final SdkManager mSdkManager;
/**
* Helper class to create android projects.
*
* @param sdkManager The {@link SdkManager} instance.
* @param sdkFolder The OS path of the SDK folder.
* @param level The {@link OutputLevel} verbosity.
* @param log Logger for errors and output. Cannot be null.
*/
public ProjectCreator(SdkManager sdkManager, String sdkFolder, OutputLevel level, ILogger log) {
mSdkManager = sdkManager;
mSdkFolder = sdkFolder;
mLevel = level;
mLog = log;
}
/**
* Creates a new (ant) project.
*
* The caller should have already checked and sanitized the parameters.
*
* @param folderPath the folder of the project to create.
* @param projectName the name of the project. The name must match the
* {@link #RE_PROJECT_NAME} regex.
* @param packageName the package of the project. The name must match the
* {@link #RE_PACKAGE_NAME} regex.
* @param activityEntry the activity of the project as it will appear in the manifest. Can be
* null if no activity should be created. The name must match the
* {@link #RE_ACTIVITY_NAME} regex.
* @param target the project target.
* @param library whether the project is a library.
* @param pathToMainProject if non-null the project will be setup to test a main project
* located at the given path.
*/
public void createProject(String folderPath, String projectName,
String packageName, String activityEntry, IAndroidTarget target, boolean library,
String pathToMainProject) {
// create project folder if it does not exist
File projectFolder = checkNewProjectLocation(folderPath);
if (projectFolder == null) {
return;
}
try {
boolean isTestProject = pathToMainProject != null;
// first create the project properties.
// location of the SDK goes in localProperty
ProjectPropertiesWorkingCopy localProperties = ProjectProperties.create(folderPath,
PropertyType.LOCAL);
localProperties.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
localProperties.save();
// target goes in project properties
ProjectPropertiesWorkingCopy projectProperties = ProjectProperties.create(folderPath,
PropertyType.PROJECT);
projectProperties.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString());
if (library) {
projectProperties.setProperty(ProjectProperties.PROPERTY_LIBRARY, "true");
}
projectProperties.save();
// create a ant.properties file with just the application package
ProjectPropertiesWorkingCopy antProperties = ProjectProperties.create(folderPath,
PropertyType.ANT);
if (isTestProject) {
antProperties.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT,
pathToMainProject);
}
antProperties.save();
// create the map for place-holders of values to replace in the templates
final HashMap keywords = new HashMap();
// create the required folders.
// compute src folder path
final String packagePath =
stripString(packageName.replace(".", File.separator),
File.separatorChar);
// put this path in the place-holder map for project files that needs to list
// files manually.
keywords.put(PH_PACKAGE_PATH, packagePath);
keywords.put(PH_PACKAGE, packageName);
keywords.put(PH_VERSION_TAG, Integer.toString(MIN_BUILD_VERSION_TAG));
// compute some activity related information
String fqActivityName = null, activityPath = null, activityClassName = null;
String originalActivityEntry = activityEntry;
String originalActivityClassName = null;
if (activityEntry != null) {
if (isTestProject) {
// append Test so that it doesn't collide with the main project activity.
activityEntry += "Test";
// get the classname from the original activity entry.
int pos = originalActivityEntry.lastIndexOf('.');
if (pos != -1) {
originalActivityClassName = originalActivityEntry.substring(pos + 1);
} else {
originalActivityClassName = originalActivityEntry;
}
}
// get the fully qualified name of the activity
fqActivityName = AndroidManifest.combinePackageAndClassName(packageName,
activityEntry);
// get the activity path (replace the . to /)
activityPath = stripString(fqActivityName.replace(".", File.separator),
File.separatorChar);
// remove the last segment, so that we only have the path to the activity, but
// not the activity filename itself.
activityPath = activityPath.substring(0,
activityPath.lastIndexOf(File.separatorChar));
// finally, get the class name for the activity
activityClassName = fqActivityName.substring(fqActivityName.lastIndexOf('.') + 1);
}
// at this point we have the following for the activity:
// activityEntry: this is the manifest entry. For instance .MyActivity
// fqActivityName: full-qualified class name: com.foo.MyActivity
// activityClassName: only the classname: MyActivity
// originalActivityClassName: the classname of the activity being tested (if applicable)
// Add whatever activity info is needed in the place-holder map.
// Older templates only expect ACTIVITY_NAME to be the same (and unmodified for tests).
if (target.getVersion().getApiLevel() < 4) { // legacy
if (originalActivityEntry != null) {
keywords.put(PH_ACTIVITY_NAME, originalActivityEntry);
}
} else {
// newer templates make a difference between the manifest entries, classnames,
// as well as the main and test classes.
if (activityEntry != null) {
keywords.put(PH_ACTIVITY_ENTRY_NAME, activityEntry);
keywords.put(PH_ACTIVITY_CLASS_NAME, activityClassName);
keywords.put(PH_ACTIVITY_FQ_NAME, fqActivityName);
if (originalActivityClassName != null) {
keywords.put(PH_ACTIVITY_TESTED_CLASS_NAME, originalActivityClassName);
}
}
}
// Take the project name from the command line if there's one
if (projectName != null) {
keywords.put(PH_PROJECT_NAME, projectName);
} else {
if (activityClassName != null) {
// Use the activity class name as project name
keywords.put(PH_PROJECT_NAME, activityClassName);
} else {
// We need a project name. Just pick up the basename of the project
// directory.
projectName = projectFolder.getName();
keywords.put(PH_PROJECT_NAME, projectName);
}
}
// create the source folder for the activity
if (activityClassName != null) {
String srcActivityFolderPath =
SdkConstants.FD_SOURCES + File.separator + activityPath;
File sourceFolder = createDirs(projectFolder, srcActivityFolderPath);
String javaTemplate = isTestProject ? "java_tests_file.template"
: "java_file.template";
String activityFileName = activityClassName + ".java";
installTargetTemplate(javaTemplate, new File(sourceFolder, activityFileName),
keywords, target);
} else {
// we should at least create 'src'
createDirs(projectFolder, SdkConstants.FD_SOURCES);
}
// create other useful folders
File resourceFolder = createDirs(projectFolder, SdkConstants.FD_RESOURCES);
createDirs(projectFolder, SdkConstants.FD_OUTPUT);
createDirs(projectFolder, SdkConstants.FD_NATIVE_LIBS);
if (isTestProject == false) {
/* Make res files only for non test projects */
File valueFolder = createDirs(resourceFolder, SdkConstants.FD_RES_VALUES);
installTargetTemplate("strings.template", new File(valueFolder, "strings.xml"),
keywords, target);
File layoutFolder = createDirs(resourceFolder, SdkConstants.FD_RES_LAYOUT);
installTargetTemplate("layout.template", new File(layoutFolder, "main.xml"),
keywords, target);
// create the icons
if (installIcons(resourceFolder, target)) {
keywords.put(PH_ICON, "android:icon=\"@drawable/ic_launcher\"");
} else {
keywords.put(PH_ICON, "");
}
}
/* Make AndroidManifest.xml and build.xml files */
String manifestTemplate = "AndroidManifest.template";
if (isTestProject) {
manifestTemplate = "AndroidManifest.tests.template";
}
installTargetTemplate(manifestTemplate,
new File(projectFolder, SdkConstants.FN_ANDROID_MANIFEST_XML),
keywords, target);
installTemplate("build.template",
new File(projectFolder, SdkConstants.FN_BUILD_XML),
keywords);
// install the proguard config file.
installTemplate(SdkConstants.FN_PROJECT_PROGUARD_FILE,
new File(projectFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
null /*keywords*/);
} catch (Exception e) {
mLog.error(e, null);
}
}
/**
* Creates a new (gradle) project.
*
* The caller should have already checked and sanitized the parameters.
*
* @param folderPath the folder of the project to create.
* @param projectName the name of the project. The name must match the
* {@link #RE_PROJECT_NAME} regex.
* @param packageName the package of the project. The name must match the
* {@link #RE_PACKAGE_NAME} regex.
* @param activityEntry the activity of the project as it will appear in the manifest. Can be
* null if no activity should be created. The name must match the
* {@link #RE_ACTIVITY_NAME} regex.
* @param target the project target.
* @param library whether the project is a library.
* @param artifactVersion the version of the gradle artifact in maven.
*/
public void createGradleProject(String folderPath, String projectName,
String packageName, String activityEntry, IAndroidTarget target, boolean library,
String artifactVersion) {
// create project folder if it does not exist
File projectFolder = checkNewProjectLocation(folderPath);
if (projectFolder == null) {
return;
}
try {
// first create the project properties.
// location of the SDK goes in localProperty
ProjectPropertiesWorkingCopy localProperties = ProjectProperties.create(folderPath,
PropertyType.LOCAL);
localProperties.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
localProperties.save();
// create the map for place-holders of values to replace in the templates
final HashMap keywords = new HashMap();
final HashMap testKeywords = new HashMap();
// create the required folders.
// compute src folder path
final String packagePath =
stripString(packageName.replace(".", File.separator),
File.separatorChar);
// put this path in the place-holder map for project files that needs to list
// files manually.
keywords.put(PH_PACKAGE_PATH, packagePath);
keywords.put(PH_PACKAGE, packageName);
testKeywords.put(PH_PACKAGE_PATH, packagePath);
testKeywords.put(PH_PACKAGE, packageName);
// compute some activity related information
String activityPath = null, activityClassName = null;
String testActivityPath = null, testActivityClassName = null;
if (activityEntry != null) {
// get the fully qualified name of the activity
String fqActivityName = AndroidManifest.combinePackageAndClassName(packageName,
activityEntry);
// get the activity path (replace the . to /)
activityPath = stripString(fqActivityName.replace(".", File.separator),
File.separatorChar);
// remove the last segment, so that we only have the path to the activity, but
// not the activity filename itself.
activityPath = activityPath.substring(0,
activityPath.lastIndexOf(File.separatorChar));
// finally, get the class name for the activity
activityClassName = fqActivityName.substring(fqActivityName.lastIndexOf('.') + 1);
// at this point we have the following for the activity:
// activityEntry: this is the manifest entry. For instance .MyActivity
// fqActivityName: full-qualified class name: com.foo.MyActivity
// activityClassName: only the classname: MyActivity
// append Test so that it doesn't collide with the main project activity.
String testActivityEntry = activityEntry + "Test";
// get the fully qualified name of the test
String testFqActivityName = AndroidManifest.combinePackageAndClassName(packageName,
testActivityEntry);
// get the test path (replace the . to /)
testActivityPath = stripString(testFqActivityName.replace(".", File.separator),
File.separatorChar);
// remove the last segment, so that we only have the path to the test, but
// not the test filename itself.
testActivityPath = testActivityPath.substring(0,
testActivityPath.lastIndexOf(File.separatorChar));
// finally, get the class name for the test
testActivityClassName = testFqActivityName.substring(testFqActivityName.lastIndexOf('.') + 1);
// at this point we have the following for the test:
// testActivityEntry: this is the manifest entry. For instance .MyActivityTest
// testFqActivityName: full-qualified class name: com.foo.MyActivityTest
// testActivityClassName: only the classname: MyActivityTest
// Add whatever activity info is needed in the place-holder map.
// Older templates only expect ACTIVITY_NAME to be the same (and unmodified for tests).
if (target.getVersion().getApiLevel() < 4) { // legacy
keywords.put(PH_ACTIVITY_NAME, activityEntry);
testKeywords.put(PH_ACTIVITY_NAME, activityEntry);
} else {
// newer templates make a difference between the manifest entries, classnames,
// as well as the main and test classes.
keywords.put(PH_ACTIVITY_ENTRY_NAME, activityEntry);
keywords.put(PH_ACTIVITY_CLASS_NAME, activityClassName);
keywords.put(PH_ACTIVITY_FQ_NAME, fqActivityName);
testKeywords.put(PH_ACTIVITY_ENTRY_NAME, testActivityEntry);
testKeywords.put(PH_ACTIVITY_CLASS_NAME, testActivityClassName);
testKeywords.put(PH_ACTIVITY_FQ_NAME, testFqActivityName);
testKeywords.put(PH_ACTIVITY_TESTED_CLASS_NAME, activityClassName);
}
}
// Take the project name from the command line if there's one
if (projectName != null) {
keywords.put(PH_PROJECT_NAME, projectName);
testKeywords.put(PH_PROJECT_NAME, projectName);
} else {
// Use the activity class name as project name, else just
// pick up the basename of the project directory.
keywords.put(PH_PROJECT_NAME, (activityClassName != null) ?
activityClassName : projectFolder.getName());
testKeywords.put(PH_PROJECT_NAME, (testActivityClassName != null) ?
testActivityClassName : projectFolder.getName());
}
String srcMainPath = SdkConstants.FD_SOURCES + File.separator +
SdkConstants.FD_MAIN;
String srcTestPath = SdkConstants.FD_SOURCES + File.separator +
SdkConstants.FD_TEST;
// create the source folders for the activity
String srcMainCodePath = srcMainPath + File.separator + SdkConstants.FD_JAVA;
createDirs(projectFolder, srcMainCodePath);
if (activityClassName != null) {
String srcActivityFolderPath =
srcMainCodePath + File.separator + activityPath;
File sourceFolder = createDirs(projectFolder, srcActivityFolderPath);
String activityFileName = activityClassName + ".java";
installTargetTemplate("java_file.template",
new File(sourceFolder, activityFileName), keywords, target);
}
// create the source folders for the test
String srcTestCodePath = srcTestPath + File.separator + SdkConstants.FD_JAVA;
createDirs(projectFolder, srcTestCodePath);
if (testActivityClassName != null) {
String srcActivityFolderPath =
srcTestCodePath + File.separator + testActivityPath;
File sourceFolder = createDirs(projectFolder, srcActivityFolderPath);
String activityFileName = testActivityClassName + ".java";
installTargetTemplate("java_tests_file.template",
new File(sourceFolder, activityFileName), testKeywords, target);
}
// create the res xml files
String srcMainResPath = srcMainPath + File.separator + SdkConstants.FD_RES;
File resourceFolder = createDirs(projectFolder, srcMainResPath);
File valueFolder = createDirs(resourceFolder, SdkConstants.FD_RES_VALUES);
installTargetTemplate("strings.template", new File(valueFolder, "strings.xml"),
keywords, target);
File layoutFolder = createDirs(resourceFolder, SdkConstants.FD_RES_LAYOUT);
installTargetTemplate("layout.template", new File(layoutFolder, "main.xml"),
keywords, target);
// create the icons
if (installIcons(resourceFolder, target)) {
keywords.put(PH_ICON, "android:icon=\"@drawable/ic_launcher\"");
} else {
keywords.put(PH_ICON, "");
}
// Create the AndroidManifest.xml and build.gradle files
installTargetTemplate("AndroidManifest.template",
new File(projectFolder, srcMainPath + File.separator +
SdkConstants.FN_ANDROID_MANIFEST_XML),
keywords, target);
String buildToolRev = mSdkManager.getLatestBuildTool().getRevision().toString();
keywords.put(PH_BUILD_TOOL_REV, buildToolRev);
keywords.put(PH_ARTIFACT_VERSION, artifactVersion);
keywords.put(PH_TARGET, target.hashString());
keywords.put(PH_PLUGIN, (library) ? PLUGIN_LIB_PROJECT : PLUGIN_PROJECT);
installTemplate("build_gradle.template",
new File(projectFolder, SdkConstants.FN_BUILD_GRADLE),
keywords);
// Create the gradle wrapper files
createDirs(projectFolder, "gradle/wrapper");
installGradleWrapperFile("gradle/wrapper/gradle-wrapper.jar", projectFolder);
installGradleWrapperFile("gradle/wrapper/gradle-wrapper.properties", projectFolder);
installGradleWrapperFile("gradlew.bat", projectFolder);
installGradleWrapperFile("gradlew", projectFolder);
new File(projectFolder, "gradlew").setExecutable(true, false);
} catch (Exception e) {
mLog.error(e, null);
}
}
private File checkNewProjectLocation(String folderPath) {
File projectFolder = new File(folderPath);
if (!projectFolder.exists()) {
boolean created = false;
Throwable t = null;
try {
created = projectFolder.mkdirs();
} catch (Exception e) {
t = e;
}
if (created) {
println("Created project directory: %1$s", projectFolder);
} else {
mLog.error(t, "Could not create directory: %1$s", projectFolder);
return null;
}
} else {
Exception e = null;
String error = null;
try {
String[] content = projectFolder.list();
if (content == null) {
error = "Project folder '%1$s' is not a directory.";
} else if (content.length != 0) {
error = "Project folder '%1$s' is not empty. Please consider using '%2$s update' instead.";
}
} catch (Exception e1) {
e = e1;
}
if (e != null || error != null) {
mLog.error(e, error, projectFolder, SdkConstants.androidCmdName());
}
}
return projectFolder;
}
/**
* Updates an existing project.
*
* Workflow:
*
* - Check AndroidManifest.xml is present (required)
*
- Check if there's a legacy properties file and convert it
*
- Check there's a project.properties with a target *or* --target was specified
*
- Update default.prop if --target was specified
*
- Refresh/create "sdk" in local.properties
*
- Build.xml: create if not present or if version-tag is found or not. version-tag:custom
* prevent any overwrite. version-tag:[integer] will override. missing version-tag will query
* the dev.
*
*
* @param folderPath the folder of the project to update. This folder must exist.
* @param target the project target. Can be null.
* @param projectName The project name from --name. Can be null.
* @param libraryPath the path to a library to add to the references. Can be null.
* @return true if the project was successfully updated.
*/
@SuppressWarnings("deprecation")
public boolean updateProject(String folderPath, IAndroidTarget target, String projectName,
String libraryPath) {
// since this is an update, check the folder does point to a project
FileWrapper androidManifest = checkProjectFolder(folderPath,
SdkConstants.FN_ANDROID_MANIFEST_XML);
if (androidManifest == null) {
return false;
}
// get the parent folder.
FolderWrapper projectFolder = (FolderWrapper) androidManifest.getParentFolder();
boolean hasProguard = false;
// Check there's a project.properties with a target *or* --target was specified
IAndroidTarget originalTarget = null;
boolean writeProjectProp = false;
ProjectProperties props = ProjectProperties.load(projectFolder, PropertyType.PROJECT);
if (props == null) {
// no project.properties, try to load default.properties
props = ProjectProperties.load(projectFolder, PropertyType.LEGACY_DEFAULT);
writeProjectProp = true;
}
if (props != null) {
String targetHash = props.getProperty(ProjectProperties.PROPERTY_TARGET);
originalTarget = mSdkManager.getTargetFromHashString(targetHash);
// if the project is already setup with proguard, we won't copy the proguard config.
hasProguard = props.getProperty(ProjectProperties.PROPERTY_PROGUARD_CONFIG) != null;
}
if (originalTarget == null && target == null) {
mLog.error(null,
"The project either has no target set or the target is invalid.\n" +
"Please provide a --target to the '%1$s update' command.",
SdkConstants.androidCmdName());
return false;
}
boolean saveProjectProps = false;
ProjectPropertiesWorkingCopy propsWC = null;
// Update default.prop if --target was specified
if (target != null || writeProjectProp) {
// we already attempted to load the file earlier, if that failed, create it.
if (props == null) {
propsWC = ProjectProperties.create(projectFolder, PropertyType.PROJECT);
} else {
propsWC = props.makeWorkingCopy(PropertyType.PROJECT);
}
// set or replace the target
if (target != null) {
propsWC.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString());
}
saveProjectProps = true;
}
if (libraryPath != null) {
// At this point, the default properties already exists, either because they were
// already there or because they were created with a new target
if (propsWC == null) {
assert props != null;
propsWC = props.makeWorkingCopy();
}
// check the reference is valid
File libProject = new File(libraryPath);
String resolvedPath;
if (libProject.isAbsolute() == false) {
libProject = new File(projectFolder, libraryPath);
try {
resolvedPath = libProject.getCanonicalPath();
} catch (IOException e) {
mLog.error(e, "Unable to resolve path to library project: %1$s", libraryPath);
return false;
}
} else {
resolvedPath = libProject.getAbsolutePath();
}
println("Resolved location of library project to: %1$s", resolvedPath);
// check the lib project exists
if (checkProjectFolder(resolvedPath, SdkConstants.FN_ANDROID_MANIFEST_XML) == null) {
mLog.error(null, "No Android Manifest at: %1$s", resolvedPath);
return false;
}
// look for other references to figure out the index
int index = 1;
while (true) {
String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index);
assert props != null;
if (props == null) {
// This should not happen yet SDK bug 20535 says it can, not sure how.
break;
}
String ref = props.getProperty(propName);
if (ref == null) {
break;
} else {
index++;
}
}
String propName = ProjectProperties.PROPERTY_LIB_REF + Integer.toString(index);
propsWC.setProperty(propName, libraryPath);
saveProjectProps = true;
}
// save the default props if needed.
if (saveProjectProps) {
try {
assert propsWC != null;
propsWC.save();
if (writeProjectProp) {
println("Updated and renamed %1$s to %2$s",
PropertyType.LEGACY_DEFAULT.getFilename(),
PropertyType.PROJECT.getFilename());
} else {
println("Updated %1$s", PropertyType.PROJECT.getFilename());
}
} catch (Exception e) {
mLog.error(e, "Failed to write %1$s file in '%2$s'",
PropertyType.PROJECT.getFilename(),
folderPath);
return false;
}
if (writeProjectProp) {
// need to delete the default prop file.
ProjectProperties.delete(projectFolder, PropertyType.LEGACY_DEFAULT);
}
}
// Refresh/create "sdk" in local.properties
// because the file may already exists and contain other values (like apk config),
// we first try to load it.
props = ProjectProperties.load(projectFolder, PropertyType.LOCAL);
if (props == null) {
propsWC = ProjectProperties.create(projectFolder, PropertyType.LOCAL);
} else {
propsWC = props.makeWorkingCopy();
}
// set or replace the sdk location.
propsWC.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
try {
propsWC.save();
println("Updated %1$s", PropertyType.LOCAL.getFilename());
} catch (Exception e) {
mLog.error(e, "Failed to write %1$s file in '%2$s'",
PropertyType.LOCAL.getFilename(),
folderPath);
return false;
}
// legacy: check if build.properties must be renamed to ant.properties.
props = ProjectProperties.load(projectFolder, PropertyType.ANT);
if (props == null) {
props = ProjectProperties.load(projectFolder, PropertyType.LEGACY_BUILD);
if (props != null) {
try {
// get a working copy with the new property type
propsWC = props.makeWorkingCopy(PropertyType.ANT);
propsWC.save();
// delete the old file
ProjectProperties.delete(projectFolder, PropertyType.LEGACY_BUILD);
println("Renamed %1$s to %2$s",
PropertyType.LEGACY_BUILD.getFilename(),
PropertyType.ANT.getFilename());
} catch (Exception e) {
mLog.error(e, "Failed to write %1$s file in '%2$s'",
PropertyType.ANT.getFilename(),
folderPath);
return false;
}
}
}
// Build.xml: create if not present or no in it
File buildXml = new File(projectFolder, SdkConstants.FN_BUILD_XML);
boolean needsBuildXml = projectName != null || !buildXml.exists();
// if it seems there's no need for a new build.xml, look for inside the file
// to try to detect old ones that may need updating.
if (!needsBuildXml) {
// we are looking for version-tag: followed by either an integer or "custom".
if (checkFileContainsRegexp(buildXml, "version-tag:\\s*custom") != null) { //$NON-NLS-1$
println("%1$s: Found version-tag: custom. File will not be updated.",
SdkConstants.FN_BUILD_XML);
} else {
Matcher m = checkFileContainsRegexp(buildXml, "version-tag:\\s*(\\d+)"); //$NON-NLS-1$
if (m == null) {
println("----------\n" +
"%1$s: Failed to find version-tag string. File must be updated.\n" +
"In order to not erase potential customizations, the file will not be automatically regenerated.\n" +
"If no changes have been made to the file, delete it manually and run the command again.\n" +
"If you have made customizations to the build process, the file must be manually updated.\n" +
"It is recommended to:\n" +
"\t* Copy current file to a safe location.\n" +
"\t* Delete original file.\n" +
"\t* Run command again to generate a new file.\n" +
"\t* Port customizations to the new file, by looking at the new rules file\n" +
"\t located at /tools/ant/build.xml\n" +
"\t* Update file to contain\n" +
"\t version-tag: custom\n" +
"\t to prevent file from being rewritten automatically by the SDK tools.\n" +
"----------\n",
SdkConstants.FN_BUILD_XML);
} else {
String versionStr = m.group(1);
if (versionStr != null) {
// can't fail due to regexp above.
int version = Integer.parseInt(versionStr);
if (version < MIN_BUILD_VERSION_TAG) {
println("%1$s: Found version-tag: %2$d. Expected version-tag: %3$d: file must be updated.",
SdkConstants.FN_BUILD_XML, version, MIN_BUILD_VERSION_TAG);
needsBuildXml = true;
}
}
}
}
}
if (needsBuildXml) {
// create the map for place-holders of values to replace in the templates
final HashMap keywords = new HashMap();
// put the current version-tag value
keywords.put(PH_VERSION_TAG, Integer.toString(MIN_BUILD_VERSION_TAG));
// if there was no project name on the command line, figure one out.
if (projectName == null) {
// otherwise, take it from the existing build.xml if it exists already.
if (buildXml.exists()) {
try {
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
projectName = xpath.evaluate(XPATH_PROJECT_NAME,
new InputSource(new FileInputStream(buildXml)));
} catch (XPathExpressionException e) {
// this is ok since we're going to recreate the file.
mLog.error(e, "Unable to find existing project name from %1$s",
SdkConstants.FN_BUILD_XML);
} catch (FileNotFoundException e) {
// can't happen since we check above.
}
}
// if the project is still null, then we find another way.
if (projectName == null) {
extractPackageFromManifest(androidManifest, keywords);
if (keywords.containsKey(PH_ACTIVITY_ENTRY_NAME)) {
String activity = keywords.get(PH_ACTIVITY_ENTRY_NAME);
// keep only the last segment if applicable
int pos = activity.lastIndexOf('.');
if (pos != -1) {
activity = activity.substring(pos + 1);
}
// Use the activity as project name
projectName = activity;
println("No project name specified, using Activity name '%1$s'.\n" +
"If you wish to change it, edit the first line of %2$s.",
activity, SdkConstants.FN_BUILD_XML);
} else {
// We need a project name. Just pick up the basename of the project
// directory.
File projectCanonicalFolder = projectFolder;
try {
projectCanonicalFolder = projectCanonicalFolder.getCanonicalFile();
} catch (IOException e) {
// ignore, keep going
}
// Use the folder name as project name
projectName = projectCanonicalFolder.getName();
println("No project name specified, using project folder name '%1$s'.\n" +
"If you wish to change it, edit the first line of %2$s.",
projectName, SdkConstants.FN_BUILD_XML);
}
}
}
// put the project name in the map for replacement during the template installation.
keywords.put(PH_PROJECT_NAME, projectName);
if (mLevel == OutputLevel.VERBOSE) {
println("Regenerating %1$s with project name %2$s",
SdkConstants.FN_BUILD_XML,
keywords.get(PH_PROJECT_NAME));
}
try {
installTemplate("build.template", buildXml, keywords);
} catch (ProjectCreateException e) {
mLog.error(e, null);
return false;
}
}
if (hasProguard == false) {
try {
installTemplate(SdkConstants.FN_PROJECT_PROGUARD_FILE,
// Write ProGuard config files with the extension .pro which
// is what is used in the ProGuard documentation and samples
new File(projectFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
null /*placeholderMap*/);
} catch (ProjectCreateException e) {
mLog.error(e, null);
return false;
}
}
return true;
}
/**
* Updates a test project with a new path to the main (tested) project.
* @param folderPath the path of the test project.
* @param pathToMainProject the path to the main project, relative to the test project.
*/
@SuppressWarnings("deprecation")
public void updateTestProject(final String folderPath, final String pathToMainProject,
final SdkManager sdkManager) {
// since this is an update, check the folder does point to a project
if (checkProjectFolder(folderPath, SdkConstants.FN_ANDROID_MANIFEST_XML) == null) {
return;
}
// check the path to the main project is valid.
File mainProject = new File(pathToMainProject);
String resolvedPath;
if (mainProject.isAbsolute() == false) {
mainProject = new File(folderPath, pathToMainProject);
try {
resolvedPath = mainProject.getCanonicalPath();
} catch (IOException e) {
mLog.error(e, "Unable to resolve path to main project: %1$s", pathToMainProject);
return;
}
} else {
resolvedPath = mainProject.getAbsolutePath();
}
println("Resolved location of main project to: %1$s", resolvedPath);
// check the main project exists
if (checkProjectFolder(resolvedPath, SdkConstants.FN_ANDROID_MANIFEST_XML) == null) {
mLog.error(null, "No Android Manifest at: %1$s", resolvedPath);
return;
}
// now get the target from the main project
ProjectProperties projectProp = ProjectProperties.load(resolvedPath, PropertyType.PROJECT);
if (projectProp == null) {
// legacy support for older file name.
projectProp = ProjectProperties.load(resolvedPath, PropertyType.LEGACY_DEFAULT);
if (projectProp == null) {
mLog.error(null, "No %1$s at: %2$s", PropertyType.PROJECT.getFilename(),
resolvedPath);
return;
}
}
String targetHash = projectProp.getProperty(ProjectProperties.PROPERTY_TARGET);
if (targetHash == null) {
mLog.error(null, "%1$s in the main project has no target property.",
PropertyType.PROJECT.getFilename());
return;
}
IAndroidTarget target = sdkManager.getTargetFromHashString(targetHash);
if (target == null) {
mLog.error(null, "Main project target %1$s is not a valid target.", targetHash);
return;
}
// update test-project does not support the --name parameter, therefore the project
// name should generally not be passed to updateProject().
// However if build.xml does not exist then updateProject() will recreate it. In this
// case we will need the project name.
// To do this, we look for the parent project name and add "test" to it.
// If the main project does not have a project name (yet), then the default behavior
// will be used (look for activity and then folder name)
String projectName = null;
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
File testBuildXml = new File(folderPath, SdkConstants.FN_BUILD_XML);
if (testBuildXml.isFile() == false) {
File mainBuildXml = new File(resolvedPath, SdkConstants.FN_BUILD_XML);
if (mainBuildXml.isFile()) {
try {
// get the name of the main project and add Test to it.
String mainProjectName = xpath.evaluate(XPATH_PROJECT_NAME,
new InputSource(new FileInputStream(mainBuildXml)));
projectName = mainProjectName + "Test";
} catch (XPathExpressionException e) {
// it's ok, updateProject() will figure out a name automatically.
// We do log the error though as the build.xml file may be broken.
mLog.warning("Failed to parse %1$s.\n" +
"File may not be valid. Consider running 'android update project' on the main project.",
mainBuildXml.getPath());
} catch (FileNotFoundException e) {
// should not happen since we check first.
}
}
}
// now update the project as if it's a normal project
if (updateProject(folderPath, target, projectName, null /*libraryPath*/) == false) {
// error message has already been displayed.
return;
}
// add the test project specific properties.
// At this point, we know build.prop has been renamed ant.prop
ProjectProperties antProps = ProjectProperties.load(folderPath, PropertyType.ANT);
ProjectPropertiesWorkingCopy antWorkingCopy;
if (antProps == null) {
antWorkingCopy = ProjectProperties.create(folderPath, PropertyType.ANT);
} else {
antWorkingCopy = antProps.makeWorkingCopy();
}
// set or replace the path to the main project
antWorkingCopy.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT, pathToMainProject);
try {
antWorkingCopy.save();
println("Updated %1$s", PropertyType.ANT.getFilename());
} catch (Exception e) {
mLog.error(e, "Failed to write %1$s file in '%2$s'",
PropertyType.ANT.getFilename(),
folderPath);
return;
}
}
/**
* Checks whether the give folderPath is a valid project folder, and returns
* a {@link FileWrapper} to the required file.
* This checks that the folder exists and contains an AndroidManifest.xml file in it.
* Any error are output using {@link #mLog}.
* @param folderPath the folder to check
* @param requiredFilename the file name of the file that's required.
* @return a {@link FileWrapper} to the AndroidManifest.xml file, or null otherwise.
*/
private FileWrapper checkProjectFolder(String folderPath, String requiredFilename) {
// project folder must exist and be a directory, since this is an update
FolderWrapper projectFolder = new FolderWrapper(folderPath);
if (!projectFolder.isDirectory()) {
mLog.error(null, "Project folder '%1$s' is not a valid directory.",
projectFolder);
return null;
}
// Check AndroidManifest.xml is present
FileWrapper requireFile = new FileWrapper(projectFolder, requiredFilename);
if (!requireFile.isFile()) {
mLog.error(null,
"%1$s is not a valid project (%2$s not found).",
folderPath, requiredFilename);
return null;
}
return requireFile;
}
/**
* Looks for a given regex in a file and returns the matcher if any line of the input file
* contains the requested regexp.
*
* @param file the file to search.
* @param regexp the regexp to search for.
*
* @return a Matcher or null if the regexp is not found.
*/
private Matcher checkFileContainsRegexp(File file, String regexp) {
Pattern p = Pattern.compile(regexp);
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(file));
String line;
while ((line = in.readLine()) != null) {
Matcher m = p.matcher(line);
if (m.find()) {
return m;
}
}
in.close();
} catch (Exception e) {
// ignore
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
}
return null;
}
/**
* Extracts a "full" package & activity name from an AndroidManifest.xml.
*
* The keywords dictionary is always filed the package name under the key {@link #PH_PACKAGE}.
* If an activity name can be found, it is filed under the key {@link #PH_ACTIVITY_ENTRY_NAME}.
* When no activity is found, this key is not created.
*
* @param manifestFile The AndroidManifest.xml file
* @param outKeywords Place where to put the out parameters: package and activity names.
* @return True if the package/activity was parsed and updated in the keyword dictionary.
*/
private boolean extractPackageFromManifest(File manifestFile,
Map outKeywords) {
try {
XPath xpath = AndroidXPathFactory.newXPath();
InputSource source = new InputSource(new FileReader(manifestFile));
String packageName = xpath.evaluate("/manifest/@package", source);
source = new InputSource(new FileReader(manifestFile));
// Select the "android:name" attribute of all nodes but only if they
// contain a sub-node with an "android:name" attribute which
// is 'android.intent.action.MAIN' and an with an
// "android:name" attribute which is 'android.intent.category.LAUNCHER'
String expression = String.format("/manifest/application/activity" +
"[intent-filter/action/@%1$s:name='android.intent.action.MAIN' and " +
"intent-filter/category/@%1$s:name='android.intent.category.LAUNCHER']" +
"/@%1$s:name", AndroidXPathFactory.DEFAULT_NS_PREFIX);
NodeList activityNames = (NodeList) xpath.evaluate(expression, source,
XPathConstants.NODESET);
// If we get here, both XPath expressions were valid so we're most likely dealing
// with an actual AndroidManifest.xml file. The nodes may not have the requested
// attributes though, if which case we should warn.
if (packageName == null || packageName.length() == 0) {
mLog.error(null,
"Missing in '%1$s'",
manifestFile.getName());
return false;
}
// Get the first activity that matched earlier. If there is no activity,
// activityName is set to an empty string and the generated "combined" name
// will be in the form "package." (with a dot at the end).
String activityName = "";
if (activityNames.getLength() > 0) {
activityName = activityNames.item(0).getNodeValue();
}
if (mLevel == OutputLevel.VERBOSE && activityNames.getLength() > 1) {
println("WARNING: There is more than one activity defined in '%1$s'.\n" +
"Only the first one will be used. If this is not appropriate, you need\n" +
"to specify one of these values manually instead:",
manifestFile.getName());
for (int i = 0; i < activityNames.getLength(); i++) {
String name = activityNames.item(i).getNodeValue();
name = combinePackageActivityNames(packageName, name);
println("- %1$s", name);
}
}
if (activityName.length() == 0) {
mLog.warning("Missing in '%2$s'.\n" +
"No activity will be generated.",
AndroidXPathFactory.DEFAULT_NS_PREFIX, manifestFile.getName());
} else {
outKeywords.put(PH_ACTIVITY_ENTRY_NAME, activityName);
}
outKeywords.put(PH_PACKAGE, packageName);
return true;
} catch (IOException e) {
mLog.error(e, "Failed to read %1$s", manifestFile.getName());
} catch (XPathExpressionException e) {
Throwable t = e.getCause();
mLog.error(t == null ? e : t,
"Failed to parse %1$s",
manifestFile.getName());
}
return false;
}
private String combinePackageActivityNames(String packageName, String activityName) {
// Activity Name can have 3 forms:
// - ".Name" means this is a class name in the given package name.
// The full FQCN is thus packageName + ".Name"
// - "Name" is an older variant of the former. Full FQCN is packageName + "." + "Name"
// - "com.blah.Name" is a full FQCN. Ignore packageName and use activityName as-is.
// To be valid, the package name should have at least two components. This is checked
// later during the creation of the build.xml file, so we just need to detect there's
// a dot but not at pos==0.
int pos = activityName.indexOf('.');
if (pos == 0) {
return packageName + activityName;
} else if (pos > 0) {
return activityName;
} else {
return packageName + "." + activityName;
}
}
/**
* Installs a new file that is based on a template file provided by a given target.
* Each match of each key from the place-holder map in the template will be replaced with its
* corresponding value in the created file.
*
* @param templateName the name of to the template file
* @param destFile the path to the destination file, relative to the project
* @param placeholderMap a map of (place-holder, value) to create the file from the template.
* @param target the Target of the project that will be providing the template.
* @throws ProjectCreateException
*/
private void installTargetTemplate(String templateName, File destFile,
Map placeholderMap, IAndroidTarget target)
throws ProjectCreateException {
// query the target for its template directory
String templateFolder = target.getPath(IAndroidTarget.TEMPLATES);
final String sourcePath = templateFolder + File.separator + templateName;
installFullPathTemplate(sourcePath, destFile, placeholderMap);
}
/**
* Installs a new file from the gradle wrapper template.
*
* @param templateName the name of the template file
* @param projectFolder the path to the project folder
* @throws ProjectCreateException
*/
public void installGradleWrapperFile(String templateName, File projectFolder)
throws ProjectCreateException {
String templateFolder = mSdkFolder + File.separator +
SdkConstants.OS_SDK_TOOLS_TEMPLATES_GRADLE_WRAPPER_FOLDER;
installBinaryFile(new File(templateFolder, templateName),
new File(projectFolder, templateName));
}
/**
* Installs a new file that is based on a template file provided by the tools folder.
* Each match of each key from the place-holder map in the template will be replaced with its
* corresponding value in the created file.
*
* @param templateName the name of to the template file
* @param destFile the path to the destination file, relative to the project
* @param placeholderMap a map of (place-holder, value) to create the file from the template.
* @throws ProjectCreateException
*/
public void installTemplate(String templateName, File destFile,
Map placeholderMap)
throws ProjectCreateException {
// query the target for its template directory
String templateFolder = mSdkFolder + File.separator + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
final String sourcePath = templateFolder + File.separator + templateName;
installFullPathTemplate(sourcePath, destFile, placeholderMap);
}
/**
* Installs a new file that is based on a template.
* Each match of each key from the place-holder map in the template will be replaced with its
* corresponding value in the created file.
*
* @param sourcePath the full path to the source template file
* @param destFile the destination file
* @param placeholderMap a map of (place-holder, value) to create the file from the template.
* @throws ProjectCreateException
*/
private void installFullPathTemplate(String sourcePath, File destFile,
Map placeholderMap) throws ProjectCreateException {
boolean existed = destFile.exists();
try {
BufferedWriter out = new BufferedWriter(new FileWriter(destFile));
BufferedReader in = new BufferedReader(new FileReader(sourcePath));
String line;
while ((line = in.readLine()) != null) {
if (placeholderMap != null) {
for (Map.Entry entry : placeholderMap.entrySet()) {
line = line.replace(entry.getKey(), entry.getValue());
}
}
out.write(line);
out.newLine();
}
out.close();
in.close();
} catch (Exception e) {
throw new ProjectCreateException(e, "Could not access %1$s: %2$s",
destFile, e.getMessage());
}
println("%1$s file %2$s",
existed ? "Updated" : "Added",
destFile);
}
/**
* Installs the project icons.
* @param resourceFolder the resource folder
* @param target the target of the project.
* @return true if any icon was installed.
*/
private boolean installIcons(File resourceFolder, IAndroidTarget target)
throws ProjectCreateException {
// query the target for its template directory
String templateFolder = target.getPath(IAndroidTarget.TEMPLATES);
boolean installedIcon = false;
installedIcon |= installIcon(templateFolder, "ic_launcher_xhdpi.png", resourceFolder,
"drawable-xhdpi");
installedIcon |= installIcon(templateFolder, "ic_launcher_hdpi.png", resourceFolder,
"drawable-hdpi");
installedIcon |= installIcon(templateFolder, "ic_launcher_mdpi.png", resourceFolder,
"drawable-mdpi");
installedIcon |= installIcon(templateFolder, "ic_launcher_ldpi.png", resourceFolder,
"drawable-ldpi");
return installedIcon;
}
/**
* Installs an Icon in the project.
* @return true if the icon was installed.
*/
private boolean installIcon(String templateFolder, String iconName, File resourceFolder,
String folderName) throws ProjectCreateException {
File icon = new File(templateFolder, iconName);
if (icon.exists()) {
File drawable = createDirs(resourceFolder, folderName);
installBinaryFile(icon, new File(drawable, "ic_launcher.png"));
return true;
}
return false;
}
/**
* Installs a binary file
* @param source the source file to copy
* @param destination the destination file to write
* @throws ProjectCreateException
*/
private void installBinaryFile(File source, File destination) throws ProjectCreateException {
byte[] buffer = new byte[8192];
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(source);
fos = new FileOutputStream(destination);
int read;
while ((read = fis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
} catch (FileNotFoundException e) {
// shouldn't happen since we check before.
} catch (IOException e) {
throw new ProjectCreateException(e, "Failed to read binary file: %1$s",
source.getAbsolutePath());
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// ignore
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
// ignore
}
}
}
}
/**
* Prints a message unless silence is enabled.
*
* This is just a convenience wrapper around {@link ILogger#info(String, Object...)} from
* {@link #mLog} after testing if output level is {@link OutputLevel#VERBOSE}.
*
* @param format Format for String.format
* @param args Arguments for String.format
*/
private void println(String format, Object... args) {
if (mLevel != OutputLevel.SILENT) {
if (!format.endsWith("\n")) {
format += "\n";
}
mLog.info(format, args);
}
}
/**
* Creates a new folder, along with any parent folders that do not exists.
*
* @param parent the parent folder
* @param name the name of the directory to create.
* @throws ProjectCreateException
*/
private File createDirs(File parent, String name) throws ProjectCreateException {
final File newFolder = new File(parent, name);
boolean existedBefore = true;
if (!newFolder.exists()) {
if (!newFolder.mkdirs()) {
throw new ProjectCreateException("Could not create directory: %1$s", newFolder);
}
existedBefore = false;
}
if (newFolder.isDirectory()) {
if (!newFolder.canWrite()) {
throw new ProjectCreateException("Path is not writable: %1$s", newFolder);
}
} else {
throw new ProjectCreateException("Path is not a directory: %1$s", newFolder);
}
if (!existedBefore) {
try {
println("Created directory %1$s", newFolder.getCanonicalPath());
} catch (IOException e) {
throw new ProjectCreateException(
"Could not determine canonical path of created directory", e);
}
}
return newFolder;
}
/**
* Strips the string of beginning and trailing characters (multiple
* characters will be stripped, example stripString("..test...", '.')
* results in "test";
*
* @param s the string to strip
* @param strip the character to strip from beginning and end
* @return the stripped string or the empty string if everything is stripped.
*/
private static String stripString(String s, char strip) {
final int sLen = s.length();
int newStart = 0, newEnd = sLen - 1;
while (newStart < sLen && s.charAt(newStart) == strip) {
newStart++;
}
while (newEnd >= 0 && s.charAt(newEnd) == strip) {
newEnd--;
}
/*
* newEnd contains a char we want, and substring takes end as being
* exclusive
*/
newEnd++;
if (newStart >= sLen || newEnd < 0) {
return "";
}
return s.substring(newStart, newEnd);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy