aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java1520
1 files changed, 1520 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
new file mode 100644
index 000000000..d168c7503
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
@@ -0,0 +1,1520 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.wizards.newproject;
+
+import static com.android.SdkConstants.FN_PROJECT_PROPERTIES;
+import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_LIBRARY;
+
+import static org.eclipse.core.resources.IResource.DEPTH_ZERO;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.res2.ValueXmlHelper;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.project.AndroidNature;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.io.StreamException;
+import com.android.resources.Density;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
+
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileInfo;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.filesystem.IFileSystem;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.jdt.core.IAccessRule;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.operation.IRunnableContext;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IWorkingSet;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * The actual project creator invoked from the New Project Wizard
+ * <p/>
+ * Note: this class is public so that it can be accessed from unit tests.
+ * It is however an internal class. Its API may change without notice.
+ * It should semantically be considered as a private final class.
+ */
+public class NewProjectCreator {
+
+ private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$
+ private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$
+ private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$
+ private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$
+ private static final String PARAM_IMPORT_RESOURCE_CLASS = "IMPORT_RESOURCE_CLASS"; //$NON-NLS-1$
+ private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$
+ private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$
+ private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$
+ private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$
+ private static final String PARAM_SAMPLE_LOCATION = "SAMPLE_LOCATION"; //$NON-NLS-1$
+ private static final String PARAM_SOURCE = "SOURCE"; //$NON-NLS-1$
+ private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$
+ private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$
+ private static final String PARAM_IS_LIBRARY = "IS_LIBRARY"; //$NON-NLS-1$
+ private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION"; //$NON-NLS-1$
+ // Warning: The expanded string PARAM_TEST_TARGET_PACKAGE must not contain the
+ // string "PACKAGE" since it collides with the replacement of PARAM_PACKAGE.
+ private static final String PARAM_TEST_TARGET_PACKAGE = "TEST_TARGET_PCKG"; //$NON-NLS-1$
+ private static final String PARAM_TARGET_SELF = "TARGET_SELF"; //$NON-NLS-1$
+ private static final String PARAM_TARGET_MAIN = "TARGET_MAIN"; //$NON-NLS-1$
+ private static final String PARAM_TARGET_EXISTING = "TARGET_EXISTING"; //$NON-NLS-1$
+ private static final String PARAM_REFERENCE_PROJECT = "REFERENCE_PROJECT"; //$NON-NLS-1$
+
+ private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$
+ private static final String PH_USES_SDK = "USES-SDK"; //$NON-NLS-1$
+ private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$
+ private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$
+ private static final String PH_TEST_USES_LIBRARY = "TEST-USES-LIBRARY"; //$NON-NLS-1$
+ private static final String PH_TEST_INSTRUMENTATION = "TEST-INSTRUMENTATION"; //$NON-NLS-1$
+
+ private static final String BIN_DIRECTORY =
+ SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP;
+ private static final String BIN_CLASSES_DIRECTORY =
+ SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP +
+ SdkConstants.FD_CLASSES_OUTPUT + AdtConstants.WS_SEP;
+ private static final String RES_DIRECTORY =
+ SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP;
+ private static final String ASSETS_DIRECTORY =
+ SdkConstants.FD_ASSETS + AdtConstants.WS_SEP;
+ private static final String DRAWABLE_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + AdtConstants.WS_SEP;
+ private static final String DRAWABLE_XHDPI_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + '-' + Density.XHIGH.getResourceValue() +
+ AdtConstants.WS_SEP;
+ private static final String DRAWABLE_HDPI_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + '-' + Density.HIGH.getResourceValue() +
+ AdtConstants.WS_SEP;
+ private static final String DRAWABLE_MDPI_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + '-' + Density.MEDIUM.getResourceValue() +
+ AdtConstants.WS_SEP;
+ private static final String DRAWABLE_LDPI_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + '-' + Density.LOW.getResourceValue() +
+ AdtConstants.WS_SEP;
+ private static final String LAYOUT_DIRECTORY =
+ SdkConstants.FD_RES_LAYOUT + AdtConstants.WS_SEP;
+ private static final String VALUES_DIRECTORY =
+ SdkConstants.FD_RES_VALUES + AdtConstants.WS_SEP;
+ private static final String GEN_SRC_DIRECTORY =
+ SdkConstants.FD_GEN_SOURCES + AdtConstants.WS_SEP;
+
+ private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
+ private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
+ + "AndroidManifest.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY
+ + "activity.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
+ + "uses-sdk.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY
+ + "launcher_intent_filter.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_TEST_USES_LIBRARY = TEMPLATES_DIRECTORY
+ + "test_uses-library.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_TEST_INSTRUMENTATION = TEMPLATES_DIRECTORY
+ + "test_instrumentation.template"; //$NON-NLS-1$
+
+
+
+ private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY
+ + "strings.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY
+ + "string.template"; //$NON-NLS-1$
+ private static final String PROJECT_ICON = "ic_launcher.png"; //$NON-NLS-1$
+ private static final String ICON_XHDPI = "ic_launcher_xhdpi.png"; //$NON-NLS-1$
+ private static final String ICON_HDPI = "ic_launcher_hdpi.png"; //$NON-NLS-1$
+ private static final String ICON_MDPI = "ic_launcher_mdpi.png"; //$NON-NLS-1$
+ private static final String ICON_LDPI = "ic_launcher_ldpi.png"; //$NON-NLS-1$
+
+ private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$
+
+ private static final String STRING_RSRC_PREFIX = SdkConstants.STRING_PREFIX;
+ private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$
+ private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$
+
+ private static final String[] DEFAULT_DIRECTORIES = new String[] {
+ BIN_DIRECTORY, BIN_CLASSES_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
+ private static final String[] RES_DIRECTORIES = new String[] {
+ DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY };
+ private static final String[] RES_DENSITY_ENABLED_DIRECTORIES = new String[] {
+ DRAWABLE_XHDPI_DIRECTORY,
+ DRAWABLE_HDPI_DIRECTORY, DRAWABLE_MDPI_DIRECTORY, DRAWABLE_LDPI_DIRECTORY,
+ LAYOUT_DIRECTORY, VALUES_DIRECTORY };
+
+ private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$
+ private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$
+ private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$
+
+ private final NewProjectWizardState mValues;
+ private final IRunnableContext mRunnableContext;
+
+ /**
+ * Creates a new {@linkplain NewProjectCreator}
+ * @param values the wizard state with initial project parameters
+ * @param runnableContext the context to run project creation in
+ */
+ public NewProjectCreator(NewProjectWizardState values, IRunnableContext runnableContext) {
+ mValues = values;
+ mRunnableContext = runnableContext;
+ }
+
+ /**
+ * Before actually creating the project for a new project (as opposed to using an
+ * existing project), we check if the target location is a directory that either does
+ * not exist or is empty.
+ *
+ * If it's not empty, ask the user for confirmation.
+ *
+ * @param destination The destination folder where the new project is to be created.
+ * @return True if the destination doesn't exist yet or is an empty directory or is
+ * accepted by the user.
+ */
+ private boolean validateNewProjectLocationIsEmpty(IPath destination) {
+ File f = new File(destination.toOSString());
+ if (f.isDirectory() && f.list().length > 0) {
+ return AdtPlugin.displayPrompt("New Android Project",
+ "You are going to create a new Android Project in an existing, non-empty, directory. Are you sure you want to proceed?");
+ }
+ return true;
+ }
+
+ /**
+ * Structure that describes all the information needed to create a project.
+ * This is collected from the pages by {@link NewProjectCreator#createAndroidProjects()}
+ * and then used by
+ * {@link NewProjectCreator#createProjectAsync(IProgressMonitor, ProjectInfo, ProjectInfo)}.
+ */
+ private static class ProjectInfo {
+ private final IProject mProject;
+ private final IProjectDescription mDescription;
+ private final Map<String, Object> mParameters;
+ private final HashMap<String, String> mDictionary;
+
+ public ProjectInfo(IProject project,
+ IProjectDescription description,
+ Map<String, Object> parameters,
+ HashMap<String, String> dictionary) {
+ mProject = project;
+ mDescription = description;
+ mParameters = parameters;
+ mDictionary = dictionary;
+ }
+
+ public IProject getProject() {
+ return mProject;
+ }
+
+ public IProjectDescription getDescription() {
+ return mDescription;
+ }
+
+ public Map<String, Object> getParameters() {
+ return mParameters;
+ }
+
+ public HashMap<String, String> getDictionary() {
+ return mDictionary;
+ }
+ }
+
+ /**
+ * Creates the android project.
+ * @return True if the project could be created.
+ */
+ public boolean createAndroidProjects() {
+ if (mValues.importProjects != null && !mValues.importProjects.isEmpty()) {
+ return importProjects();
+ }
+
+ final ProjectInfo mainData = collectMainPageInfo();
+ final ProjectInfo testData = collectTestPageInfo();
+
+ // Create a monitored operation to create the actual project
+ WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+ @Override
+ protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
+ createProjectAsync(monitor, mainData, testData, null, true);
+ }
+ };
+
+ // Run the operation in a different thread
+ runAsyncOperation(op);
+ return true;
+ }
+
+ /**
+ * Creates the a plain Java project without typical android directories or an Android Nature.
+ * This is intended for use by unit tests and not as a general-purpose Java project creator.
+ * @return True if the project could be created.
+ */
+ @VisibleForTesting
+ public boolean createJavaProjects() {
+ if (mValues.importProjects != null && !mValues.importProjects.isEmpty()) {
+ return importProjects();
+ }
+
+ final ProjectInfo mainData = collectMainPageInfo();
+ final ProjectInfo testData = collectTestPageInfo();
+
+ // Create a monitored operation to create the actual project
+ WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+ @Override
+ protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
+ createProjectAsync(monitor, mainData, testData, null, false);
+ }
+ };
+
+ // Run the operation in a different thread
+ runAsyncOperation(op);
+ return true;
+ }
+
+ /**
+ * Imports a list of projects
+ */
+ private boolean importProjects() {
+ assert mValues.importProjects != null && !mValues.importProjects.isEmpty();
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+
+ final List<ProjectInfo> projectData = new ArrayList<ProjectInfo>();
+ for (ImportedProject p : mValues.importProjects) {
+
+ // Compute the project name and the package name from the manifest
+ ManifestData manifest = p.getManifest();
+ if (manifest == null) {
+ continue;
+ }
+ String packageName = manifest.getPackage();
+ String projectName = p.getProjectName();
+ String minSdk = manifest.getMinSdkVersionString();
+
+ final IProject project = workspace.getRoot().getProject(projectName);
+ final IProjectDescription description =
+ workspace.newProjectDescription(project.getName());
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_PROJECT, projectName);
+ parameters.put(PARAM_PACKAGE, packageName);
+ parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
+ parameters.put(PARAM_IS_NEW_PROJECT, Boolean.FALSE);
+ parameters.put(PARAM_SRC_FOLDER, SdkConstants.FD_SOURCES);
+
+ parameters.put(PARAM_SDK_TARGET, p.getTarget());
+
+ // TODO: Find out if these end up getting used in the import-path through the code!
+ parameters.put(PARAM_MIN_SDK_VERSION, minSdk);
+ parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
+ final HashMap<String, String> dictionary = new HashMap<String, String>();
+ dictionary.put(STRING_APP_NAME, mValues.applicationName);
+
+ if (mValues.copyIntoWorkspace) {
+ parameters.put(PARAM_SOURCE, p.getLocation());
+
+ // TODO: Make sure it isn't *already* in the workspace!
+ //IPath defaultLocation = Platform.getLocation();
+ //if ((!mValues.useDefaultLocation || mValues.useExisting)
+ // && !defaultLocation.isPrefixOf(path)) {
+ //IPath workspaceLocation = Platform.getLocation().append(projectName);
+ //description.setLocation(workspaceLocation);
+ // DON'T SET THE LOCATION: It's IMPLIED and in fact it will generate
+ // an error if you set it!
+ } else {
+ // Create in place
+ description.setLocation(new Path(p.getLocation().getPath()));
+ }
+
+ projectData.add(new ProjectInfo(project, description, parameters, dictionary));
+ }
+
+ // Create a monitored operation to create the actual project
+ WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+ @Override
+ protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
+ createProjectAsync(monitor, null, null, projectData, true);
+ }
+ };
+
+ // Run the operation in a different thread
+ runAsyncOperation(op);
+ return true;
+ }
+
+ /**
+ * Collects all the parameters needed to create the main project.
+ * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
+ * created because parameters are incorrect or should not be created because there
+ * is no main page.
+ */
+ private ProjectInfo collectMainPageInfo() {
+ if (mValues.mode == Mode.TEST) {
+ return null;
+ }
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ final IProject project = workspace.getRoot().getProject(mValues.projectName);
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_PROJECT, mValues.projectName);
+ parameters.put(PARAM_PACKAGE, mValues.packageName);
+ parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
+ parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
+ parameters.put(PARAM_IS_NEW_PROJECT, mValues.mode == Mode.ANY && !mValues.useExisting);
+ parameters.put(PARAM_SAMPLE_LOCATION, mValues.chosenSample);
+ parameters.put(PARAM_SRC_FOLDER, mValues.sourceFolder);
+ parameters.put(PARAM_SDK_TARGET, mValues.target);
+ parameters.put(PARAM_MIN_SDK_VERSION, mValues.minSdk);
+
+ if (mValues.createActivity) {
+ parameters.put(PARAM_ACTIVITY, mValues.activityName);
+ }
+
+ // create a dictionary of string that will contain name+content.
+ // we'll put all the strings into values/strings.xml
+ final HashMap<String, String> dictionary = new HashMap<String, String>();
+ dictionary.put(STRING_APP_NAME, mValues.applicationName);
+
+ IPath path = new Path(mValues.projectLocation.getPath());
+ IPath defaultLocation = Platform.getLocation();
+ if ((!mValues.useDefaultLocation || mValues.useExisting)
+ && !defaultLocation.isPrefixOf(path)) {
+ description.setLocation(path);
+ }
+
+ if (mValues.mode == Mode.ANY && !mValues.useExisting && !mValues.useDefaultLocation &&
+ !validateNewProjectLocationIsEmpty(path)) {
+ return null;
+ }
+
+ return new ProjectInfo(project, description, parameters, dictionary);
+ }
+
+ /**
+ * Collects all the parameters needed to create the test project.
+ *
+ * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
+ * created because parameters are incorrect or should not be created because there
+ * is no test page.
+ */
+ private ProjectInfo collectTestPageInfo() {
+ if (mValues.mode != Mode.TEST && !mValues.createPairProject) {
+ return null;
+ }
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ String projectName =
+ mValues.mode == Mode.TEST ? mValues.projectName : mValues.testProjectName;
+ final IProject project = workspace.getRoot().getProject(projectName);
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+
+ String pkg =
+ mValues.mode == Mode.TEST ? mValues.packageName : mValues.testPackageName;
+
+ parameters.put(PARAM_PACKAGE, pkg);
+ parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
+ parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
+ parameters.put(PARAM_IS_NEW_PROJECT, !mValues.useExisting);
+ parameters.put(PARAM_SRC_FOLDER, mValues.sourceFolder);
+ parameters.put(PARAM_SDK_TARGET, mValues.target);
+ parameters.put(PARAM_MIN_SDK_VERSION, mValues.minSdk);
+
+ // Test-specific parameters
+ String testedPkg = mValues.createPairProject
+ ? mValues.packageName : mValues.testTargetPackageName;
+ if (testedPkg == null) {
+ assert mValues.testingSelf;
+ testedPkg = pkg;
+ }
+
+ parameters.put(PARAM_TEST_TARGET_PACKAGE, testedPkg);
+
+ if (mValues.testingSelf) {
+ parameters.put(PARAM_TARGET_SELF, true);
+ } else {
+ parameters.put(PARAM_TARGET_EXISTING, true);
+ parameters.put(PARAM_REFERENCE_PROJECT, mValues.testedProject);
+ }
+
+ if (mValues.createPairProject) {
+ parameters.put(PARAM_TARGET_MAIN, true);
+ }
+
+ // create a dictionary of string that will contain name+content.
+ // we'll put all the strings into values/strings.xml
+ final HashMap<String, String> dictionary = new HashMap<String, String>();
+ dictionary.put(STRING_APP_NAME, mValues.testApplicationName);
+
+ // Use the same logic to determine test project location as in
+ // ApplicationInfoPage#validateTestProjectLocation
+ IPath path = new Path(mValues.projectLocation.getPath());
+ path = path.removeLastSegments(1).append(mValues.testProjectName);
+ IPath defaultLocation = Platform.getLocation();
+ if ((!mValues.useDefaultLocation || mValues.useExisting)
+ && !path.equals(defaultLocation)) {
+ description.setLocation(path);
+ }
+
+ if (!mValues.useExisting && !mValues.useDefaultLocation &&
+ !validateNewProjectLocationIsEmpty(path)) {
+ return null;
+ }
+
+ return new ProjectInfo(project, description, parameters, dictionary);
+ }
+
+ /**
+ * Runs the operation in a different thread and display generated
+ * exceptions.
+ *
+ * @param op The asynchronous operation to run.
+ */
+ private void runAsyncOperation(WorkspaceModifyOperation op) {
+ try {
+ mRunnableContext.run(true /* fork */, true /* cancelable */, op);
+ } catch (InvocationTargetException e) {
+
+ AdtPlugin.log(e, "New Project Wizard failed");
+
+ // The runnable threw an exception
+ Throwable t = e.getTargetException();
+ if (t instanceof CoreException) {
+ CoreException core = (CoreException) t;
+ if (core.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
+ // The error indicates the file system is not case sensitive
+ // and there's a resource with a similar name.
+ MessageDialog.openError(AdtPlugin.getShell(),
+ "Error", "Error: Case Variant Exists");
+ } else {
+ ErrorDialog.openError(AdtPlugin.getShell(),
+ "Error", core.getMessage(), core.getStatus());
+ }
+ } else {
+ // Some other kind of exception
+ String msg = t.getMessage();
+ Throwable t1 = t;
+ while (msg == null && t1.getCause() != null) {
+ msg = t1.getMessage();
+ t1 = t1.getCause();
+ }
+ if (msg == null) {
+ msg = t.toString();
+ }
+ MessageDialog.openError(AdtPlugin.getShell(), "Error", msg);
+ }
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Creates the actual project(s). This is run asynchronously in a different thread.
+ *
+ * @param monitor An existing monitor.
+ * @param mainData Data for main project. Can be null.
+ * @param isAndroidProject true if the project is to be set up as a full Android project; false
+ * for a plain Java project.
+ * @throws InvocationTargetException to wrap any unmanaged exception and
+ * return it to the calling thread. The method can fail if it fails
+ * to create or modify the project or if it is canceled by the user.
+ */
+ private void createProjectAsync(IProgressMonitor monitor,
+ ProjectInfo mainData,
+ ProjectInfo testData,
+ List<ProjectInfo> importData,
+ boolean isAndroidProject)
+ throws InvocationTargetException {
+ monitor.beginTask("Create Android Project", 100);
+ try {
+ IProject mainProject = null;
+
+ if (mainData != null) {
+ mainProject = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ mainData.getProject(),
+ mainData.getDescription(),
+ mainData.getParameters(),
+ mainData.getDictionary(),
+ null,
+ isAndroidProject);
+
+ if (mainProject != null) {
+ final IJavaProject javaProject = JavaCore.create(mainProject);
+ Display.getDefault().syncExec(new WorksetAdder(javaProject,
+ mValues.workingSets));
+ }
+ }
+
+ if (testData != null) {
+ Map<String, Object> parameters = testData.getParameters();
+ if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) {
+ parameters.put(PARAM_REFERENCE_PROJECT, mainProject);
+ }
+
+ IProject testProject = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ testData.getProject(),
+ testData.getDescription(),
+ parameters,
+ testData.getDictionary(),
+ null,
+ isAndroidProject);
+ if (testProject != null) {
+ final IJavaProject javaProject = JavaCore.create(testProject);
+ Display.getDefault().syncExec(new WorksetAdder(javaProject,
+ mValues.workingSets));
+ }
+ }
+
+ if (importData != null) {
+ for (final ProjectInfo data : importData) {
+ ProjectPopulator projectPopulator = null;
+ if (mValues.copyIntoWorkspace) {
+ projectPopulator = new ProjectPopulator() {
+ @Override
+ public void populate(IProject project) {
+ // Copy
+ IFileSystem fileSystem = EFS.getLocalFileSystem();
+ File source = (File) data.getParameters().get(PARAM_SOURCE);
+ IFileStore sourceDir = new ReadWriteFileStore(
+ fileSystem.getStore(source.toURI()));
+ IFileStore destDir = new ReadWriteFileStore(
+ fileSystem.getStore(AdtUtils.getAbsolutePath(project)));
+ try {
+ sourceDir.copy(destDir, EFS.OVERWRITE, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ };
+ }
+ IProject project = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ data.getProject(),
+ data.getDescription(),
+ data.getParameters(),
+ data.getDictionary(),
+ projectPopulator,
+ isAndroidProject);
+ if (project != null) {
+ final IJavaProject javaProject = JavaCore.create(project);
+ Display.getDefault().syncExec(new WorksetAdder(javaProject,
+ mValues.workingSets));
+ ProjectHelper.enforcePreferredCompilerCompliance(javaProject);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ throw new InvocationTargetException(e);
+ } catch (IOException e) {
+ throw new InvocationTargetException(e);
+ } catch (StreamException e) {
+ throw new InvocationTargetException(e);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /** Handler which can write contents into a project */
+ public interface ProjectPopulator {
+ /**
+ * Add contents into the given project
+ *
+ * @param project the project to write into
+ * @throws InvocationTargetException if anything goes wrong
+ */
+ public void populate(IProject project) throws InvocationTargetException;
+ }
+
+ /**
+ * Creates the actual project, sets its nature and adds the required folders
+ * and files to it. This is run asynchronously in a different thread.
+ *
+ * @param monitor An existing monitor.
+ * @param project The project to create.
+ * @param description A description of the project.
+ * @param parameters Template parameters.
+ * @param dictionary String definition.
+ * @param isAndroidProject true if the project is to be set up as a full Android project; false
+ * for a plain Java project.
+ * @return The project newly created
+ * @throws StreamException
+ */
+ private IProject createEclipseProject(
+ @NonNull IProgressMonitor monitor,
+ @NonNull IProject project,
+ @NonNull IProjectDescription description,
+ @NonNull Map<String, Object> parameters,
+ @Nullable Map<String, String> dictionary,
+ @Nullable ProjectPopulator projectPopulator,
+ boolean isAndroidProject)
+ throws CoreException, IOException, StreamException {
+
+ // get the project target
+ IAndroidTarget target = (IAndroidTarget) parameters.get(PARAM_SDK_TARGET);
+ boolean legacy = isAndroidProject && target.getVersion().getApiLevel() < 4;
+
+ // Create project and open it
+ project.create(description, new SubProgressMonitor(monitor, 10));
+ if (monitor.isCanceled()) throw new OperationCanceledException();
+
+ project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10));
+
+ // Add the Java and android nature to the project
+ AndroidNature.setupProjectNatures(project, monitor, isAndroidProject);
+
+ // Create folders in the project if they don't already exist
+ addDefaultDirectories(project, AdtConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
+ String[] sourceFolders;
+ if (isAndroidProject) {
+ sourceFolders = new String[] {
+ (String) parameters.get(PARAM_SRC_FOLDER),
+ GEN_SRC_DIRECTORY
+ };
+ } else {
+ sourceFolders = new String[] {
+ (String) parameters.get(PARAM_SRC_FOLDER)
+ };
+ }
+ addDefaultDirectories(project, AdtConstants.WS_ROOT, sourceFolders, monitor);
+
+ // Create the resource folders in the project if they don't already exist.
+ if (legacy) {
+ addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
+ } else {
+ addDefaultDirectories(project, RES_DIRECTORY, RES_DENSITY_ENABLED_DIRECTORIES, monitor);
+ }
+
+ if (projectPopulator != null) {
+ try {
+ projectPopulator.populate(project);
+ } catch (InvocationTargetException ite) {
+ AdtPlugin.log(ite, null);
+ }
+ }
+
+ // Setup class path: mark folders as source folders
+ IJavaProject javaProject = JavaCore.create(project);
+ setupSourceFolders(javaProject, sourceFolders, monitor);
+
+ if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
+ // Create files in the project if they don't already exist
+ addManifest(project, parameters, dictionary, monitor);
+
+ // add the default app icon
+ addIcon(project, legacy, monitor);
+
+ // Create the default package components
+ addSampleCode(project, sourceFolders[0], parameters, dictionary, monitor);
+
+ // add the string definition file if needed
+ if (dictionary != null && dictionary.size() > 0) {
+ addStringDictionaryFile(project, dictionary, monitor);
+ }
+
+ // add the default proguard config
+ File libFolder = new File((String) parameters.get(PARAM_SDK_TOOLS_DIR),
+ SdkConstants.FD_LIB);
+ addLocalFile(project,
+ new File(libFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
+ // Write ProGuard config files with the extension .pro which
+ // is what is used in the ProGuard documentation and samples
+ SdkConstants.FN_PROJECT_PROGUARD_FILE,
+ monitor);
+
+ // Set output location
+ javaProject.setOutputLocation(project.getFolder(BIN_CLASSES_DIRECTORY).getFullPath(),
+ monitor);
+ }
+
+ File sampleDir = (File) parameters.get(PARAM_SAMPLE_LOCATION);
+ if (sampleDir != null) {
+ // Copy project
+ copySampleCode(project, sampleDir, parameters, dictionary, monitor);
+ }
+
+ // Create the reference to the target project
+ if (parameters.containsKey(PARAM_REFERENCE_PROJECT)) {
+ IProject refProject = (IProject) parameters.get(PARAM_REFERENCE_PROJECT);
+ if (refProject != null) {
+ IProjectDescription desc = project.getDescription();
+
+ // Add out reference to the existing project reference.
+ // We just created a project with no references so we don't need to expand
+ // the currently-empty current list.
+ desc.setReferencedProjects(new IProject[] { refProject });
+
+ project.setDescription(desc, IResource.KEEP_HISTORY,
+ new SubProgressMonitor(monitor, 10));
+
+ IClasspathEntry entry = JavaCore.newProjectEntry(
+ refProject.getFullPath(), //path
+ new IAccessRule[0], //accessRules
+ false, //combineAccessRules
+ new IClasspathAttribute[0], //extraAttributes
+ false //isExported
+
+ );
+ ProjectHelper.addEntryToClasspath(javaProject, entry);
+ }
+ }
+
+ if (isAndroidProject) {
+ Sdk.getCurrent().initProject(project, target);
+ }
+
+ // Fix the project to make sure all properties are as expected.
+ // Necessary for existing projects and good for new ones to.
+ ProjectHelper.fixProject(project);
+
+ Boolean isLibraryProject = (Boolean) parameters.get(PARAM_IS_LIBRARY);
+ if (isLibraryProject != null && isLibraryProject.booleanValue()
+ && Sdk.getCurrent() != null && project.isOpen()) {
+ ProjectState state = Sdk.getProjectState(project);
+ if (state != null) {
+ // make a working copy of the properties
+ ProjectPropertiesWorkingCopy properties =
+ state.getProperties().makeWorkingCopy();
+
+ properties.setProperty(PROPERTY_LIBRARY, Boolean.TRUE.toString());
+ try {
+ properties.save();
+ IResource projectProp = project.findMember(FN_PROJECT_PROPERTIES);
+ if (projectProp != null) {
+ projectProp.refreshLocal(DEPTH_ZERO, new NullProgressMonitor());
+ }
+ } catch (Exception e) {
+ String msg = String.format(
+ "Failed to save %1$s for project %2$s",
+ SdkConstants.FN_PROJECT_PROPERTIES, project.getName());
+ AdtPlugin.log(e, msg);
+ }
+ }
+ }
+
+ return project;
+ }
+
+ /**
+ * Creates a new project
+ *
+ * @param monitor An existing monitor.
+ * @param project The project to create.
+ * @param target the build target to associate with the project
+ * @param projectPopulator a handler for writing the template contents
+ * @param isLibrary whether this project should be marked as a library project
+ * @param projectLocation the location to write the project into
+ * @param workingSets Eclipse working sets, if any, to add the project to
+ * @throws CoreException if anything goes wrong
+ */
+ public static void create(
+ @NonNull IProgressMonitor monitor,
+ @NonNull final IProject project,
+ @NonNull IAndroidTarget target,
+ @Nullable final ProjectPopulator projectPopulator,
+ boolean isLibrary,
+ @NonNull String projectLocation,
+ @NonNull final IWorkingSet[] workingSets)
+ throws CoreException {
+ final NewProjectCreator creator = new NewProjectCreator(null, null);
+
+ final Map<String, String> dictionary = null;
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_SDK_TARGET, target);
+ parameters.put(PARAM_SRC_FOLDER, SdkConstants.FD_SOURCES);
+ parameters.put(PARAM_IS_NEW_PROJECT, false);
+ parameters.put(PARAM_SAMPLE_LOCATION, null);
+ parameters.put(PARAM_IS_LIBRARY, isLibrary);
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ if (projectLocation != null) {
+ IPath path = new Path(projectLocation);
+ IPath parent = new Path(path.toFile().getParent());
+ IPath workspaceLocation = Platform.getLocation();
+ if (!workspaceLocation.equals(parent)) {
+ description.setLocation(path);
+ }
+ }
+
+ IWorkspaceRunnable workspaceRunnable = new IWorkspaceRunnable() {
+ @Override
+ public void run(IProgressMonitor submonitor) throws CoreException {
+ try {
+ creator.createEclipseProject(submonitor, project, description, parameters,
+ dictionary, projectPopulator, true);
+ } catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Unexpected error while creating project", e));
+ } catch (StreamException e) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Unexpected error while creating project", e));
+ }
+ if (workingSets != null && workingSets.length > 0) {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ if (javaProject != null) {
+ Display.getDefault().syncExec(new WorksetAdder(javaProject,
+ workingSets));
+ }
+ }
+ }
+ };
+
+ ResourcesPlugin.getWorkspace().run(workspaceRunnable, monitor);
+ }
+
+ /**
+ * Adds default directories to the project.
+ *
+ * @param project The Java Project to update.
+ * @param parentFolder The path of the parent folder. Must end with a
+ * separator.
+ * @param folders Folders to be added.
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to create the directories in
+ * the project.
+ */
+ private void addDefaultDirectories(IProject project, String parentFolder,
+ String[] folders, IProgressMonitor monitor) throws CoreException {
+ for (String name : folders) {
+ if (name.length() > 0) {
+ IFolder folder = project.getFolder(parentFolder + name);
+ if (!folder.exists()) {
+ folder.create(true /* force */, true /* local */,
+ new SubProgressMonitor(monitor, 10));
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds the manifest to the project.
+ *
+ * @param project The Java Project to update.
+ * @param parameters Template Parameters.
+ * @param dictionary String List to be added to a string definition
+ * file. This map will be filled by this method.
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to update the project.
+ * @throws IOException if the method fails to create the files in the
+ * project.
+ */
+ private void addManifest(IProject project, Map<String, Object> parameters,
+ Map<String, String> dictionary, IProgressMonitor monitor)
+ throws CoreException, IOException {
+
+ // get IFile to the manifest and check if it's not already there.
+ IFile file = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
+ if (!file.exists()) {
+
+ // Read manifest template
+ String manifestTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_MANIFEST);
+
+ // Replace all keyword parameters
+ manifestTemplate = replaceParameters(manifestTemplate, parameters);
+
+ if (manifestTemplate == null) {
+ // Inform the user there will be not manifest.
+ AdtPlugin.logAndPrintError(null, "Create Project" /*TAG*/,
+ "Failed to generate the Android manifest. Missing template %s",
+ TEMPLATE_MANIFEST);
+ // Abort now, there's no need to continue
+ return;
+ }
+
+ if (parameters.containsKey(PARAM_ACTIVITY)) {
+ // now get the activity template
+ String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES);
+
+ // If the activity name doesn't contain any dot, it's in the form
+ // "ClassName" and we need to expand it to ".ClassName" in the XML.
+ String name = (String) parameters.get(PARAM_ACTIVITY);
+ if (name.indexOf('.') == -1) {
+ // Duplicate the parameters map to avoid changing the caller
+ parameters = new HashMap<String, Object>(parameters);
+ parameters.put(PARAM_ACTIVITY, "." + name); //$NON-NLS-1$
+ }
+
+ // Replace all keyword parameters to make main activity.
+ String activities = replaceParameters(activityTemplate, parameters);
+
+ // set the intent.
+ String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER);
+
+ if (activities != null) {
+ if (intent != null) {
+ // set the intent to the main activity
+ activities = activities.replaceAll(PH_INTENT_FILTERS, intent);
+ }
+
+ // set the activity(ies) in the manifest
+ manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, activities);
+ }
+ } else {
+ // remove the activity(ies) from the manifest
+ manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, ""); //$NON-NLS-1$
+ }
+
+ // Handle the case of the test projects
+ if (parameters.containsKey(PARAM_TEST_TARGET_PACKAGE)) {
+ // Set the uses-library needed by the test project
+ String usesLibrary = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_USES_LIBRARY);
+ if (usesLibrary != null) {
+ manifestTemplate = manifestTemplate.replaceAll(
+ PH_TEST_USES_LIBRARY, usesLibrary);
+ }
+
+ // Set the instrumentation element needed by the test project
+ String instru = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_INSTRUMENTATION);
+ if (instru != null) {
+ manifestTemplate = manifestTemplate.replaceAll(
+ PH_TEST_INSTRUMENTATION, instru);
+ }
+
+ // Replace PARAM_TEST_TARGET_PACKAGE itself now
+ manifestTemplate = replaceParameters(manifestTemplate, parameters);
+
+ } else {
+ // remove the unused entries
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_USES_LIBRARY, ""); //$NON-NLS-1$
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_INSTRUMENTATION, ""); //$NON-NLS-1$
+ }
+
+ String minSdkVersion = (String) parameters.get(PARAM_MIN_SDK_VERSION);
+ if (minSdkVersion != null && minSdkVersion.length() > 0) {
+ String usesSdkTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_USES_SDK);
+ if (usesSdkTemplate != null) {
+ String usesSdk = replaceParameters(usesSdkTemplate, parameters);
+ manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, usesSdk);
+ }
+ } else {
+ manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, "");
+ }
+
+ // Reformat the file according to the user's formatting settings
+ manifestTemplate = reformat(XmlFormatStyle.MANIFEST, manifestTemplate);
+
+ // Save in the project as UTF-8
+ InputStream stream = new ByteArrayInputStream(
+ manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$
+ file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Adds the string resource file.
+ *
+ * @param project The Java Project to update.
+ * @param strings The list of strings to be added to the string file.
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to update the project.
+ * @throws IOException if the method fails to create the files in the
+ * project.
+ */
+ private void addStringDictionaryFile(IProject project,
+ Map<String, String> strings, IProgressMonitor monitor)
+ throws CoreException, IOException {
+
+ // create the IFile object and check if the file doesn't already exist.
+ IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + VALUES_DIRECTORY + AdtConstants.WS_SEP + STRINGS_FILE);
+ if (!file.exists()) {
+ // get the Strings.xml template
+ String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS);
+
+ // get the template for one string
+ String stringTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRING);
+
+ // get all the string names
+ Set<String> stringNames = strings.keySet();
+
+ // loop on it and create the string definitions
+ StringBuilder stringNodes = new StringBuilder();
+ for (String key : stringNames) {
+ // get the value from the key
+ String value = strings.get(key);
+
+ // Escape values if necessary
+ value = ValueXmlHelper.escapeResourceString(value);
+
+ // place them in the template
+ String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key);
+ stringDef = stringDef.replace(PARAM_STRING_CONTENT, value);
+
+ // append to the other string
+ if (stringNodes.length() > 0) {
+ stringNodes.append('\n');
+ }
+ stringNodes.append(stringDef);
+ }
+
+ // put the string nodes in the Strings.xml template
+ stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS,
+ stringNodes.toString());
+
+ // reformat the file according to the user's formatting settings
+ stringDefinitionTemplate = reformat(XmlFormatStyle.RESOURCE, stringDefinitionTemplate);
+
+ // write the file as UTF-8
+ InputStream stream = new ByteArrayInputStream(
+ stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$
+ file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /** Reformats the given contents with the current formatting settings */
+ private String reformat(XmlFormatStyle style, String contents) {
+ if (AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
+ EclipseXmlFormatPreferences formatPrefs = EclipseXmlFormatPreferences.create();
+ return EclipseXmlPrettyPrinter.prettyPrint(contents, formatPrefs, style,
+ null /*lineSeparator*/);
+ } else {
+ return contents;
+ }
+ }
+
+ /**
+ * Adds default application icon to the project.
+ *
+ * @param project The Java Project to update.
+ * @param legacy whether we're running in legacy mode (no density support)
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to update the project.
+ */
+ private void addIcon(IProject project, boolean legacy, IProgressMonitor monitor)
+ throws CoreException {
+ if (legacy) { // density support
+ // do medium density icon only, in the default drawable folder.
+ IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
+ }
+ } else {
+ // do all 4 icons.
+ IFile file;
+
+ // extra high density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_XHDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_XHDPI), monitor);
+ }
+
+ // high density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_HDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_HDPI), monitor);
+ }
+
+ // medium density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_MDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
+ }
+
+ // low density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_LDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_LDPI), monitor);
+ }
+ }
+ }
+
+ /**
+ * Creates a file from a data source.
+ * @param dest the file to write
+ * @param source the content of the file.
+ * @param monitor the progress monitor
+ * @throws CoreException
+ */
+ private void addFile(IFile dest, byte[] source, IProgressMonitor monitor) throws CoreException {
+ if (source != null) {
+ // Save in the project
+ InputStream stream = new ByteArrayInputStream(source);
+ dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Creates the package folder and copies the sample code in the project.
+ *
+ * @param project The Java Project to update.
+ * @param parameters Template Parameters.
+ * @param dictionary String List to be added to a string definition
+ * file. This map will be filled by this method.
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to update the project.
+ * @throws IOException if the method fails to create the files in the
+ * project.
+ */
+ private void addSampleCode(IProject project, String sourceFolder,
+ Map<String, Object> parameters, Map<String, String> dictionary,
+ IProgressMonitor monitor) throws CoreException, IOException {
+ // create the java package directories.
+ IFolder pkgFolder = project.getFolder(sourceFolder);
+ String packageName = (String) parameters.get(PARAM_PACKAGE);
+
+ // The PARAM_ACTIVITY key will be absent if no activity should be created,
+ // in which case activityName will be null.
+ String activityName = (String) parameters.get(PARAM_ACTIVITY);
+
+ Map<String, Object> java_activity_parameters = new HashMap<String, Object>(parameters);
+ java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, ""); //$NON-NLS-1$
+
+ if (activityName != null) {
+
+ String resourcePackageClass = null;
+
+ // An activity name can be of the form ".package.Class", ".Class" or FQDN.
+ // The initial dot is ignored, as it is always added later in the templates.
+ int lastDotIndex = activityName.lastIndexOf('.');
+
+ if (lastDotIndex != -1) {
+
+ // Resource class
+ if (lastDotIndex > 0) {
+ resourcePackageClass = packageName + '.' + SdkConstants.FN_RESOURCE_BASE;
+ }
+
+ // Package name
+ if (activityName.startsWith(".")) { //$NON-NLS-1$
+ packageName += activityName.substring(0, lastDotIndex);
+ } else {
+ packageName = activityName.substring(0, lastDotIndex);
+ }
+
+ // Activity Class name
+ activityName = activityName.substring(lastDotIndex + 1);
+ }
+
+ java_activity_parameters.put(PARAM_ACTIVITY, activityName);
+ java_activity_parameters.put(PARAM_PACKAGE, packageName);
+ if (resourcePackageClass != null) {
+ String importResourceClass = "\nimport " + resourcePackageClass + ";"; //$NON-NLS-1$ // $NON-NLS-2$
+ java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, importResourceClass);
+ }
+ }
+
+ String[] components = packageName.split(AdtConstants.RE_DOT);
+ for (String component : components) {
+ pkgFolder = pkgFolder.getFolder(component);
+ if (!pkgFolder.exists()) {
+ pkgFolder.create(true /* force */, true /* local */,
+ new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ if (activityName != null) {
+ // create the main activity Java file
+ String activityJava = activityName + SdkConstants.DOT_JAVA;
+ IFile file = pkgFolder.getFile(activityJava);
+ if (!file.exists()) {
+ copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor, false);
+ }
+
+ // create the layout file (if we're creating an
+ IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY);
+ file = layoutfolder.getFile(MAIN_LAYOUT_XML);
+ if (!file.exists()) {
+ copyFile(LAYOUT_TEMPLATE, file, parameters, monitor, true);
+ dictionary.put(STRING_HELLO_WORLD, String.format("Hello World, %1$s!",
+ activityName));
+ }
+ }
+ }
+
+ private void copySampleCode(IProject project, File sampleDir,
+ Map<String, Object> parameters, Map<String, String> dictionary,
+ IProgressMonitor monitor) throws CoreException {
+ // Copy the sampleDir into the project directory recursively
+ IFileSystem fileSystem = EFS.getLocalFileSystem();
+ IFileStore sourceDir = new ReadWriteFileStore(
+ fileSystem.getStore(sampleDir.toURI()));
+ IFileStore destDir = new ReadWriteFileStore(
+ fileSystem.getStore(AdtUtils.getAbsolutePath(project)));
+ sourceDir.copy(destDir, EFS.OVERWRITE, null);
+ }
+
+ /**
+ * In a sample we never duplicate source files as read-only.
+ * This creates a store that read files attributes and doesn't set the r-o flag.
+ */
+ private static class ReadWriteFileStore extends FileStoreAdapter {
+
+ public ReadWriteFileStore(IFileStore store) {
+ super(store);
+ }
+
+ // Override when reading attributes
+ @Override
+ public IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException {
+ IFileInfo info = super.fetchInfo(options, monitor);
+ info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, false);
+ return info;
+ }
+
+ // Override when writing attributes
+ @Override
+ public void putInfo(IFileInfo info, int options, IProgressMonitor storeMonitor)
+ throws CoreException {
+ info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, false);
+ super.putInfo(info, options, storeMonitor);
+ }
+
+ @Deprecated
+ @Override
+ public IFileStore getChild(IPath path) {
+ IFileStore child = super.getChild(path);
+ if (!(child instanceof ReadWriteFileStore)) {
+ child = new ReadWriteFileStore(child);
+ }
+ return child;
+ }
+
+ @Override
+ public IFileStore getChild(String name) {
+ return new ReadWriteFileStore(super.getChild(name));
+ }
+ }
+
+ /**
+ * Adds a file to the root of the project
+ * @param project the project to add the file to.
+ * @param destName the name to write the file as
+ * @param source the file to add. It'll keep the same filename once copied into the project.
+ * @param monitor the monitor to report progress to
+ * @throws FileNotFoundException if the file to be added does not exist
+ * @throws CoreException if writing the file does not work
+ */
+ public static void addLocalFile(IProject project, File source, String destName,
+ IProgressMonitor monitor) throws FileNotFoundException, CoreException {
+ IFile dest = project.getFile(destName);
+ if (dest.exists() == false) {
+ FileInputStream stream = new FileInputStream(source);
+ dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Adds the given folder to the project's class path.
+ *
+ * @param javaProject The Java Project to update.
+ * @param sourceFolders Template Parameters.
+ * @param monitor An existing monitor.
+ * @throws JavaModelException if the classpath could not be set.
+ */
+ private void setupSourceFolders(IJavaProject javaProject, String[] sourceFolders,
+ IProgressMonitor monitor) throws JavaModelException {
+ IProject project = javaProject.getProject();
+
+ // get the list of entries.
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+
+ // remove the project as a source folder (This is the default)
+ entries = removeSourceClasspath(entries, project);
+
+ // add the source folders.
+ for (String sourceFolder : sourceFolders) {
+ IFolder srcFolder = project.getFolder(sourceFolder);
+
+ // remove it first in case.
+ entries = removeSourceClasspath(entries, srcFolder);
+ entries = ProjectHelper.addEntryToClasspath(entries,
+ JavaCore.newSourceEntry(srcFolder.getFullPath()));
+ }
+
+ javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10));
+ }
+
+
+ /**
+ * Removes the corresponding source folder from the class path entries if
+ * found.
+ *
+ * @param entries The class path entries to read. A copy will be returned.
+ * @param folder The parent source folder to remove.
+ * @return A new class path entries array.
+ */
+ private IClasspathEntry[] removeSourceClasspath(IClasspathEntry[] entries, IContainer folder) {
+ if (folder == null) {
+ return entries;
+ }
+ IClasspathEntry source = JavaCore.newSourceEntry(folder.getFullPath());
+ int n = entries.length;
+ for (int i = n - 1; i >= 0; i--) {
+ if (entries[i].equals(source)) {
+ IClasspathEntry[] newEntries = new IClasspathEntry[n - 1];
+ if (i > 0) System.arraycopy(entries, 0, newEntries, 0, i);
+ if (i < n - 1) System.arraycopy(entries, i + 1, newEntries, i, n - i - 1);
+ n--;
+ entries = newEntries;
+ }
+ }
+ return entries;
+ }
+
+
+ /**
+ * Copies the given file from our resource folder to the new project.
+ * Expects the file to the US-ASCII or UTF-8 encoded.
+ *
+ * @throws CoreException from IFile if failing to create the new file.
+ * @throws MalformedURLException from URL if failing to interpret the URL.
+ * @throws FileNotFoundException from RandomAccessFile.
+ * @throws IOException from RandomAccessFile.length() if can't determine the
+ * length.
+ */
+ private void copyFile(String resourceFilename, IFile destFile,
+ Map<String, Object> parameters, IProgressMonitor monitor, boolean reformat)
+ throws CoreException, IOException {
+
+ // Read existing file.
+ String template = AdtPlugin.readEmbeddedTextFile(
+ TEMPLATES_DIRECTORY + resourceFilename);
+
+ // Replace all keyword parameters
+ template = replaceParameters(template, parameters);
+
+ if (reformat) {
+ // Guess the formatting style based on the file location
+ XmlFormatStyle style = EclipseXmlPrettyPrinter
+ .getForFile(destFile.getProjectRelativePath());
+ if (style != null) {
+ template = reformat(style, template);
+ }
+ }
+
+ // Save in the project as UTF-8
+ InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$
+ destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+
+ /**
+ * Replaces placeholders found in a string with values.
+ *
+ * @param str the string to search for placeholders.
+ * @param parameters a map of <placeholder, Value> to search for in the string
+ * @return A new String object with the placeholder replaced by the values.
+ */
+ private String replaceParameters(String str, Map<String, Object> parameters) {
+
+ if (parameters == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "NPW replace parameters: null parameter map. String: '%s'", str); //$NON-NLS-1$
+ return str;
+ } else if (str == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "NPW replace parameters: null template string"); //$NON-NLS-1$
+ return str;
+ }
+
+ for (Entry<String, Object> entry : parameters.entrySet()) {
+ if (entry != null && entry.getValue() instanceof String) {
+ Object value = entry.getValue();
+ if (value == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "NPW replace parameters: null value for key '%s' in template '%s'", //$NON-NLS-1$
+ entry.getKey(),
+ str);
+ } else {
+ str = str.replaceAll(entry.getKey(), (String) value);
+ }
+ }
+ }
+
+ return str;
+ }
+
+ private static class WorksetAdder implements Runnable {
+ private final IJavaProject mProject;
+ private final IWorkingSet[] mWorkingSets;
+
+ private WorksetAdder(IJavaProject project, IWorkingSet[] workingSets) {
+ mProject = project;
+ mWorkingSets = workingSets;
+ }
+
+ @Override
+ public void run() {
+ if (mWorkingSets.length > 0 && mProject != null
+ && mProject.exists()) {
+ PlatformUI.getWorkbench().getWorkingSetManager()
+ .addToWorkingSets(mProject, mWorkingSets);
+ }
+ }
+ }
+}