Home | History | Annotate | Download | only in newproject
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.wizards.newproject;
     18 
     19 import static com.android.SdkConstants.FN_PROJECT_PROPERTIES;
     20 import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_LIBRARY;
     21 
     22 import static org.eclipse.core.resources.IResource.DEPTH_ZERO;
     23 
     24 import com.android.SdkConstants;
     25 import com.android.annotations.NonNull;
     26 import com.android.annotations.Nullable;
     27 import com.android.annotations.VisibleForTesting;
     28 import com.android.ide.common.res2.ValueXmlHelper;
     29 import com.android.ide.common.xml.ManifestData;
     30 import com.android.ide.common.xml.XmlFormatStyle;
     31 import com.android.ide.eclipse.adt.AdtConstants;
     32 import com.android.ide.eclipse.adt.AdtPlugin;
     33 import com.android.ide.eclipse.adt.AdtUtils;
     34 import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
     35 import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
     36 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     37 import com.android.ide.eclipse.adt.internal.project.AndroidNature;
     38 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     39 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
     40 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     41 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     42 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
     43 import com.android.io.StreamException;
     44 import com.android.resources.Density;
     45 import com.android.sdklib.IAndroidTarget;
     46 import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
     47 
     48 import org.eclipse.core.filesystem.EFS;
     49 import org.eclipse.core.filesystem.IFileInfo;
     50 import org.eclipse.core.filesystem.IFileStore;
     51 import org.eclipse.core.filesystem.IFileSystem;
     52 import org.eclipse.core.resources.IContainer;
     53 import org.eclipse.core.resources.IFile;
     54 import org.eclipse.core.resources.IFolder;
     55 import org.eclipse.core.resources.IProject;
     56 import org.eclipse.core.resources.IProjectDescription;
     57 import org.eclipse.core.resources.IResource;
     58 import org.eclipse.core.resources.IResourceStatus;
     59 import org.eclipse.core.resources.IWorkspace;
     60 import org.eclipse.core.resources.IWorkspaceRunnable;
     61 import org.eclipse.core.resources.ResourcesPlugin;
     62 import org.eclipse.core.runtime.CoreException;
     63 import org.eclipse.core.runtime.IPath;
     64 import org.eclipse.core.runtime.IProgressMonitor;
     65 import org.eclipse.core.runtime.IStatus;
     66 import org.eclipse.core.runtime.NullProgressMonitor;
     67 import org.eclipse.core.runtime.OperationCanceledException;
     68 import org.eclipse.core.runtime.Path;
     69 import org.eclipse.core.runtime.Platform;
     70 import org.eclipse.core.runtime.Status;
     71 import org.eclipse.core.runtime.SubProgressMonitor;
     72 import org.eclipse.jdt.core.IAccessRule;
     73 import org.eclipse.jdt.core.IClasspathAttribute;
     74 import org.eclipse.jdt.core.IClasspathEntry;
     75 import org.eclipse.jdt.core.IJavaProject;
     76 import org.eclipse.jdt.core.JavaCore;
     77 import org.eclipse.jdt.core.JavaModelException;
     78 import org.eclipse.jface.dialogs.ErrorDialog;
     79 import org.eclipse.jface.dialogs.MessageDialog;
     80 import org.eclipse.jface.operation.IRunnableContext;
     81 import org.eclipse.swt.widgets.Display;
     82 import org.eclipse.ui.IWorkingSet;
     83 import org.eclipse.ui.PlatformUI;
     84 import org.eclipse.ui.actions.WorkspaceModifyOperation;
     85 
     86 import java.io.ByteArrayInputStream;
     87 import java.io.File;
     88 import java.io.FileInputStream;
     89 import java.io.FileNotFoundException;
     90 import java.io.IOException;
     91 import java.io.InputStream;
     92 import java.lang.reflect.InvocationTargetException;
     93 import java.net.MalformedURLException;
     94 import java.util.ArrayList;
     95 import java.util.HashMap;
     96 import java.util.List;
     97 import java.util.Map;
     98 import java.util.Map.Entry;
     99 import java.util.Set;
    100 
    101 /**
    102  * The actual project creator invoked from the New Project Wizard
    103  * <p/>
    104  * Note: this class is public so that it can be accessed from unit tests.
    105  * It is however an internal class. Its API may change without notice.
    106  * It should semantically be considered as a private final class.
    107  */
    108 public class NewProjectCreator  {
    109 
    110     private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS";          //$NON-NLS-1$
    111     private static final String PARAM_ACTIVITY = "ACTIVITY_NAME";                   //$NON-NLS-1$
    112     private static final String PARAM_APPLICATION = "APPLICATION_NAME";             //$NON-NLS-1$
    113     private static final String PARAM_PACKAGE = "PACKAGE";                          //$NON-NLS-1$
    114     private static final String PARAM_IMPORT_RESOURCE_CLASS = "IMPORT_RESOURCE_CLASS"; //$NON-NLS-1$
    115     private static final String PARAM_PROJECT = "PROJECT_NAME";                     //$NON-NLS-1$
    116     private static final String PARAM_STRING_NAME = "STRING_NAME";                  //$NON-NLS-1$
    117     private static final String PARAM_STRING_CONTENT = "STRING_CONTENT";            //$NON-NLS-1$
    118     private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT";            //$NON-NLS-1$
    119     private static final String PARAM_SAMPLE_LOCATION = "SAMPLE_LOCATION";          //$NON-NLS-1$
    120     private static final String PARAM_SOURCE = "SOURCE";                            //$NON-NLS-1$
    121     private static final String PARAM_SRC_FOLDER = "SRC_FOLDER";                    //$NON-NLS-1$
    122     private static final String PARAM_SDK_TARGET = "SDK_TARGET";                    //$NON-NLS-1$
    123     private static final String PARAM_IS_LIBRARY = "IS_LIBRARY";                    //$NON-NLS-1$
    124     private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION";          //$NON-NLS-1$
    125     // Warning: The expanded string PARAM_TEST_TARGET_PACKAGE must not contain the
    126     // string "PACKAGE" since it collides with the replacement of PARAM_PACKAGE.
    127     private static final String PARAM_TEST_TARGET_PACKAGE = "TEST_TARGET_PCKG";     //$NON-NLS-1$
    128     private static final String PARAM_TARGET_SELF = "TARGET_SELF";                  //$NON-NLS-1$
    129     private static final String PARAM_TARGET_MAIN = "TARGET_MAIN";                  //$NON-NLS-1$
    130     private static final String PARAM_TARGET_EXISTING = "TARGET_EXISTING";          //$NON-NLS-1$
    131     private static final String PARAM_REFERENCE_PROJECT = "REFERENCE_PROJECT";      //$NON-NLS-1$
    132 
    133     private static final String PH_ACTIVITIES = "ACTIVITIES";                       //$NON-NLS-1$
    134     private static final String PH_USES_SDK = "USES-SDK";                           //$NON-NLS-1$
    135     private static final String PH_INTENT_FILTERS = "INTENT_FILTERS";               //$NON-NLS-1$
    136     private static final String PH_STRINGS = "STRINGS";                             //$NON-NLS-1$
    137     private static final String PH_TEST_USES_LIBRARY = "TEST-USES-LIBRARY";         //$NON-NLS-1$
    138     private static final String PH_TEST_INSTRUMENTATION = "TEST-INSTRUMENTATION";   //$NON-NLS-1$
    139 
    140     private static final String BIN_DIRECTORY =
    141         SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP;
    142     private static final String BIN_CLASSES_DIRECTORY =
    143         SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP +
    144         SdkConstants.FD_CLASSES_OUTPUT + AdtConstants.WS_SEP;
    145     private static final String RES_DIRECTORY =
    146         SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP;
    147     private static final String ASSETS_DIRECTORY =
    148         SdkConstants.FD_ASSETS + AdtConstants.WS_SEP;
    149     private static final String DRAWABLE_DIRECTORY =
    150         SdkConstants.FD_RES_DRAWABLE + AdtConstants.WS_SEP;
    151     private static final String DRAWABLE_XHDPI_DIRECTORY =
    152             SdkConstants.FD_RES_DRAWABLE + '-' + Density.XHIGH.getResourceValue() +
    153             AdtConstants.WS_SEP;
    154     private static final String DRAWABLE_HDPI_DIRECTORY =
    155             SdkConstants.FD_RES_DRAWABLE + '-' + Density.HIGH.getResourceValue() +
    156             AdtConstants.WS_SEP;
    157     private static final String DRAWABLE_MDPI_DIRECTORY =
    158         SdkConstants.FD_RES_DRAWABLE + '-' + Density.MEDIUM.getResourceValue() +
    159         AdtConstants.WS_SEP;
    160     private static final String DRAWABLE_LDPI_DIRECTORY =
    161         SdkConstants.FD_RES_DRAWABLE + '-' + Density.LOW.getResourceValue() +
    162         AdtConstants.WS_SEP;
    163     private static final String LAYOUT_DIRECTORY =
    164         SdkConstants.FD_RES_LAYOUT + AdtConstants.WS_SEP;
    165     private static final String VALUES_DIRECTORY =
    166         SdkConstants.FD_RES_VALUES + AdtConstants.WS_SEP;
    167     private static final String GEN_SRC_DIRECTORY =
    168         SdkConstants.FD_GEN_SOURCES + AdtConstants.WS_SEP;
    169 
    170     private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
    171     private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
    172             + "AndroidManifest.template"; //$NON-NLS-1$
    173     private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY
    174             + "activity.template"; //$NON-NLS-1$
    175     private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
    176             + "uses-sdk.template"; //$NON-NLS-1$
    177     private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY
    178             + "launcher_intent_filter.template"; //$NON-NLS-1$
    179     private static final String TEMPLATE_TEST_USES_LIBRARY = TEMPLATES_DIRECTORY
    180             + "test_uses-library.template"; //$NON-NLS-1$
    181     private static final String TEMPLATE_TEST_INSTRUMENTATION = TEMPLATES_DIRECTORY
    182             + "test_instrumentation.template"; //$NON-NLS-1$
    183 
    184 
    185 
    186     private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY
    187             + "strings.template"; //$NON-NLS-1$
    188     private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY
    189             + "string.template"; //$NON-NLS-1$
    190     private static final String PROJECT_ICON = "ic_launcher.png"; //$NON-NLS-1$
    191     private static final String ICON_XHDPI = "ic_launcher_xhdpi.png"; //$NON-NLS-1$
    192     private static final String ICON_HDPI = "ic_launcher_hdpi.png"; //$NON-NLS-1$
    193     private static final String ICON_MDPI = "ic_launcher_mdpi.png"; //$NON-NLS-1$
    194     private static final String ICON_LDPI = "ic_launcher_ldpi.png"; //$NON-NLS-1$
    195 
    196     private static final String STRINGS_FILE = "strings.xml";       //$NON-NLS-1$
    197 
    198     private static final String STRING_RSRC_PREFIX = SdkConstants.STRING_PREFIX;
    199     private static final String STRING_APP_NAME = "app_name";       //$NON-NLS-1$
    200     private static final String STRING_HELLO_WORLD = "hello";       //$NON-NLS-1$
    201 
    202     private static final String[] DEFAULT_DIRECTORIES = new String[] {
    203             BIN_DIRECTORY, BIN_CLASSES_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
    204     private static final String[] RES_DIRECTORIES = new String[] {
    205             DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY };
    206     private static final String[] RES_DENSITY_ENABLED_DIRECTORIES = new String[] {
    207             DRAWABLE_XHDPI_DIRECTORY,
    208             DRAWABLE_HDPI_DIRECTORY, DRAWABLE_MDPI_DIRECTORY, DRAWABLE_LDPI_DIRECTORY,
    209             LAYOUT_DIRECTORY, VALUES_DIRECTORY };
    210 
    211     private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template";  //$NON-NLS-1$
    212     private static final String LAYOUT_TEMPLATE = "layout.template";            //$NON-NLS-1$
    213     private static final String MAIN_LAYOUT_XML = "main.xml";                   //$NON-NLS-1$
    214 
    215     private final NewProjectWizardState mValues;
    216     private final IRunnableContext mRunnableContext;
    217 
    218     /**
    219      * Creates a new {@linkplain NewProjectCreator}
    220      * @param values the wizard state with initial project parameters
    221      * @param runnableContext the context to run project creation in
    222      */
    223     public NewProjectCreator(NewProjectWizardState values, IRunnableContext runnableContext) {
    224         mValues = values;
    225         mRunnableContext = runnableContext;
    226     }
    227 
    228     /**
    229      * Before actually creating the project for a new project (as opposed to using an
    230      * existing project), we check if the target location is a directory that either does
    231      * not exist or is empty.
    232      *
    233      * If it's not empty, ask the user for confirmation.
    234      *
    235      * @param destination The destination folder where the new project is to be created.
    236      * @return True if the destination doesn't exist yet or is an empty directory or is
    237      *         accepted by the user.
    238      */
    239     private boolean validateNewProjectLocationIsEmpty(IPath destination) {
    240         File f = new File(destination.toOSString());
    241         if (f.isDirectory() && f.list().length > 0) {
    242             return AdtPlugin.displayPrompt("New Android Project",
    243                     "You are going to create a new Android Project in an existing, non-empty, directory. Are you sure you want to proceed?");
    244         }
    245         return true;
    246     }
    247 
    248     /**
    249      * Structure that describes all the information needed to create a project.
    250      * This is collected from the pages by {@link NewProjectCreator#createAndroidProjects()}
    251      * and then used by
    252      * {@link NewProjectCreator#createProjectAsync(IProgressMonitor, ProjectInfo, ProjectInfo)}.
    253      */
    254     private static class ProjectInfo {
    255         private final IProject mProject;
    256         private final IProjectDescription mDescription;
    257         private final Map<String, Object> mParameters;
    258         private final HashMap<String, String> mDictionary;
    259 
    260         public ProjectInfo(IProject project,
    261                 IProjectDescription description,
    262                 Map<String, Object> parameters,
    263                 HashMap<String, String> dictionary) {
    264                     mProject = project;
    265                     mDescription = description;
    266                     mParameters = parameters;
    267                     mDictionary = dictionary;
    268         }
    269 
    270         public IProject getProject() {
    271             return mProject;
    272         }
    273 
    274         public IProjectDescription getDescription() {
    275             return mDescription;
    276         }
    277 
    278         public Map<String, Object> getParameters() {
    279             return mParameters;
    280         }
    281 
    282         public HashMap<String, String> getDictionary() {
    283             return mDictionary;
    284         }
    285     }
    286 
    287     /**
    288      * Creates the android project.
    289      * @return True if the project could be created.
    290      */
    291     public boolean createAndroidProjects() {
    292         if (mValues.importProjects != null && !mValues.importProjects.isEmpty()) {
    293             return importProjects();
    294         }
    295 
    296         final ProjectInfo mainData = collectMainPageInfo();
    297         final ProjectInfo testData = collectTestPageInfo();
    298 
    299         // Create a monitored operation to create the actual project
    300         WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
    301             @Override
    302             protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
    303                 createProjectAsync(monitor, mainData, testData, null, true);
    304             }
    305         };
    306 
    307         // Run the operation in a different thread
    308         runAsyncOperation(op);
    309         return true;
    310     }
    311 
    312     /**
    313      * Creates the a plain Java project without typical android directories or an Android Nature.
    314      * This is intended for use by unit tests and not as a general-purpose Java project creator.
    315      * @return True if the project could be created.
    316      */
    317     @VisibleForTesting
    318     public boolean createJavaProjects() {
    319         if (mValues.importProjects != null && !mValues.importProjects.isEmpty()) {
    320             return importProjects();
    321         }
    322 
    323         final ProjectInfo mainData = collectMainPageInfo();
    324         final ProjectInfo testData = collectTestPageInfo();
    325 
    326         // Create a monitored operation to create the actual project
    327         WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
    328             @Override
    329             protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
    330                 createProjectAsync(monitor, mainData, testData, null, false);
    331             }
    332         };
    333 
    334         // Run the operation in a different thread
    335         runAsyncOperation(op);
    336         return true;
    337     }
    338 
    339     /**
    340      * Imports a list of projects
    341      */
    342     private boolean importProjects() {
    343         assert mValues.importProjects != null && !mValues.importProjects.isEmpty();
    344         IWorkspace workspace = ResourcesPlugin.getWorkspace();
    345 
    346         final List<ProjectInfo> projectData = new ArrayList<ProjectInfo>();
    347         for (ImportedProject p : mValues.importProjects) {
    348 
    349             // Compute the project name and the package name from the manifest
    350             ManifestData manifest = p.getManifest();
    351             if (manifest == null) {
    352                 continue;
    353             }
    354             String packageName = manifest.getPackage();
    355             String projectName = p.getProjectName();
    356             String minSdk = manifest.getMinSdkVersionString();
    357 
    358             final IProject project = workspace.getRoot().getProject(projectName);
    359             final IProjectDescription description =
    360                     workspace.newProjectDescription(project.getName());
    361 
    362             final Map<String, Object> parameters = new HashMap<String, Object>();
    363             parameters.put(PARAM_PROJECT, projectName);
    364             parameters.put(PARAM_PACKAGE, packageName);
    365             parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
    366             parameters.put(PARAM_IS_NEW_PROJECT, Boolean.FALSE);
    367             parameters.put(PARAM_SRC_FOLDER, SdkConstants.FD_SOURCES);
    368 
    369             parameters.put(PARAM_SDK_TARGET, p.getTarget());
    370 
    371             // TODO: Find out if these end up getting used in the import-path through the code!
    372             parameters.put(PARAM_MIN_SDK_VERSION, minSdk);
    373             parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
    374             final HashMap<String, String> dictionary = new HashMap<String, String>();
    375             dictionary.put(STRING_APP_NAME, mValues.applicationName);
    376 
    377             if (mValues.copyIntoWorkspace) {
    378                 parameters.put(PARAM_SOURCE, p.getLocation());
    379 
    380                 // TODO: Make sure it isn't *already* in the workspace!
    381                 //IPath defaultLocation = Platform.getLocation();
    382                 //if ((!mValues.useDefaultLocation || mValues.useExisting)
    383                 //        && !defaultLocation.isPrefixOf(path)) {
    384                 //IPath workspaceLocation = Platform.getLocation().append(projectName);
    385                 //description.setLocation(workspaceLocation);
    386                 // DON'T SET THE LOCATION: It's IMPLIED and in fact it will generate
    387                 // an error if you set it!
    388             } else {
    389                 // Create in place
    390                 description.setLocation(new Path(p.getLocation().getPath()));
    391             }
    392 
    393             projectData.add(new ProjectInfo(project, description, parameters, dictionary));
    394         }
    395 
    396         // Create a monitored operation to create the actual project
    397         WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
    398             @Override
    399             protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
    400                 createProjectAsync(monitor, null, null, projectData, true);
    401             }
    402         };
    403 
    404         // Run the operation in a different thread
    405         runAsyncOperation(op);
    406         return true;
    407     }
    408 
    409     /**
    410      * Collects all the parameters needed to create the main project.
    411      * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
    412      *    created because parameters are incorrect or should not be created because there
    413      *    is no main page.
    414      */
    415     private ProjectInfo collectMainPageInfo() {
    416         if (mValues.mode == Mode.TEST) {
    417             return null;
    418         }
    419 
    420         IWorkspace workspace = ResourcesPlugin.getWorkspace();
    421         final IProject project = workspace.getRoot().getProject(mValues.projectName);
    422         final IProjectDescription description = workspace.newProjectDescription(project.getName());
    423 
    424         final Map<String, Object> parameters = new HashMap<String, Object>();
    425         parameters.put(PARAM_PROJECT, mValues.projectName);
    426         parameters.put(PARAM_PACKAGE, mValues.packageName);
    427         parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
    428         parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
    429         parameters.put(PARAM_IS_NEW_PROJECT, mValues.mode == Mode.ANY && !mValues.useExisting);
    430         parameters.put(PARAM_SAMPLE_LOCATION, mValues.chosenSample);
    431         parameters.put(PARAM_SRC_FOLDER, mValues.sourceFolder);
    432         parameters.put(PARAM_SDK_TARGET, mValues.target);
    433         parameters.put(PARAM_MIN_SDK_VERSION, mValues.minSdk);
    434 
    435         if (mValues.createActivity) {
    436             parameters.put(PARAM_ACTIVITY, mValues.activityName);
    437         }
    438 
    439         // create a dictionary of string that will contain name+content.
    440         // we'll put all the strings into values/strings.xml
    441         final HashMap<String, String> dictionary = new HashMap<String, String>();
    442         dictionary.put(STRING_APP_NAME, mValues.applicationName);
    443 
    444         IPath path = new Path(mValues.projectLocation.getPath());
    445         IPath defaultLocation = Platform.getLocation();
    446         if ((!mValues.useDefaultLocation || mValues.useExisting)
    447                 && !defaultLocation.isPrefixOf(path)) {
    448             description.setLocation(path);
    449         }
    450 
    451         if (mValues.mode == Mode.ANY && !mValues.useExisting && !mValues.useDefaultLocation &&
    452                 !validateNewProjectLocationIsEmpty(path)) {
    453             return null;
    454         }
    455 
    456         return new ProjectInfo(project, description, parameters, dictionary);
    457     }
    458 
    459     /**
    460      * Collects all the parameters needed to create the test project.
    461      *
    462      * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
    463      *    created because parameters are incorrect or should not be created because there
    464      *    is no test page.
    465      */
    466     private ProjectInfo collectTestPageInfo() {
    467         if (mValues.mode != Mode.TEST && !mValues.createPairProject) {
    468             return null;
    469         }
    470 
    471         IWorkspace workspace = ResourcesPlugin.getWorkspace();
    472         String projectName =
    473                 mValues.mode == Mode.TEST ? mValues.projectName : mValues.testProjectName;
    474         final IProject project = workspace.getRoot().getProject(projectName);
    475         final IProjectDescription description = workspace.newProjectDescription(project.getName());
    476 
    477         final Map<String, Object> parameters = new HashMap<String, Object>();
    478 
    479         String pkg =
    480                 mValues.mode == Mode.TEST ? mValues.packageName : mValues.testPackageName;
    481 
    482         parameters.put(PARAM_PACKAGE, pkg);
    483         parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
    484         parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
    485         parameters.put(PARAM_IS_NEW_PROJECT, !mValues.useExisting);
    486         parameters.put(PARAM_SRC_FOLDER, mValues.sourceFolder);
    487         parameters.put(PARAM_SDK_TARGET, mValues.target);
    488         parameters.put(PARAM_MIN_SDK_VERSION, mValues.minSdk);
    489 
    490         // Test-specific parameters
    491         String testedPkg = mValues.createPairProject
    492                 ? mValues.packageName : mValues.testTargetPackageName;
    493         if (testedPkg == null) {
    494             assert mValues.testingSelf;
    495             testedPkg = pkg;
    496         }
    497 
    498         parameters.put(PARAM_TEST_TARGET_PACKAGE, testedPkg);
    499 
    500         if (mValues.testingSelf) {
    501             parameters.put(PARAM_TARGET_SELF, true);
    502         } else {
    503             parameters.put(PARAM_TARGET_EXISTING, true);
    504             parameters.put(PARAM_REFERENCE_PROJECT, mValues.testedProject);
    505         }
    506 
    507         if (mValues.createPairProject) {
    508             parameters.put(PARAM_TARGET_MAIN, true);
    509         }
    510 
    511         // create a dictionary of string that will contain name+content.
    512         // we'll put all the strings into values/strings.xml
    513         final HashMap<String, String> dictionary = new HashMap<String, String>();
    514         dictionary.put(STRING_APP_NAME, mValues.testApplicationName);
    515 
    516         // Use the same logic to determine test project location as in
    517         // ApplicationInfoPage#validateTestProjectLocation
    518         IPath path = new Path(mValues.projectLocation.getPath());
    519         path = path.removeLastSegments(1).append(mValues.testProjectName);
    520         IPath defaultLocation = Platform.getLocation();
    521         if ((!mValues.useDefaultLocation || mValues.useExisting)
    522                 && !path.equals(defaultLocation)) {
    523             description.setLocation(path);
    524         }
    525 
    526         if (!mValues.useExisting && !mValues.useDefaultLocation &&
    527                 !validateNewProjectLocationIsEmpty(path)) {
    528             return null;
    529         }
    530 
    531         return new ProjectInfo(project, description, parameters, dictionary);
    532     }
    533 
    534     /**
    535      * Runs the operation in a different thread and display generated
    536      * exceptions.
    537      *
    538      * @param op The asynchronous operation to run.
    539      */
    540     private void runAsyncOperation(WorkspaceModifyOperation op) {
    541         try {
    542             mRunnableContext.run(true /* fork */, true /* cancelable */, op);
    543         } catch (InvocationTargetException e) {
    544 
    545             AdtPlugin.log(e, "New Project Wizard failed");
    546 
    547             // The runnable threw an exception
    548             Throwable t = e.getTargetException();
    549             if (t instanceof CoreException) {
    550                 CoreException core = (CoreException) t;
    551                 if (core.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
    552                     // The error indicates the file system is not case sensitive
    553                     // and there's a resource with a similar name.
    554                     MessageDialog.openError(AdtPlugin.getShell(),
    555                             "Error", "Error: Case Variant Exists");
    556                 } else {
    557                     ErrorDialog.openError(AdtPlugin.getShell(),
    558                             "Error", core.getMessage(), core.getStatus());
    559                 }
    560             } else {
    561                 // Some other kind of exception
    562                 String msg = t.getMessage();
    563                 Throwable t1 = t;
    564                 while (msg == null && t1.getCause() != null) {
    565                     msg = t1.getMessage();
    566                     t1 = t1.getCause();
    567                 }
    568                 if (msg == null) {
    569                     msg = t.toString();
    570                 }
    571                 MessageDialog.openError(AdtPlugin.getShell(), "Error", msg);
    572             }
    573             e.printStackTrace();
    574         } catch (InterruptedException e) {
    575             e.printStackTrace();
    576         }
    577     }
    578 
    579     /**
    580      * Creates the actual project(s). This is run asynchronously in a different thread.
    581      *
    582      * @param monitor An existing monitor.
    583      * @param mainData Data for main project. Can be null.
    584      * @param isAndroidProject true if the project is to be set up as a full Android project; false
    585      * for a plain Java project.
    586      * @throws InvocationTargetException to wrap any unmanaged exception and
    587      *         return it to the calling thread. The method can fail if it fails
    588      *         to create or modify the project or if it is canceled by the user.
    589      */
    590     private void createProjectAsync(IProgressMonitor monitor,
    591             ProjectInfo mainData,
    592             ProjectInfo testData,
    593             List<ProjectInfo> importData,
    594             boolean isAndroidProject)
    595                 throws InvocationTargetException {
    596         monitor.beginTask("Create Android Project", 100);
    597         try {
    598             IProject mainProject = null;
    599 
    600             if (mainData != null) {
    601                 mainProject = createEclipseProject(
    602                         new SubProgressMonitor(monitor, 50),
    603                         mainData.getProject(),
    604                         mainData.getDescription(),
    605                         mainData.getParameters(),
    606                         mainData.getDictionary(),
    607                         null,
    608                         isAndroidProject);
    609 
    610                 if (mainProject != null) {
    611                     final IJavaProject javaProject = JavaCore.create(mainProject);
    612                     Display.getDefault().syncExec(new WorksetAdder(javaProject,
    613                             mValues.workingSets));
    614                 }
    615             }
    616 
    617             if (testData != null) {
    618                 Map<String, Object> parameters = testData.getParameters();
    619                 if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) {
    620                     parameters.put(PARAM_REFERENCE_PROJECT, mainProject);
    621                 }
    622 
    623                 IProject testProject = createEclipseProject(
    624                         new SubProgressMonitor(monitor, 50),
    625                         testData.getProject(),
    626                         testData.getDescription(),
    627                         parameters,
    628                         testData.getDictionary(),
    629                         null,
    630                         isAndroidProject);
    631                 if (testProject != null) {
    632                     final IJavaProject javaProject = JavaCore.create(testProject);
    633                     Display.getDefault().syncExec(new WorksetAdder(javaProject,
    634                             mValues.workingSets));
    635                 }
    636             }
    637 
    638             if (importData != null) {
    639                 for (final ProjectInfo data : importData) {
    640                     ProjectPopulator projectPopulator = null;
    641                     if (mValues.copyIntoWorkspace) {
    642                         projectPopulator = new ProjectPopulator() {
    643                             @Override
    644                             public void populate(IProject project) {
    645                                 // Copy
    646                                 IFileSystem fileSystem = EFS.getLocalFileSystem();
    647                                 File source = (File) data.getParameters().get(PARAM_SOURCE);
    648                                 IFileStore sourceDir = new ReadWriteFileStore(
    649                                         fileSystem.getStore(source.toURI()));
    650                                 IFileStore destDir = new ReadWriteFileStore(
    651                                         fileSystem.getStore(AdtUtils.getAbsolutePath(project)));
    652                                 try {
    653                                     sourceDir.copy(destDir, EFS.OVERWRITE, null);
    654                                 } catch (CoreException e) {
    655                                     AdtPlugin.log(e, null);
    656                                 }
    657                             }
    658                         };
    659                     }
    660                     IProject project = createEclipseProject(
    661                             new SubProgressMonitor(monitor, 50),
    662                             data.getProject(),
    663                             data.getDescription(),
    664                             data.getParameters(),
    665                             data.getDictionary(),
    666                             projectPopulator,
    667                             isAndroidProject);
    668                     if (project != null) {
    669                         final IJavaProject javaProject = JavaCore.create(project);
    670                         Display.getDefault().syncExec(new WorksetAdder(javaProject,
    671                                 mValues.workingSets));
    672                         ProjectHelper.enforcePreferredCompilerCompliance(javaProject);
    673                     }
    674                 }
    675             }
    676         } catch (CoreException e) {
    677             throw new InvocationTargetException(e);
    678         } catch (IOException e) {
    679             throw new InvocationTargetException(e);
    680         } catch (StreamException e) {
    681             throw new InvocationTargetException(e);
    682         } finally {
    683             monitor.done();
    684         }
    685     }
    686 
    687     /** Handler which can write contents into a project */
    688     public interface ProjectPopulator {
    689         /**
    690          * Add contents into the given project
    691          *
    692          * @param project the project to write into
    693          * @throws InvocationTargetException if anything goes wrong
    694          */
    695         public void populate(IProject project) throws InvocationTargetException;
    696     }
    697 
    698     /**
    699      * Creates the actual project, sets its nature and adds the required folders
    700      * and files to it. This is run asynchronously in a different thread.
    701      *
    702      * @param monitor An existing monitor.
    703      * @param project The project to create.
    704      * @param description A description of the project.
    705      * @param parameters Template parameters.
    706      * @param dictionary String definition.
    707      * @param isAndroidProject true if the project is to be set up as a full Android project; false
    708      * for a plain Java project.
    709      * @return The project newly created
    710      * @throws StreamException
    711      */
    712     private IProject createEclipseProject(
    713             @NonNull IProgressMonitor monitor,
    714             @NonNull IProject project,
    715             @NonNull IProjectDescription description,
    716             @NonNull Map<String, Object> parameters,
    717             @Nullable Map<String, String> dictionary,
    718             @Nullable ProjectPopulator projectPopulator,
    719             boolean isAndroidProject)
    720                 throws CoreException, IOException, StreamException {
    721 
    722         // get the project target
    723         IAndroidTarget target = (IAndroidTarget) parameters.get(PARAM_SDK_TARGET);
    724         boolean legacy = isAndroidProject && target.getVersion().getApiLevel() < 4;
    725 
    726         // Create project and open it
    727         project.create(description, new SubProgressMonitor(monitor, 10));
    728         if (monitor.isCanceled()) throw new OperationCanceledException();
    729 
    730         project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10));
    731 
    732         // Add the Java and android nature to the project
    733         AndroidNature.setupProjectNatures(project, monitor, isAndroidProject);
    734 
    735         // Create folders in the project if they don't already exist
    736         addDefaultDirectories(project, AdtConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
    737         String[] sourceFolders;
    738         if (isAndroidProject) {
    739             sourceFolders = new String[] {
    740                     (String) parameters.get(PARAM_SRC_FOLDER),
    741                     GEN_SRC_DIRECTORY
    742                 };
    743         } else {
    744             sourceFolders = new String[] {
    745                     (String) parameters.get(PARAM_SRC_FOLDER)
    746                 };
    747         }
    748         addDefaultDirectories(project, AdtConstants.WS_ROOT, sourceFolders, monitor);
    749 
    750         // Create the resource folders in the project if they don't already exist.
    751         if (legacy) {
    752             addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
    753         } else {
    754             addDefaultDirectories(project, RES_DIRECTORY, RES_DENSITY_ENABLED_DIRECTORIES, monitor);
    755         }
    756 
    757         if (projectPopulator != null) {
    758             try {
    759                 projectPopulator.populate(project);
    760             } catch (InvocationTargetException ite) {
    761                 AdtPlugin.log(ite, null);
    762             }
    763         }
    764 
    765         // Setup class path: mark folders as source folders
    766         IJavaProject javaProject = JavaCore.create(project);
    767         setupSourceFolders(javaProject, sourceFolders, monitor);
    768 
    769         if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
    770             // Create files in the project if they don't already exist
    771             addManifest(project, parameters, dictionary, monitor);
    772 
    773             // add the default app icon
    774             addIcon(project, legacy, monitor);
    775 
    776             // Create the default package components
    777             addSampleCode(project, sourceFolders[0], parameters, dictionary, monitor);
    778 
    779             // add the string definition file if needed
    780             if (dictionary != null && dictionary.size() > 0) {
    781                 addStringDictionaryFile(project, dictionary, monitor);
    782             }
    783 
    784             // add the default proguard config
    785             File libFolder = new File((String) parameters.get(PARAM_SDK_TOOLS_DIR),
    786                     SdkConstants.FD_LIB);
    787             addLocalFile(project,
    788                     new File(libFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
    789                     // Write ProGuard config files with the extension .pro which
    790                     // is what is used in the ProGuard documentation and samples
    791                     SdkConstants.FN_PROJECT_PROGUARD_FILE,
    792                     monitor);
    793 
    794             // Set output location
    795             javaProject.setOutputLocation(project.getFolder(BIN_CLASSES_DIRECTORY).getFullPath(),
    796                     monitor);
    797         }
    798 
    799         File sampleDir = (File) parameters.get(PARAM_SAMPLE_LOCATION);
    800         if (sampleDir != null) {
    801             // Copy project
    802             copySampleCode(project, sampleDir, parameters, dictionary, monitor);
    803         }
    804 
    805         // Create the reference to the target project
    806         if (parameters.containsKey(PARAM_REFERENCE_PROJECT)) {
    807             IProject refProject = (IProject) parameters.get(PARAM_REFERENCE_PROJECT);
    808             if (refProject != null) {
    809                 IProjectDescription desc = project.getDescription();
    810 
    811                 // Add out reference to the existing project reference.
    812                 // We just created a project with no references so we don't need to expand
    813                 // the currently-empty current list.
    814                 desc.setReferencedProjects(new IProject[] { refProject });
    815 
    816                 project.setDescription(desc, IResource.KEEP_HISTORY,
    817                         new SubProgressMonitor(monitor, 10));
    818 
    819                 IClasspathEntry entry = JavaCore.newProjectEntry(
    820                         refProject.getFullPath(), //path
    821                         new IAccessRule[0], //accessRules
    822                         false, //combineAccessRules
    823                         new IClasspathAttribute[0], //extraAttributes
    824                         false //isExported
    825 
    826                 );
    827                 ProjectHelper.addEntryToClasspath(javaProject, entry);
    828             }
    829         }
    830 
    831         if (isAndroidProject) {
    832             Sdk.getCurrent().initProject(project, target);
    833         }
    834 
    835         // Fix the project to make sure all properties are as expected.
    836         // Necessary for existing projects and good for new ones to.
    837         ProjectHelper.fixProject(project);
    838 
    839         Boolean isLibraryProject = (Boolean) parameters.get(PARAM_IS_LIBRARY);
    840         if (isLibraryProject != null && isLibraryProject.booleanValue()
    841                 && Sdk.getCurrent() != null && project.isOpen()) {
    842             ProjectState state = Sdk.getProjectState(project);
    843             if (state != null) {
    844                 // make a working copy of the properties
    845                 ProjectPropertiesWorkingCopy properties =
    846                         state.getProperties().makeWorkingCopy();
    847 
    848                 properties.setProperty(PROPERTY_LIBRARY, Boolean.TRUE.toString());
    849                 try {
    850                     properties.save();
    851                     IResource projectProp = project.findMember(FN_PROJECT_PROPERTIES);
    852                     if (projectProp != null) {
    853                         projectProp.refreshLocal(DEPTH_ZERO, new NullProgressMonitor());
    854                     }
    855                 } catch (Exception e) {
    856                     String msg = String.format(
    857                             "Failed to save %1$s for project %2$s",
    858                             SdkConstants.FN_PROJECT_PROPERTIES, project.getName());
    859                     AdtPlugin.log(e, msg);
    860                 }
    861             }
    862         }
    863 
    864         return project;
    865     }
    866 
    867     /**
    868      * Creates a new project
    869      *
    870      * @param monitor An existing monitor.
    871      * @param project The project to create.
    872      * @param target the build target to associate with the project
    873      * @param projectPopulator a handler for writing the template contents
    874      * @param isLibrary whether this project should be marked as a library project
    875      * @param projectLocation the location to write the project into
    876      * @param workingSets Eclipse working sets, if any, to add the project to
    877      * @throws CoreException if anything goes wrong
    878      */
    879     public static void create(
    880             @NonNull IProgressMonitor monitor,
    881             @NonNull final IProject project,
    882             @NonNull IAndroidTarget target,
    883             @Nullable final ProjectPopulator projectPopulator,
    884             boolean isLibrary,
    885             @NonNull String projectLocation,
    886             @NonNull final IWorkingSet[] workingSets)
    887                 throws CoreException {
    888         final NewProjectCreator creator = new NewProjectCreator(null, null);
    889 
    890         final Map<String, String> dictionary = null;
    891         final Map<String, Object> parameters = new HashMap<String, Object>();
    892         parameters.put(PARAM_SDK_TARGET, target);
    893         parameters.put(PARAM_SRC_FOLDER, SdkConstants.FD_SOURCES);
    894         parameters.put(PARAM_IS_NEW_PROJECT, false);
    895         parameters.put(PARAM_SAMPLE_LOCATION, null);
    896         parameters.put(PARAM_IS_LIBRARY, isLibrary);
    897 
    898         IWorkspace workspace = ResourcesPlugin.getWorkspace();
    899         final IProjectDescription description = workspace.newProjectDescription(project.getName());
    900 
    901         if (projectLocation != null) {
    902             IPath path = new Path(projectLocation);
    903             IPath parent = new Path(path.toFile().getParent());
    904             IPath workspaceLocation = Platform.getLocation();
    905             if (!workspaceLocation.equals(parent)) {
    906                 description.setLocation(path);
    907             }
    908         }
    909 
    910         IWorkspaceRunnable workspaceRunnable = new IWorkspaceRunnable() {
    911             @Override
    912             public void run(IProgressMonitor submonitor) throws CoreException {
    913                 try {
    914                     creator.createEclipseProject(submonitor, project, description, parameters,
    915                             dictionary, projectPopulator, true);
    916                 } catch (IOException e) {
    917                     throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    918                             "Unexpected error while creating project", e));
    919                 } catch (StreamException e) {
    920                     throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    921                             "Unexpected error while creating project", e));
    922                 }
    923                 if (workingSets != null && workingSets.length > 0) {
    924                     IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
    925                     if (javaProject != null) {
    926                         Display.getDefault().syncExec(new WorksetAdder(javaProject,
    927                                 workingSets));
    928                     }
    929                 }
    930             }
    931         };
    932 
    933         ResourcesPlugin.getWorkspace().run(workspaceRunnable, monitor);
    934     }
    935 
    936     /**
    937      * Adds default directories to the project.
    938      *
    939      * @param project The Java Project to update.
    940      * @param parentFolder The path of the parent folder. Must end with a
    941      *        separator.
    942      * @param folders Folders to be added.
    943      * @param monitor An existing monitor.
    944      * @throws CoreException if the method fails to create the directories in
    945      *         the project.
    946      */
    947     private void addDefaultDirectories(IProject project, String parentFolder,
    948             String[] folders, IProgressMonitor monitor) throws CoreException {
    949         for (String name : folders) {
    950             if (name.length() > 0) {
    951                 IFolder folder = project.getFolder(parentFolder + name);
    952                 if (!folder.exists()) {
    953                     folder.create(true /* force */, true /* local */,
    954                             new SubProgressMonitor(monitor, 10));
    955                 }
    956             }
    957         }
    958     }
    959 
    960     /**
    961      * Adds the manifest to the project.
    962      *
    963      * @param project The Java Project to update.
    964      * @param parameters Template Parameters.
    965      * @param dictionary String List to be added to a string definition
    966      *        file. This map will be filled by this method.
    967      * @param monitor An existing monitor.
    968      * @throws CoreException if the method fails to update the project.
    969      * @throws IOException if the method fails to create the files in the
    970      *         project.
    971      */
    972     private void addManifest(IProject project, Map<String, Object> parameters,
    973             Map<String, String> dictionary, IProgressMonitor monitor)
    974             throws CoreException, IOException {
    975 
    976         // get IFile to the manifest and check if it's not already there.
    977         IFile file = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
    978         if (!file.exists()) {
    979 
    980             // Read manifest template
    981             String manifestTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_MANIFEST);
    982 
    983             // Replace all keyword parameters
    984             manifestTemplate = replaceParameters(manifestTemplate, parameters);
    985 
    986             if (manifestTemplate == null) {
    987                 // Inform the user there will be not manifest.
    988                 AdtPlugin.logAndPrintError(null, "Create Project" /*TAG*/,
    989                         "Failed to generate the Android manifest. Missing template %s",
    990                         TEMPLATE_MANIFEST);
    991                 // Abort now, there's no need to continue
    992                 return;
    993             }
    994 
    995             if (parameters.containsKey(PARAM_ACTIVITY)) {
    996                 // now get the activity template
    997                 String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES);
    998 
    999                 // If the activity name doesn't contain any dot, it's in the form
   1000                 // "ClassName" and we need to expand it to ".ClassName" in the XML.
   1001                 String name = (String) parameters.get(PARAM_ACTIVITY);
   1002                 if (name.indexOf('.') == -1) {
   1003                     // Duplicate the parameters map to avoid changing the caller
   1004                     parameters = new HashMap<String, Object>(parameters);
   1005                     parameters.put(PARAM_ACTIVITY, "." + name); //$NON-NLS-1$
   1006                 }
   1007 
   1008                 // Replace all keyword parameters to make main activity.
   1009                 String activities = replaceParameters(activityTemplate, parameters);
   1010 
   1011                 // set the intent.
   1012                 String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER);
   1013 
   1014                 if (activities != null) {
   1015                     if (intent != null) {
   1016                         // set the intent to the main activity
   1017                         activities = activities.replaceAll(PH_INTENT_FILTERS, intent);
   1018                     }
   1019 
   1020                     // set the activity(ies) in the manifest
   1021                     manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, activities);
   1022                 }
   1023             } else {
   1024                 // remove the activity(ies) from the manifest
   1025                 manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, "");  //$NON-NLS-1$
   1026             }
   1027 
   1028             // Handle the case of the test projects
   1029             if (parameters.containsKey(PARAM_TEST_TARGET_PACKAGE)) {
   1030                 // Set the uses-library needed by the test project
   1031                 String usesLibrary = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_USES_LIBRARY);
   1032                 if (usesLibrary != null) {
   1033                     manifestTemplate = manifestTemplate.replaceAll(
   1034                             PH_TEST_USES_LIBRARY, usesLibrary);
   1035                 }
   1036 
   1037                 // Set the instrumentation element needed by the test project
   1038                 String instru = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_INSTRUMENTATION);
   1039                 if (instru != null) {
   1040                     manifestTemplate = manifestTemplate.replaceAll(
   1041                             PH_TEST_INSTRUMENTATION, instru);
   1042                 }
   1043 
   1044                 // Replace PARAM_TEST_TARGET_PACKAGE itself now
   1045                 manifestTemplate = replaceParameters(manifestTemplate, parameters);
   1046 
   1047             } else {
   1048                 // remove the unused entries
   1049                 manifestTemplate = manifestTemplate.replaceAll(PH_TEST_USES_LIBRARY, "");     //$NON-NLS-1$
   1050                 manifestTemplate = manifestTemplate.replaceAll(PH_TEST_INSTRUMENTATION, "");  //$NON-NLS-1$
   1051             }
   1052 
   1053             String minSdkVersion = (String) parameters.get(PARAM_MIN_SDK_VERSION);
   1054             if (minSdkVersion != null && minSdkVersion.length() > 0) {
   1055                 String usesSdkTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_USES_SDK);
   1056                 if (usesSdkTemplate != null) {
   1057                     String usesSdk = replaceParameters(usesSdkTemplate, parameters);
   1058                     manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, usesSdk);
   1059                 }
   1060             } else {
   1061                 manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, "");
   1062             }
   1063 
   1064             // Reformat the file according to the user's formatting settings
   1065             manifestTemplate = reformat(XmlFormatStyle.MANIFEST, manifestTemplate);
   1066 
   1067             // Save in the project as UTF-8
   1068             InputStream stream = new ByteArrayInputStream(
   1069                     manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$
   1070             file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
   1071         }
   1072     }
   1073 
   1074     /**
   1075      * Adds the string resource file.
   1076      *
   1077      * @param project The Java Project to update.
   1078      * @param strings The list of strings to be added to the string file.
   1079      * @param monitor An existing monitor.
   1080      * @throws CoreException if the method fails to update the project.
   1081      * @throws IOException if the method fails to create the files in the
   1082      *         project.
   1083      */
   1084     private void addStringDictionaryFile(IProject project,
   1085             Map<String, String> strings, IProgressMonitor monitor)
   1086             throws CoreException, IOException {
   1087 
   1088         // create the IFile object and check if the file doesn't already exist.
   1089         IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
   1090                                      + VALUES_DIRECTORY + AdtConstants.WS_SEP + STRINGS_FILE);
   1091         if (!file.exists()) {
   1092             // get the Strings.xml template
   1093             String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS);
   1094 
   1095             // get the template for one string
   1096             String stringTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRING);
   1097 
   1098             // get all the string names
   1099             Set<String> stringNames = strings.keySet();
   1100 
   1101             // loop on it and create the string definitions
   1102             StringBuilder stringNodes = new StringBuilder();
   1103             for (String key : stringNames) {
   1104                 // get the value from the key
   1105                 String value = strings.get(key);
   1106 
   1107                 // Escape values if necessary
   1108                 value = ValueXmlHelper.escapeResourceString(value);
   1109 
   1110                 // place them in the template
   1111                 String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key);
   1112                 stringDef = stringDef.replace(PARAM_STRING_CONTENT, value);
   1113 
   1114                 // append to the other string
   1115                 if (stringNodes.length() > 0) {
   1116                     stringNodes.append('\n');
   1117                 }
   1118                 stringNodes.append(stringDef);
   1119             }
   1120 
   1121             // put the string nodes in the Strings.xml template
   1122             stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS,
   1123                                                                         stringNodes.toString());
   1124 
   1125             // reformat the file according to the user's formatting settings
   1126             stringDefinitionTemplate = reformat(XmlFormatStyle.RESOURCE, stringDefinitionTemplate);
   1127 
   1128             // write the file as UTF-8
   1129             InputStream stream = new ByteArrayInputStream(
   1130                     stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$
   1131             file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
   1132         }
   1133     }
   1134 
   1135     /** Reformats the given contents with the current formatting settings */
   1136     private String reformat(XmlFormatStyle style, String contents) {
   1137         if (AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
   1138             EclipseXmlFormatPreferences formatPrefs = EclipseXmlFormatPreferences.create();
   1139             return EclipseXmlPrettyPrinter.prettyPrint(contents, formatPrefs, style,
   1140                     null /*lineSeparator*/);
   1141         } else {
   1142             return contents;
   1143         }
   1144     }
   1145 
   1146     /**
   1147      * Adds default application icon to the project.
   1148      *
   1149      * @param project The Java Project to update.
   1150      * @param legacy whether we're running in legacy mode (no density support)
   1151      * @param monitor An existing monitor.
   1152      * @throws CoreException if the method fails to update the project.
   1153      */
   1154     private void addIcon(IProject project, boolean legacy, IProgressMonitor monitor)
   1155             throws CoreException {
   1156         if (legacy) { // density support
   1157             // do medium density icon only, in the default drawable folder.
   1158             IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
   1159                     + DRAWABLE_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
   1160             if (!file.exists()) {
   1161                 addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
   1162             }
   1163         } else {
   1164             // do all 4 icons.
   1165             IFile file;
   1166 
   1167             // extra high density
   1168             file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
   1169                     + DRAWABLE_XHDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
   1170             if (!file.exists()) {
   1171                 addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_XHDPI), monitor);
   1172             }
   1173 
   1174             // high density
   1175             file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
   1176                     + DRAWABLE_HDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
   1177             if (!file.exists()) {
   1178                 addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_HDPI), monitor);
   1179             }
   1180 
   1181             // medium density
   1182             file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
   1183                     + DRAWABLE_MDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
   1184             if (!file.exists()) {
   1185                 addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
   1186             }
   1187 
   1188             // low density
   1189             file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
   1190                     + DRAWABLE_LDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
   1191             if (!file.exists()) {
   1192                 addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_LDPI), monitor);
   1193             }
   1194         }
   1195     }
   1196 
   1197     /**
   1198      * Creates a file from a data source.
   1199      * @param dest the file to write
   1200      * @param source the content of the file.
   1201      * @param monitor the progress monitor
   1202      * @throws CoreException
   1203      */
   1204     private void addFile(IFile dest, byte[] source, IProgressMonitor monitor) throws CoreException {
   1205         if (source != null) {
   1206             // Save in the project
   1207             InputStream stream = new ByteArrayInputStream(source);
   1208             dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
   1209         }
   1210     }
   1211 
   1212     /**
   1213      * Creates the package folder and copies the sample code in the project.
   1214      *
   1215      * @param project The Java Project to update.
   1216      * @param parameters Template Parameters.
   1217      * @param dictionary String List to be added to a string definition
   1218      *        file. This map will be filled by this method.
   1219      * @param monitor An existing monitor.
   1220      * @throws CoreException if the method fails to update the project.
   1221      * @throws IOException if the method fails to create the files in the
   1222      *         project.
   1223      */
   1224     private void addSampleCode(IProject project, String sourceFolder,
   1225             Map<String, Object> parameters, Map<String, String> dictionary,
   1226             IProgressMonitor monitor) throws CoreException, IOException {
   1227         // create the java package directories.
   1228         IFolder pkgFolder = project.getFolder(sourceFolder);
   1229         String packageName = (String) parameters.get(PARAM_PACKAGE);
   1230 
   1231         // The PARAM_ACTIVITY key will be absent if no activity should be created,
   1232         // in which case activityName will be null.
   1233         String activityName = (String) parameters.get(PARAM_ACTIVITY);
   1234 
   1235         Map<String, Object> java_activity_parameters = new HashMap<String, Object>(parameters);
   1236         java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, "");  //$NON-NLS-1$
   1237 
   1238         if (activityName != null) {
   1239 
   1240             String resourcePackageClass = null;
   1241 
   1242             // An activity name can be of the form ".package.Class", ".Class" or FQDN.
   1243             // The initial dot is ignored, as it is always added later in the templates.
   1244             int lastDotIndex = activityName.lastIndexOf('.');
   1245 
   1246             if (lastDotIndex != -1) {
   1247 
   1248                 // Resource class
   1249                 if (lastDotIndex > 0) {
   1250                     resourcePackageClass = packageName + '.' + SdkConstants.FN_RESOURCE_BASE;
   1251                 }
   1252 
   1253                 // Package name
   1254                 if (activityName.startsWith(".")) {  //$NON-NLS-1$
   1255                     packageName += activityName.substring(0, lastDotIndex);
   1256                 } else {
   1257                     packageName = activityName.substring(0, lastDotIndex);
   1258                 }
   1259 
   1260                 // Activity Class name
   1261                 activityName = activityName.substring(lastDotIndex + 1);
   1262             }
   1263 
   1264             java_activity_parameters.put(PARAM_ACTIVITY, activityName);
   1265             java_activity_parameters.put(PARAM_PACKAGE, packageName);
   1266             if (resourcePackageClass != null) {
   1267                 String importResourceClass = "\nimport " + resourcePackageClass + ";";  //$NON-NLS-1$ // $NON-NLS-2$
   1268                 java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, importResourceClass);
   1269             }
   1270         }
   1271 
   1272         String[] components = packageName.split(AdtConstants.RE_DOT);
   1273         for (String component : components) {
   1274             pkgFolder = pkgFolder.getFolder(component);
   1275             if (!pkgFolder.exists()) {
   1276                 pkgFolder.create(true /* force */, true /* local */,
   1277                         new SubProgressMonitor(monitor, 10));
   1278             }
   1279         }
   1280 
   1281         if (activityName != null) {
   1282             // create the main activity Java file
   1283             String activityJava = activityName + SdkConstants.DOT_JAVA;
   1284             IFile file = pkgFolder.getFile(activityJava);
   1285             if (!file.exists()) {
   1286                 copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor, false);
   1287             }
   1288 
   1289             // create the layout file (if we're creating an
   1290             IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY);
   1291             file = layoutfolder.getFile(MAIN_LAYOUT_XML);
   1292             if (!file.exists()) {
   1293                 copyFile(LAYOUT_TEMPLATE, file, parameters, monitor, true);
   1294                 dictionary.put(STRING_HELLO_WORLD, String.format("Hello World, %1$s!",
   1295                         activityName));
   1296             }
   1297         }
   1298     }
   1299 
   1300     private void copySampleCode(IProject project, File sampleDir,
   1301             Map<String, Object> parameters, Map<String, String> dictionary,
   1302             IProgressMonitor monitor) throws CoreException {
   1303         // Copy the sampleDir into the project directory recursively
   1304         IFileSystem fileSystem = EFS.getLocalFileSystem();
   1305         IFileStore sourceDir = new ReadWriteFileStore(
   1306                                         fileSystem.getStore(sampleDir.toURI()));
   1307         IFileStore destDir   = new ReadWriteFileStore(
   1308                                         fileSystem.getStore(AdtUtils.getAbsolutePath(project)));
   1309         sourceDir.copy(destDir, EFS.OVERWRITE, null);
   1310     }
   1311 
   1312     /**
   1313      * In a sample we never duplicate source files as read-only.
   1314      * This creates a store that read files attributes and doesn't set the r-o flag.
   1315      */
   1316     private static class ReadWriteFileStore extends FileStoreAdapter {
   1317 
   1318         public ReadWriteFileStore(IFileStore store) {
   1319             super(store);
   1320         }
   1321 
   1322         // Override when reading attributes
   1323         @Override
   1324         public IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException {
   1325             IFileInfo info = super.fetchInfo(options, monitor);
   1326             info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, false);
   1327             return info;
   1328         }
   1329 
   1330         // Override when writing attributes
   1331         @Override
   1332         public void putInfo(IFileInfo info, int options, IProgressMonitor storeMonitor)
   1333                 throws CoreException {
   1334             info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, false);
   1335             super.putInfo(info, options, storeMonitor);
   1336         }
   1337 
   1338         @Deprecated
   1339         @Override
   1340         public IFileStore getChild(IPath path) {
   1341             IFileStore child = super.getChild(path);
   1342             if (!(child instanceof ReadWriteFileStore)) {
   1343                 child = new ReadWriteFileStore(child);
   1344             }
   1345             return child;
   1346         }
   1347 
   1348         @Override
   1349         public IFileStore getChild(String name) {
   1350             return new ReadWriteFileStore(super.getChild(name));
   1351         }
   1352     }
   1353 
   1354     /**
   1355      * Adds a file to the root of the project
   1356      * @param project the project to add the file to.
   1357      * @param destName the name to write the file as
   1358      * @param source the file to add. It'll keep the same filename once copied into the project.
   1359      * @param monitor the monitor to report progress to
   1360      * @throws FileNotFoundException if the file to be added does not exist
   1361      * @throws CoreException if writing the file does not work
   1362      */
   1363     public static void addLocalFile(IProject project, File source, String destName,
   1364             IProgressMonitor monitor) throws FileNotFoundException, CoreException {
   1365         IFile dest = project.getFile(destName);
   1366         if (dest.exists() == false) {
   1367             FileInputStream stream = new FileInputStream(source);
   1368             dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
   1369         }
   1370     }
   1371 
   1372     /**
   1373      * Adds the given folder to the project's class path.
   1374      *
   1375      * @param javaProject The Java Project to update.
   1376      * @param sourceFolders Template Parameters.
   1377      * @param monitor An existing monitor.
   1378      * @throws JavaModelException if the classpath could not be set.
   1379      */
   1380     private void setupSourceFolders(IJavaProject javaProject, String[] sourceFolders,
   1381             IProgressMonitor monitor) throws JavaModelException {
   1382         IProject project = javaProject.getProject();
   1383 
   1384         // get the list of entries.
   1385         IClasspathEntry[] entries = javaProject.getRawClasspath();
   1386 
   1387         // remove the project as a source folder (This is the default)
   1388         entries = removeSourceClasspath(entries, project);
   1389 
   1390         // add the source folders.
   1391         for (String sourceFolder : sourceFolders) {
   1392             IFolder srcFolder = project.getFolder(sourceFolder);
   1393 
   1394             // remove it first in case.
   1395             entries = removeSourceClasspath(entries, srcFolder);
   1396             entries = ProjectHelper.addEntryToClasspath(entries,
   1397                     JavaCore.newSourceEntry(srcFolder.getFullPath()));
   1398         }
   1399 
   1400         javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10));
   1401     }
   1402 
   1403 
   1404     /**
   1405      * Removes the corresponding source folder from the class path entries if
   1406      * found.
   1407      *
   1408      * @param entries The class path entries to read. A copy will be returned.
   1409      * @param folder The parent source folder to remove.
   1410      * @return A new class path entries array.
   1411      */
   1412     private IClasspathEntry[] removeSourceClasspath(IClasspathEntry[] entries, IContainer folder) {
   1413         if (folder == null) {
   1414             return entries;
   1415         }
   1416         IClasspathEntry source = JavaCore.newSourceEntry(folder.getFullPath());
   1417         int n = entries.length;
   1418         for (int i = n - 1; i >= 0; i--) {
   1419             if (entries[i].equals(source)) {
   1420                 IClasspathEntry[] newEntries = new IClasspathEntry[n - 1];
   1421                 if (i > 0) System.arraycopy(entries, 0, newEntries, 0, i);
   1422                 if (i < n - 1) System.arraycopy(entries, i + 1, newEntries, i, n - i - 1);
   1423                 n--;
   1424                 entries = newEntries;
   1425             }
   1426         }
   1427         return entries;
   1428     }
   1429 
   1430 
   1431     /**
   1432      * Copies the given file from our resource folder to the new project.
   1433      * Expects the file to the US-ASCII or UTF-8 encoded.
   1434      *
   1435      * @throws CoreException from IFile if failing to create the new file.
   1436      * @throws MalformedURLException from URL if failing to interpret the URL.
   1437      * @throws FileNotFoundException from RandomAccessFile.
   1438      * @throws IOException from RandomAccessFile.length() if can't determine the
   1439      *         length.
   1440      */
   1441     private void copyFile(String resourceFilename, IFile destFile,
   1442             Map<String, Object> parameters, IProgressMonitor monitor, boolean reformat)
   1443             throws CoreException, IOException {
   1444 
   1445         // Read existing file.
   1446         String template = AdtPlugin.readEmbeddedTextFile(
   1447                 TEMPLATES_DIRECTORY + resourceFilename);
   1448 
   1449         // Replace all keyword parameters
   1450         template = replaceParameters(template, parameters);
   1451 
   1452         if (reformat) {
   1453             // Guess the formatting style based on the file location
   1454             XmlFormatStyle style = EclipseXmlPrettyPrinter
   1455                     .getForFile(destFile.getProjectRelativePath());
   1456             if (style != null) {
   1457                 template = reformat(style, template);
   1458             }
   1459         }
   1460 
   1461         // Save in the project as UTF-8
   1462         InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$
   1463         destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
   1464     }
   1465 
   1466     /**
   1467      * Replaces placeholders found in a string with values.
   1468      *
   1469      * @param str the string to search for placeholders.
   1470      * @param parameters a map of <placeholder, Value> to search for in the string
   1471      * @return A new String object with the placeholder replaced by the values.
   1472      */
   1473     private String replaceParameters(String str, Map<String, Object> parameters) {
   1474 
   1475         if (parameters == null) {
   1476             AdtPlugin.log(IStatus.ERROR,
   1477                     "NPW replace parameters: null parameter map. String: '%s'", str);  //$NON-NLS-1$
   1478             return str;
   1479         } else if (str == null) {
   1480             AdtPlugin.log(IStatus.ERROR,
   1481                     "NPW replace parameters: null template string");  //$NON-NLS-1$
   1482             return str;
   1483         }
   1484 
   1485         for (Entry<String, Object> entry : parameters.entrySet()) {
   1486             if (entry != null && entry.getValue() instanceof String) {
   1487                 Object value = entry.getValue();
   1488                 if (value == null) {
   1489                     AdtPlugin.log(IStatus.ERROR,
   1490                     "NPW replace parameters: null value for key '%s' in template '%s'",  //$NON-NLS-1$
   1491                     entry.getKey(),
   1492                     str);
   1493                 } else {
   1494                     str = str.replaceAll(entry.getKey(), (String) value);
   1495                 }
   1496             }
   1497         }
   1498 
   1499         return str;
   1500     }
   1501 
   1502     private static class WorksetAdder implements Runnable {
   1503         private final IJavaProject mProject;
   1504         private final IWorkingSet[] mWorkingSets;
   1505 
   1506         private WorksetAdder(IJavaProject project, IWorkingSet[] workingSets) {
   1507             mProject = project;
   1508             mWorkingSets = workingSets;
   1509         }
   1510 
   1511         @Override
   1512         public void run() {
   1513             if (mWorkingSets.length > 0 && mProject != null
   1514                     && mProject.exists()) {
   1515                 PlatformUI.getWorkbench().getWorkingSetManager()
   1516                         .addToWorkingSets(mProject, mWorkingSets);
   1517             }
   1518         }
   1519     }
   1520 }
   1521