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 com.android.ide.eclipse.adt.AdtPlugin;
     20 import com.android.ide.eclipse.adt.AndroidConstants;
     21 import com.android.ide.eclipse.adt.internal.project.AndroidNature;
     22 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
     23 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     24 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage.IMainInfo;
     25 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewTestProjectCreationPage.TestInfo;
     26 import com.android.sdklib.IAndroidTarget;
     27 import com.android.sdklib.SdkConstants;
     28 import com.android.sdklib.io.StreamException;
     29 import com.android.sdklib.resources.Density;
     30 
     31 import org.eclipse.core.resources.IContainer;
     32 import org.eclipse.core.resources.IFile;
     33 import org.eclipse.core.resources.IFolder;
     34 import org.eclipse.core.resources.IProject;
     35 import org.eclipse.core.resources.IProjectDescription;
     36 import org.eclipse.core.resources.IResource;
     37 import org.eclipse.core.resources.IResourceStatus;
     38 import org.eclipse.core.resources.IWorkspace;
     39 import org.eclipse.core.resources.ResourcesPlugin;
     40 import org.eclipse.core.runtime.CoreException;
     41 import org.eclipse.core.runtime.IPath;
     42 import org.eclipse.core.runtime.IProgressMonitor;
     43 import org.eclipse.core.runtime.IStatus;
     44 import org.eclipse.core.runtime.OperationCanceledException;
     45 import org.eclipse.core.runtime.Platform;
     46 import org.eclipse.core.runtime.SubProgressMonitor;
     47 import org.eclipse.jdt.core.IAccessRule;
     48 import org.eclipse.jdt.core.IClasspathAttribute;
     49 import org.eclipse.jdt.core.IClasspathEntry;
     50 import org.eclipse.jdt.core.IJavaProject;
     51 import org.eclipse.jdt.core.JavaCore;
     52 import org.eclipse.jdt.core.JavaModelException;
     53 import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
     54 import org.eclipse.jface.dialogs.ErrorDialog;
     55 import org.eclipse.jface.dialogs.MessageDialog;
     56 import org.eclipse.jface.resource.ImageDescriptor;
     57 import org.eclipse.jface.viewers.IStructuredSelection;
     58 import org.eclipse.jface.wizard.Wizard;
     59 import org.eclipse.ui.INewWizard;
     60 import org.eclipse.ui.IWorkbench;
     61 import org.eclipse.ui.actions.WorkspaceModifyOperation;
     62 
     63 import java.io.ByteArrayInputStream;
     64 import java.io.File;
     65 import java.io.FileNotFoundException;
     66 import java.io.IOException;
     67 import java.io.InputStream;
     68 import java.lang.reflect.InvocationTargetException;
     69 import java.net.MalformedURLException;
     70 import java.util.HashMap;
     71 import java.util.Map;
     72 import java.util.Set;
     73 import java.util.Map.Entry;
     74 
     75 /**
     76  * A "New Android Project" Wizard.
     77  * <p/>
     78  * Note: this class is public so that it can be accessed from unit tests.
     79  * It is however an internal class. Its API may change without notice.
     80  * It should semantically be considered as a private final class.
     81  * Do not derive from this class.
     82 
     83  */
     84 public class NewProjectWizard extends Wizard implements INewWizard {
     85 
     86     /**
     87      * Indicates which pages should be available in the New Project Wizard.
     88      */
     89     protected enum AvailablePages {
     90         /**
     91          * Both the usual "Android Project" and the "Android Test Project" pages will
     92          * be available. The first page displayed will be the former one and it can depend
     93          * on the soon-to-be created normal project.
     94          */
     95         ANDROID_AND_TEST_PROJECT,
     96         /**
     97          * Only the "Android Test Project" page will be available. User will have to
     98          * select an existing Android Project. If the selection matches such a project,
     99          * it will be used as a default.
    100          */
    101         TEST_PROJECT_ONLY
    102     }
    103 
    104     private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS";          //$NON-NLS-1$
    105     private static final String PARAM_ACTIVITY = "ACTIVITY_NAME";                   //$NON-NLS-1$
    106     private static final String PARAM_APPLICATION = "APPLICATION_NAME";             //$NON-NLS-1$
    107     private static final String PARAM_PACKAGE = "PACKAGE";                          //$NON-NLS-1$
    108     private static final String PARAM_PROJECT = "PROJECT_NAME";                     //$NON-NLS-1$
    109     private static final String PARAM_STRING_NAME = "STRING_NAME";                  //$NON-NLS-1$
    110     private static final String PARAM_STRING_CONTENT = "STRING_CONTENT";            //$NON-NLS-1$
    111     private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT";            //$NON-NLS-1$
    112     private static final String PARAM_SRC_FOLDER = "SRC_FOLDER";                    //$NON-NLS-1$
    113     private static final String PARAM_SDK_TARGET = "SDK_TARGET";                    //$NON-NLS-1$
    114     private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION";          //$NON-NLS-1$
    115     // Warning: The expanded string PARAM_TEST_TARGET_PACKAGE must not contain the
    116     // string "PACKAGE" since it collides with the replacement of PARAM_PACKAGE.
    117     private static final String PARAM_TEST_TARGET_PACKAGE = "TEST_TARGET_PCKG";     //$NON-NLS-1$
    118     private static final String PARAM_TARGET_SELF = "TARGET_SELF";                  //$NON-NLS-1$
    119     private static final String PARAM_TARGET_MAIN = "TARGET_MAIN";                  //$NON-NLS-1$
    120     private static final String PARAM_TARGET_EXISTING = "TARGET_EXISTING";          //$NON-NLS-1$
    121     private static final String PARAM_REFERENCE_PROJECT = "REFERENCE_PROJECT";      //$NON-NLS-1$
    122 
    123     private static final String PH_ACTIVITIES = "ACTIVITIES";                       //$NON-NLS-1$
    124     private static final String PH_USES_SDK = "USES-SDK";                           //$NON-NLS-1$
    125     private static final String PH_INTENT_FILTERS = "INTENT_FILTERS";               //$NON-NLS-1$
    126     private static final String PH_STRINGS = "STRINGS";                             //$NON-NLS-1$
    127     private static final String PH_TEST_USES_LIBRARY = "TEST-USES-LIBRARY";         //$NON-NLS-1$
    128     private static final String PH_TEST_INSTRUMENTATION = "TEST-INSTRUMENTATION";   //$NON-NLS-1$
    129 
    130     private static final String BIN_DIRECTORY =
    131         SdkConstants.FD_OUTPUT + AndroidConstants.WS_SEP;
    132     private static final String RES_DIRECTORY =
    133         SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP;
    134     private static final String ASSETS_DIRECTORY =
    135         SdkConstants.FD_ASSETS + AndroidConstants.WS_SEP;
    136     private static final String DRAWABLE_DIRECTORY =
    137         SdkConstants.FD_DRAWABLE + AndroidConstants.WS_SEP;
    138     private static final String DRAWABLE_HDPI_DIRECTORY =
    139         SdkConstants.FD_DRAWABLE + "-" + Density.HIGH.getResourceValue() + AndroidConstants.WS_SEP;   //$NON-NLS-1$
    140     private static final String DRAWABLE_MDPI_DIRECTORY =
    141         SdkConstants.FD_DRAWABLE + "-" + Density.MEDIUM.getResourceValue() + AndroidConstants.WS_SEP; //$NON-NLS-1$
    142     private static final String DRAWABLE_LDPI_DIRECTORY =
    143         SdkConstants.FD_DRAWABLE + "-" + Density.LOW.getResourceValue() + AndroidConstants.WS_SEP;    //$NON-NLS-1$
    144     private static final String LAYOUT_DIRECTORY =
    145         SdkConstants.FD_LAYOUT + AndroidConstants.WS_SEP;
    146     private static final String VALUES_DIRECTORY =
    147         SdkConstants.FD_VALUES + AndroidConstants.WS_SEP;
    148     private static final String GEN_SRC_DIRECTORY =
    149         SdkConstants.FD_GEN_SOURCES + AndroidConstants.WS_SEP;
    150 
    151     private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
    152     private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
    153             + "AndroidManifest.template"; //$NON-NLS-1$
    154     private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY
    155             + "activity.template"; //$NON-NLS-1$
    156     private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
    157             + "uses-sdk.template"; //$NON-NLS-1$
    158     private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY
    159             + "launcher_intent_filter.template"; //$NON-NLS-1$
    160     private static final String TEMPLATE_TEST_USES_LIBRARY = TEMPLATES_DIRECTORY
    161             + "test_uses-library.template"; //$NON-NLS-1$
    162     private static final String TEMPLATE_TEST_INSTRUMENTATION = TEMPLATES_DIRECTORY
    163             + "test_instrumentation.template"; //$NON-NLS-1$
    164 
    165 
    166 
    167     private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY
    168             + "strings.template"; //$NON-NLS-1$
    169     private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY
    170             + "string.template"; //$NON-NLS-1$
    171     private static final String PROJECT_ICON = "icon.png"; //$NON-NLS-1$
    172     private static final String ICON_HDPI = "icon_hdpi.png"; //$NON-NLS-1$
    173     private static final String ICON_MDPI = "icon_mdpi.png"; //$NON-NLS-1$
    174     private static final String ICON_LDPI = "icon_ldpi.png"; //$NON-NLS-1$
    175 
    176     private static final String STRINGS_FILE = "strings.xml";       //$NON-NLS-1$
    177 
    178     private static final String STRING_RSRC_PREFIX = "@string/";    //$NON-NLS-1$
    179     private static final String STRING_APP_NAME = "app_name";       //$NON-NLS-1$
    180     private static final String STRING_HELLO_WORLD = "hello";       //$NON-NLS-1$
    181 
    182     private static final String[] DEFAULT_DIRECTORIES = new String[] {
    183             BIN_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
    184     private static final String[] RES_DIRECTORIES = new String[] {
    185             DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY };
    186     private static final String[] RES_DENSITY_ENABLED_DIRECTORIES = new String[] {
    187             DRAWABLE_HDPI_DIRECTORY, DRAWABLE_MDPI_DIRECTORY, DRAWABLE_LDPI_DIRECTORY,
    188             LAYOUT_DIRECTORY, VALUES_DIRECTORY };
    189 
    190     private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$
    191     private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template";  //$NON-NLS-1$
    192     private static final String LAYOUT_TEMPLATE = "layout.template";            //$NON-NLS-1$
    193     private static final String MAIN_LAYOUT_XML = "main.xml";                   //$NON-NLS-1$
    194 
    195     private NewProjectCreationPage mMainPage;
    196     private NewTestProjectCreationPage mTestPage;
    197     /** Package name available when the wizard completes. */
    198     private String mPackageName;
    199     private final AvailablePages mAvailablePages;
    200 
    201     public NewProjectWizard() {
    202         this(AvailablePages.ANDROID_AND_TEST_PROJECT);
    203     }
    204 
    205     protected NewProjectWizard(AvailablePages availablePages) {
    206         mAvailablePages = availablePages;
    207     }
    208 
    209     /**
    210      * Initializes this creation wizard using the passed workbench and object
    211      * selection. Inherited from org.eclipse.ui.IWorkbenchWizard
    212      */
    213     public void init(IWorkbench workbench, IStructuredSelection selection) {
    214         setHelpAvailable(false); // TODO have help
    215         setImageDescriptor();
    216 
    217         if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) {
    218             mMainPage = createMainPage();
    219             setWindowTitle("New Android Project");
    220         } else {
    221             setWindowTitle("New Android Test Project");
    222         }
    223         mTestPage = createTestPage();
    224     }
    225 
    226     /**
    227      * Creates the main wizard page.
    228      * <p/>
    229      * Please do NOT override this method.
    230      * <p/>
    231      * This is protected so that it can be overridden by unit tests.
    232      * However the contract of this class is private and NO ATTEMPT will be made
    233      * to maintain compatibility between different versions of the plugin.
    234      */
    235     protected NewProjectCreationPage createMainPage() {
    236         return new NewProjectCreationPage();
    237     }
    238 
    239     /**
    240      * Creates the test wizard page.
    241      * <p/>
    242      * Please do NOT override this method.
    243      * <p/>
    244      * This is protected so that it can be overridden by unit tests.
    245      * However the contract of this class is private and NO ATTEMPT will be made
    246      * to maintain compatibility between different versions of the plugin.
    247      */
    248     protected NewTestProjectCreationPage createTestPage() {
    249         return new NewTestProjectCreationPage();
    250     }
    251 
    252     // -- Methods inherited from org.eclipse.jface.wizard.Wizard --
    253     // The Wizard class implements most defaults and boilerplate code needed by
    254     // IWizard
    255 
    256     /**
    257      * Adds pages to this wizard.
    258      */
    259     @Override
    260     public void addPages() {
    261         if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) {
    262             addPage(mMainPage);
    263         }
    264         addPage(mTestPage);
    265 
    266         if (mMainPage != null && mTestPage != null) {
    267             mTestPage.setMainInfo(mMainPage.getMainInfo());
    268             mMainPage.setTestInfo(mTestPage.getTestInfo());
    269         }
    270     }
    271 
    272     /**
    273      * Performs any actions appropriate in response to the user having pressed
    274      * the Finish button, or refuse if finishing now is not permitted: here, it
    275      * actually creates the workspace project and then switch to the Java
    276      * perspective.
    277      *
    278      * @return True
    279      */
    280     @Override
    281     public boolean performFinish() {
    282         if (!createAndroidProjects()) {
    283             return false;
    284         }
    285 
    286         // Open the default Java Perspective
    287         OpenJavaPerspectiveAction action = new OpenJavaPerspectiveAction();
    288         action.run();
    289         return true;
    290     }
    291 
    292     // -- Public Fields --
    293 
    294     /** Returns the main project package name. Only valid once the wizard finishes. */
    295     public String getPackageName() {
    296         return mPackageName;
    297     }
    298 
    299     // -- Custom Methods --
    300 
    301     /**
    302      * Before actually creating the project for a new project (as opposed to using an
    303      * existing project), we check if the target location is a directory that either does
    304      * not exist or is empty.
    305      *
    306      * If it's not empty, ask the user for confirmation.
    307      *
    308      * @param destination The destination folder where the new project is to be created.
    309      * @return True if the destination doesn't exist yet or is an empty directory or is
    310      *         accepted by the user.
    311      */
    312     private boolean validateNewProjectLocationIsEmpty(IPath destination) {
    313         File f = new File(destination.toOSString());
    314         if (f.isDirectory() && f.list().length > 0) {
    315             return AdtPlugin.displayPrompt("New Android Project",
    316                     "You are going to create a new Android Project in an existing, non-empty, directory. Are you sure you want to proceed?");
    317         }
    318         return true;
    319     }
    320 
    321     /**
    322      * Structure that describes all the information needed to create a project.
    323      * This is collected from the pages by {@link NewProjectWizard#createAndroidProjects()}
    324      * and then used by
    325      * {@link NewProjectWizard#createProjectAsync(IProgressMonitor, ProjectInfo, ProjectInfo)}.
    326      */
    327     private static class ProjectInfo {
    328         private final IProject mProject;
    329         private final IProjectDescription mDescription;
    330         private final Map<String, Object> mParameters;
    331         private final HashMap<String, String> mDictionary;
    332 
    333         public ProjectInfo(IProject project,
    334                 IProjectDescription description,
    335                 Map<String, Object> parameters,
    336                 HashMap<String, String> dictionary) {
    337                     mProject = project;
    338                     mDescription = description;
    339                     mParameters = parameters;
    340                     mDictionary = dictionary;
    341         }
    342 
    343         public IProject getProject() {
    344             return mProject;
    345         }
    346 
    347         public IProjectDescription getDescription() {
    348             return mDescription;
    349         }
    350 
    351         public Map<String, Object> getParameters() {
    352             return mParameters;
    353         }
    354 
    355         public HashMap<String, String> getDictionary() {
    356             return mDictionary;
    357         }
    358     }
    359 
    360     /**
    361      * Creates the android project.
    362      * @return True if the project could be created.
    363      */
    364     private boolean createAndroidProjects() {
    365 
    366         final ProjectInfo mainData = collectMainPageInfo();
    367         if (mMainPage != null && mainData == null) {
    368             return false;
    369         }
    370 
    371         final ProjectInfo testData = collectTestPageInfo();
    372 
    373         // Create a monitored operation to create the actual project
    374         WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
    375             @Override
    376             protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
    377                 createProjectAsync(monitor, mainData, testData);
    378             }
    379         };
    380 
    381         // Run the operation in a different thread
    382         runAsyncOperation(op);
    383         return true;
    384     }
    385 
    386     /**
    387      * Collects all the parameters needed to create the main project.
    388      * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
    389      *    created because parameters are incorrect or should not be created because there
    390      *    is no main page.
    391      */
    392     private ProjectInfo collectMainPageInfo() {
    393         if (mMainPage == null) {
    394             return null;
    395         }
    396 
    397         IMainInfo info = mMainPage.getMainInfo();
    398 
    399         IWorkspace workspace = ResourcesPlugin.getWorkspace();
    400         final IProject project = workspace.getRoot().getProject(info.getProjectName());
    401         final IProjectDescription description = workspace.newProjectDescription(project.getName());
    402 
    403         // keep some variables to make them available once the wizard closes
    404         mPackageName = info.getPackageName();
    405 
    406         final Map<String, Object> parameters = new HashMap<String, Object>();
    407         parameters.put(PARAM_PROJECT, info.getProjectName());
    408         parameters.put(PARAM_PACKAGE, mPackageName);
    409         parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
    410         parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
    411         parameters.put(PARAM_IS_NEW_PROJECT, info.isNewProject());
    412         parameters.put(PARAM_SRC_FOLDER, info.getSourceFolder());
    413         parameters.put(PARAM_SDK_TARGET, info.getSdkTarget());
    414         parameters.put(PARAM_MIN_SDK_VERSION, info.getMinSdkVersion());
    415 
    416         if (info.isCreateActivity()) {
    417             // An activity name can be of the form ".package.Class" or ".Class".
    418             // The initial dot is ignored, as it is always added later in the templates.
    419             String activityName = info.getActivityName();
    420             if (activityName.startsWith(".")) { //$NON-NLS-1$
    421                 activityName = activityName.substring(1);
    422             }
    423             parameters.put(PARAM_ACTIVITY, activityName);
    424         }
    425 
    426         // create a dictionary of string that will contain name+content.
    427         // we'll put all the strings into values/strings.xml
    428         final HashMap<String, String> dictionary = new HashMap<String, String>();
    429         dictionary.put(STRING_APP_NAME, info.getApplicationName());
    430 
    431         IPath path = info.getLocationPath();
    432         IPath defaultLocation = Platform.getLocation();
    433         if (!path.equals(defaultLocation)) {
    434             description.setLocation(path);
    435         }
    436 
    437         if (info.isNewProject() && !info.useDefaultLocation() &&
    438                 !validateNewProjectLocationIsEmpty(path)) {
    439             return null;
    440         }
    441 
    442         return new ProjectInfo(project, description, parameters, dictionary);
    443     }
    444 
    445     /**
    446      * Collects all the parameters needed to create the test project.
    447      *
    448      * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
    449      *    created because parameters are incorrect or should not be created because there
    450      *    is no test page.
    451      */
    452     private ProjectInfo collectTestPageInfo() {
    453         if (mTestPage == null) {
    454             return null;
    455         }
    456         TestInfo info = mTestPage.getTestInfo();
    457 
    458         if (!info.getCreateTestProject()) {
    459             return null;
    460         }
    461 
    462         IWorkspace workspace = ResourcesPlugin.getWorkspace();
    463         final IProject project = workspace.getRoot().getProject(info.getProjectName());
    464         final IProjectDescription description = workspace.newProjectDescription(project.getName());
    465 
    466         final Map<String, Object> parameters = new HashMap<String, Object>();
    467         parameters.put(PARAM_PROJECT, info.getProjectName());
    468         parameters.put(PARAM_PACKAGE, info.getPackageName());
    469         parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
    470         parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
    471         parameters.put(PARAM_IS_NEW_PROJECT, true);
    472         parameters.put(PARAM_SRC_FOLDER, info.getSourceFolder());
    473         parameters.put(PARAM_SDK_TARGET, info.getSdkTarget());
    474         parameters.put(PARAM_MIN_SDK_VERSION, info.getMinSdkVersion());
    475 
    476         // Test-specific parameters
    477         parameters.put(PARAM_TEST_TARGET_PACKAGE, info.getTargetPackageName());
    478 
    479         if (info.isTestingSelf()) {
    480             parameters.put(PARAM_TARGET_SELF, true);
    481         }
    482         if (info.isTestingMain()) {
    483             parameters.put(PARAM_TARGET_MAIN, true);
    484         }
    485         if (info.isTestingExisting()) {
    486             parameters.put(PARAM_TARGET_EXISTING, true);
    487             parameters.put(PARAM_REFERENCE_PROJECT, info.getExistingTestedProject());
    488         }
    489 
    490         // create a dictionary of string that will contain name+content.
    491         // we'll put all the strings into values/strings.xml
    492         final HashMap<String, String> dictionary = new HashMap<String, String>();
    493         dictionary.put(STRING_APP_NAME, info.getApplicationName());
    494 
    495         IPath path = info.getLocationPath();
    496         IPath defaultLocation = Platform.getLocation();
    497         if (!path.equals(defaultLocation)) {
    498             description.setLocation(path);
    499         }
    500 
    501         if (!info.useDefaultLocation() && !validateNewProjectLocationIsEmpty(path)) {
    502             return null;
    503         }
    504 
    505         return new ProjectInfo(project, description, parameters, dictionary);
    506     }
    507 
    508     /**
    509      * Runs the operation in a different thread and display generated
    510      * exceptions.
    511      *
    512      * @param op The asynchronous operation to run.
    513      */
    514     private void runAsyncOperation(WorkspaceModifyOperation op) {
    515         try {
    516             getContainer().run(true /* fork */, true /* cancelable */, op);
    517         } catch (InvocationTargetException e) {
    518 
    519             AdtPlugin.log(e, "New Project Wizard failed");
    520 
    521             // The runnable threw an exception
    522             Throwable t = e.getTargetException();
    523             if (t instanceof CoreException) {
    524                 CoreException core = (CoreException) t;
    525                 if (core.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
    526                     // The error indicates the file system is not case sensitive
    527                     // and there's a resource with a similar name.
    528                     MessageDialog.openError(getShell(), "Error", "Error: Case Variant Exists");
    529                 } else {
    530                     ErrorDialog.openError(getShell(), "Error", core.getMessage(), core.getStatus());
    531                 }
    532             } else {
    533                 // Some other kind of exception
    534                 String msg = t.getMessage();
    535                 Throwable t1 = t;
    536                 while (msg == null && t1.getCause() != null) {
    537                     msg = t1.getMessage();
    538                     t1 = t1.getCause();
    539                 }
    540                 if (msg == null) {
    541                     msg = t.toString();
    542                 }
    543                 MessageDialog.openError(getShell(), "Error", msg);
    544             }
    545             e.printStackTrace();
    546         } catch (InterruptedException e) {
    547             e.printStackTrace();
    548         }
    549     }
    550 
    551     /**
    552      * Creates the actual project(s). This is run asynchronously in a different thread.
    553      *
    554      * @param monitor An existing monitor.
    555      * @param mainData Data for main project. Can be null.
    556      * @throws InvocationTargetException to wrap any unmanaged exception and
    557      *         return it to the calling thread. The method can fail if it fails
    558      *         to create or modify the project or if it is canceled by the user.
    559      */
    560     private void createProjectAsync(IProgressMonitor monitor,
    561             ProjectInfo mainData,
    562             ProjectInfo testData)
    563                 throws InvocationTargetException {
    564         monitor.beginTask("Create Android Project", 100);
    565         try {
    566             IProject mainProject = null;
    567 
    568             if (mainData != null) {
    569                 mainProject = createEclipseProject(
    570                         new SubProgressMonitor(monitor, 50),
    571                         mainData.getProject(),
    572                         mainData.getDescription(),
    573                         mainData.getParameters(),
    574                         mainData.getDictionary());
    575             }
    576 
    577             if (testData != null) {
    578 
    579                 Map<String, Object> parameters = testData.getParameters();
    580                 if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) {
    581                     parameters.put(PARAM_REFERENCE_PROJECT, mainProject);
    582                 }
    583 
    584                 createEclipseProject(
    585                         new SubProgressMonitor(monitor, 50),
    586                         testData.getProject(),
    587                         testData.getDescription(),
    588                         parameters,
    589                         testData.getDictionary());
    590             }
    591 
    592         } catch (CoreException e) {
    593             throw new InvocationTargetException(e);
    594         } catch (IOException e) {
    595             throw new InvocationTargetException(e);
    596         } catch (StreamException e) {
    597             throw new InvocationTargetException(e);
    598         } finally {
    599             monitor.done();
    600         }
    601     }
    602 
    603     /**
    604      * Creates the actual project, sets its nature and adds the required folders
    605      * and files to it. This is run asynchronously in a different thread.
    606      *
    607      * @param monitor An existing monitor.
    608      * @param project The project to create.
    609      * @param description A description of the project.
    610      * @param parameters Template parameters.
    611      * @param dictionary String definition.
    612      * @return The project newly created
    613      * @throws StreamException
    614      */
    615     private IProject createEclipseProject(IProgressMonitor monitor,
    616             IProject project,
    617             IProjectDescription description,
    618             Map<String, Object> parameters,
    619             Map<String, String> dictionary)
    620                 throws CoreException, IOException, StreamException {
    621 
    622         // get the project target
    623         IAndroidTarget target = (IAndroidTarget) parameters.get(PARAM_SDK_TARGET);
    624         boolean legacy = target.getVersion().getApiLevel() < 4;
    625 
    626         // Create project and open it
    627         project.create(description, new SubProgressMonitor(monitor, 10));
    628         if (monitor.isCanceled()) throw new OperationCanceledException();
    629 
    630         project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10));
    631 
    632         // Add the Java and android nature to the project
    633         AndroidNature.setupProjectNatures(project, monitor);
    634 
    635         // Create folders in the project if they don't already exist
    636         addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
    637         String[] sourceFolders = new String[] {
    638                     (String) parameters.get(PARAM_SRC_FOLDER),
    639                     GEN_SRC_DIRECTORY
    640                 };
    641         addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor);
    642 
    643         // Create the resource folders in the project if they don't already exist.
    644         if (legacy) {
    645             addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
    646         } else {
    647             addDefaultDirectories(project, RES_DIRECTORY, RES_DENSITY_ENABLED_DIRECTORIES, monitor);
    648         }
    649 
    650         // Setup class path: mark folders as source folders
    651         IJavaProject javaProject = JavaCore.create(project);
    652         setupSourceFolders(javaProject, sourceFolders, monitor);
    653 
    654         // Mark the gen source folder as derived
    655         IFolder genSrcFolder = project.getFolder(AndroidConstants.WS_ROOT + GEN_SRC_DIRECTORY);
    656         if (genSrcFolder.exists()) {
    657             genSrcFolder.setDerived(true);
    658         }
    659 
    660         if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
    661             // Create files in the project if they don't already exist
    662             addManifest(project, parameters, dictionary, monitor);
    663 
    664             // add the default app icon
    665             addIcon(project, legacy, monitor);
    666 
    667             // Create the default package components
    668             addSampleCode(project, sourceFolders[0], parameters, dictionary, monitor);
    669 
    670             // add the string definition file if needed
    671             if (dictionary.size() > 0) {
    672                 addStringDictionaryFile(project, dictionary, monitor);
    673             }
    674 
    675             // Set output location
    676             javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(),
    677                     monitor);
    678         }
    679 
    680         // Create the reference to the target project
    681         if (parameters.containsKey(PARAM_REFERENCE_PROJECT)) {
    682             IProject refProject = (IProject) parameters.get(PARAM_REFERENCE_PROJECT);
    683             if (refProject != null) {
    684                 IProjectDescription desc = project.getDescription();
    685 
    686                 // Add out reference to the existing project reference.
    687                 // We just created a project with no references so we don't need to expand
    688                 // the currently-empty current list.
    689                 desc.setReferencedProjects(new IProject[] { refProject });
    690 
    691                 project.setDescription(desc, IResource.KEEP_HISTORY,
    692                         new SubProgressMonitor(monitor, 10));
    693 
    694                 IClasspathEntry entry = JavaCore.newProjectEntry(
    695                         refProject.getFullPath(), //path
    696                         new IAccessRule[0], //accessRules
    697                         false, //combineAccessRules
    698                         new IClasspathAttribute[0], //extraAttributes
    699                         false //isExported
    700 
    701                 );
    702                 ProjectHelper.addEntryToClasspath(javaProject, entry);
    703             }
    704         }
    705 
    706         Sdk.getCurrent().initProject(project, target);
    707 
    708         // Fix the project to make sure all properties are as expected.
    709         // Necessary for existing projects and good for new ones to.
    710         ProjectHelper.fixProject(project);
    711 
    712         return project;
    713     }
    714 
    715     /**
    716      * Adds default directories to the project.
    717      *
    718      * @param project The Java Project to update.
    719      * @param parentFolder The path of the parent folder. Must end with a
    720      *        separator.
    721      * @param folders Folders to be added.
    722      * @param monitor An existing monitor.
    723      * @throws CoreException if the method fails to create the directories in
    724      *         the project.
    725      */
    726     private void addDefaultDirectories(IProject project, String parentFolder,
    727             String[] folders, IProgressMonitor monitor) throws CoreException {
    728         for (String name : folders) {
    729             if (name.length() > 0) {
    730                 IFolder folder = project.getFolder(parentFolder + name);
    731                 if (!folder.exists()) {
    732                     folder.create(true /* force */, true /* local */,
    733                             new SubProgressMonitor(monitor, 10));
    734                 }
    735             }
    736         }
    737     }
    738 
    739     /**
    740      * Adds the manifest to the project.
    741      *
    742      * @param project The Java Project to update.
    743      * @param parameters Template Parameters.
    744      * @param dictionary String List to be added to a string definition
    745      *        file. This map will be filled by this method.
    746      * @param monitor An existing monitor.
    747      * @throws CoreException if the method fails to update the project.
    748      * @throws IOException if the method fails to create the files in the
    749      *         project.
    750      */
    751     private void addManifest(IProject project, Map<String, Object> parameters,
    752             Map<String, String> dictionary, IProgressMonitor monitor)
    753             throws CoreException, IOException {
    754 
    755         // get IFile to the manifest and check if it's not already there.
    756         IFile file = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
    757         if (!file.exists()) {
    758 
    759             // Read manifest template
    760             String manifestTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_MANIFEST);
    761 
    762             // Replace all keyword parameters
    763             manifestTemplate = replaceParameters(manifestTemplate, parameters);
    764 
    765             if (manifestTemplate == null) {
    766                 // Inform the user there will be not manifest.
    767                 AdtPlugin.logAndPrintError(null, getWindowTitle() /*TAG*/,
    768                         "Failed to generate the Android manifest. Missing template %s",
    769                         TEMPLATE_MANIFEST);
    770                 // Abort now, there's no need to continue
    771                 return;
    772             }
    773 
    774             if (parameters.containsKey(PARAM_ACTIVITY)) {
    775                 // now get the activity template
    776                 String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES);
    777 
    778                 // Replace all keyword parameters to make main activity.
    779                 String activities = replaceParameters(activityTemplate, parameters);
    780 
    781                 // set the intent.
    782                 String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER);
    783 
    784                 if (activities != null) {
    785                     if (intent != null) {
    786                         // set the intent to the main activity
    787                         activities = activities.replaceAll(PH_INTENT_FILTERS, intent);
    788                     }
    789 
    790                     // set the activity(ies) in the manifest
    791                     manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, activities);
    792                 }
    793             } else {
    794                 // remove the activity(ies) from the manifest
    795                 manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, "");  //$NON-NLS-1$
    796             }
    797 
    798             // Handle the case of the test projects
    799             if (parameters.containsKey(PARAM_TEST_TARGET_PACKAGE)) {
    800                 // Set the uses-library needed by the test project
    801                 String usesLibrary = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_USES_LIBRARY);
    802                 if (usesLibrary != null) {
    803                     manifestTemplate = manifestTemplate.replaceAll(
    804                             PH_TEST_USES_LIBRARY, usesLibrary);
    805                 }
    806 
    807                 // Set the instrumentation element needed by the test project
    808                 String instru = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_INSTRUMENTATION);
    809                 if (instru != null) {
    810                     manifestTemplate = manifestTemplate.replaceAll(
    811                             PH_TEST_INSTRUMENTATION, instru);
    812                 }
    813 
    814                 // Replace PARAM_TEST_TARGET_PACKAGE itself now
    815                 manifestTemplate = replaceParameters(manifestTemplate, parameters);
    816 
    817             } else {
    818                 // remove the unused entries
    819                 manifestTemplate = manifestTemplate.replaceAll(PH_TEST_USES_LIBRARY, "");     //$NON-NLS-1$
    820                 manifestTemplate = manifestTemplate.replaceAll(PH_TEST_INSTRUMENTATION, "");  //$NON-NLS-1$
    821             }
    822 
    823             String minSdkVersion = (String) parameters.get(PARAM_MIN_SDK_VERSION);
    824             if (minSdkVersion != null && minSdkVersion.length() > 0) {
    825                 String usesSdkTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_USES_SDK);
    826                 if (usesSdkTemplate != null) {
    827                     String usesSdk = replaceParameters(usesSdkTemplate, parameters);
    828                     manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, usesSdk);
    829                 }
    830             } else {
    831                 manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, "");
    832             }
    833 
    834             // Save in the project as UTF-8
    835             InputStream stream = new ByteArrayInputStream(
    836                     manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$
    837             file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
    838         }
    839     }
    840 
    841     /**
    842      * Adds the string resource file.
    843      *
    844      * @param project The Java Project to update.
    845      * @param strings The list of strings to be added to the string file.
    846      * @param monitor An existing monitor.
    847      * @throws CoreException if the method fails to update the project.
    848      * @throws IOException if the method fails to create the files in the
    849      *         project.
    850      */
    851     private void addStringDictionaryFile(IProject project,
    852             Map<String, String> strings, IProgressMonitor monitor)
    853             throws CoreException, IOException {
    854 
    855         // create the IFile object and check if the file doesn't already exist.
    856         IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP
    857                                      + VALUES_DIRECTORY + AndroidConstants.WS_SEP + STRINGS_FILE);
    858         if (!file.exists()) {
    859             // get the Strings.xml template
    860             String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS);
    861 
    862             // get the template for one string
    863             String stringTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRING);
    864 
    865             // get all the string names
    866             Set<String> stringNames = strings.keySet();
    867 
    868             // loop on it and create the string definitions
    869             StringBuilder stringNodes = new StringBuilder();
    870             for (String key : stringNames) {
    871                 // get the value from the key
    872                 String value = strings.get(key);
    873 
    874                 // place them in the template
    875                 String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key);
    876                 stringDef = stringDef.replace(PARAM_STRING_CONTENT, value);
    877 
    878                 // append to the other string
    879                 if (stringNodes.length() > 0) {
    880                     stringNodes.append("\n");
    881                 }
    882                 stringNodes.append(stringDef);
    883             }
    884 
    885             // put the string nodes in the Strings.xml template
    886             stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS,
    887                                                                         stringNodes.toString());
    888 
    889             // write the file as UTF-8
    890             InputStream stream = new ByteArrayInputStream(
    891                     stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$
    892             file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
    893         }
    894     }
    895 
    896 
    897     /**
    898      * Adds default application icon to the project.
    899      *
    900      * @param project The Java Project to update.
    901      * @param legacy whether we're running in legacy mode (no density support)
    902      * @param monitor An existing monitor.
    903      * @throws CoreException if the method fails to update the project.
    904      */
    905     private void addIcon(IProject project, boolean legacy, IProgressMonitor monitor)
    906             throws CoreException {
    907         if (legacy) { // density support
    908             // do medium density icon only, in the default drawable folder.
    909             IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP
    910                     + DRAWABLE_DIRECTORY + AndroidConstants.WS_SEP + PROJECT_ICON);
    911             if (!file.exists()) {
    912                 addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
    913             }
    914         } else {
    915             // do all 3 icons.
    916             IFile file;
    917 
    918             // high density
    919             file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP
    920                     + DRAWABLE_HDPI_DIRECTORY + AndroidConstants.WS_SEP + PROJECT_ICON);
    921             if (!file.exists()) {
    922                 addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_HDPI), monitor);
    923             }
    924 
    925             // medium density
    926             file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP
    927                     + DRAWABLE_MDPI_DIRECTORY + AndroidConstants.WS_SEP + PROJECT_ICON);
    928             if (!file.exists()) {
    929                 addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
    930             }
    931 
    932             // low density
    933             file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP
    934                     + DRAWABLE_LDPI_DIRECTORY + AndroidConstants.WS_SEP + PROJECT_ICON);
    935             if (!file.exists()) {
    936                 addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_LDPI), monitor);
    937             }
    938         }
    939     }
    940 
    941     /**
    942      * Creates a file from a data source.
    943      * @param dest the file to write
    944      * @param source the content of the file.
    945      * @param monitor the progress monitor
    946      * @throws CoreException
    947      */
    948     private void addFile(IFile dest, byte[] source, IProgressMonitor monitor) throws CoreException {
    949         if (source != null) {
    950             // Save in the project
    951             InputStream stream = new ByteArrayInputStream(source);
    952             dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
    953         }
    954     }
    955 
    956     /**
    957      * Creates the package folder and copies the sample code in the project.
    958      *
    959      * @param project The Java Project to update.
    960      * @param parameters Template Parameters.
    961      * @param dictionary String List to be added to a string definition
    962      *        file. This map will be filled by this method.
    963      * @param monitor An existing monitor.
    964      * @throws CoreException if the method fails to update the project.
    965      * @throws IOException if the method fails to create the files in the
    966      *         project.
    967      */
    968     private void addSampleCode(IProject project, String sourceFolder,
    969             Map<String, Object> parameters, Map<String, String> dictionary,
    970             IProgressMonitor monitor) throws CoreException, IOException {
    971         // create the java package directories.
    972         IFolder pkgFolder = project.getFolder(sourceFolder);
    973         String packageName = (String) parameters.get(PARAM_PACKAGE);
    974 
    975         // The PARAM_ACTIVITY key will be absent if no activity should be created,
    976         // in which case activityName will be null.
    977         String activityName = (String) parameters.get(PARAM_ACTIVITY);
    978         Map<String, Object> java_activity_parameters = parameters;
    979         if (activityName != null) {
    980             if (activityName.indexOf('.') >= 0) {
    981                 // There are package names in the activity name. Transform packageName to add
    982                 // those sub packages and remove them from activityName.
    983                 packageName += "." + activityName; //$NON-NLS-1$
    984                 int pos = packageName.lastIndexOf('.');
    985                 activityName = packageName.substring(pos + 1);
    986                 packageName = packageName.substring(0, pos);
    987 
    988                 // Also update the values used in the JAVA_FILE_TEMPLATE below
    989                 // (but not the ones from the manifest so don't change the caller's dictionary)
    990                 java_activity_parameters = new HashMap<String, Object>(parameters);
    991                 java_activity_parameters.put(PARAM_PACKAGE, packageName);
    992                 java_activity_parameters.put(PARAM_ACTIVITY, activityName);
    993             }
    994         }
    995 
    996         String[] components = packageName.split(AndroidConstants.RE_DOT);
    997         for (String component : components) {
    998             pkgFolder = pkgFolder.getFolder(component);
    999             if (!pkgFolder.exists()) {
   1000                 pkgFolder.create(true /* force */, true /* local */,
   1001                         new SubProgressMonitor(monitor, 10));
   1002             }
   1003         }
   1004 
   1005         if (activityName != null) {
   1006             // create the main activity Java file
   1007             String activityJava = activityName + AndroidConstants.DOT_JAVA;
   1008             IFile file = pkgFolder.getFile(activityJava);
   1009             if (!file.exists()) {
   1010                 copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor);
   1011             }
   1012         }
   1013 
   1014         // create the layout file
   1015         IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY);
   1016         IFile file = layoutfolder.getFile(MAIN_LAYOUT_XML);
   1017         if (!file.exists()) {
   1018             copyFile(LAYOUT_TEMPLATE, file, parameters, monitor);
   1019             if (activityName != null) {
   1020                 dictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!");
   1021             } else {
   1022                 dictionary.put(STRING_HELLO_WORLD, "Hello World!");
   1023             }
   1024         }
   1025     }
   1026 
   1027     /**
   1028      * Adds the given folder to the project's class path.
   1029      *
   1030      * @param javaProject The Java Project to update.
   1031      * @param sourceFolder Template Parameters.
   1032      * @param monitor An existing monitor.
   1033      * @throws JavaModelException if the classpath could not be set.
   1034      */
   1035     private void setupSourceFolders(IJavaProject javaProject, String[] sourceFolders,
   1036             IProgressMonitor monitor) throws JavaModelException {
   1037         IProject project = javaProject.getProject();
   1038 
   1039         // get the list of entries.
   1040         IClasspathEntry[] entries = javaProject.getRawClasspath();
   1041 
   1042         // remove the project as a source folder (This is the default)
   1043         entries = removeSourceClasspath(entries, project);
   1044 
   1045         // add the source folders.
   1046         for (String sourceFolder : sourceFolders) {
   1047             IFolder srcFolder = project.getFolder(sourceFolder);
   1048 
   1049             // remove it first in case.
   1050             entries = removeSourceClasspath(entries, srcFolder);
   1051             entries = ProjectHelper.addEntryToClasspath(entries,
   1052                     JavaCore.newSourceEntry(srcFolder.getFullPath()));
   1053         }
   1054 
   1055         javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10));
   1056     }
   1057 
   1058 
   1059     /**
   1060      * Removes the corresponding source folder from the class path entries if
   1061      * found.
   1062      *
   1063      * @param entries The class path entries to read. A copy will be returned.
   1064      * @param folder The parent source folder to remove.
   1065      * @return A new class path entries array.
   1066      */
   1067     private IClasspathEntry[] removeSourceClasspath(IClasspathEntry[] entries, IContainer folder) {
   1068         if (folder == null) {
   1069             return entries;
   1070         }
   1071         IClasspathEntry source = JavaCore.newSourceEntry(folder.getFullPath());
   1072         int n = entries.length;
   1073         for (int i = n - 1; i >= 0; i--) {
   1074             if (entries[i].equals(source)) {
   1075                 IClasspathEntry[] newEntries = new IClasspathEntry[n - 1];
   1076                 if (i > 0) System.arraycopy(entries, 0, newEntries, 0, i);
   1077                 if (i < n - 1) System.arraycopy(entries, i + 1, newEntries, i, n - i - 1);
   1078                 n--;
   1079                 entries = newEntries;
   1080             }
   1081         }
   1082         return entries;
   1083     }
   1084 
   1085 
   1086     /**
   1087      * Copies the given file from our resource folder to the new project.
   1088      * Expects the file to the US-ASCII or UTF-8 encoded.
   1089      *
   1090      * @throws CoreException from IFile if failing to create the new file.
   1091      * @throws MalformedURLException from URL if failing to interpret the URL.
   1092      * @throws FileNotFoundException from RandomAccessFile.
   1093      * @throws IOException from RandomAccessFile.length() if can't determine the
   1094      *         length.
   1095      */
   1096     private void copyFile(String resourceFilename, IFile destFile,
   1097             Map<String, Object> parameters, IProgressMonitor monitor)
   1098             throws CoreException, IOException {
   1099 
   1100         // Read existing file.
   1101         String template = AdtPlugin.readEmbeddedTextFile(
   1102                 TEMPLATES_DIRECTORY + resourceFilename);
   1103 
   1104         // Replace all keyword parameters
   1105         template = replaceParameters(template, parameters);
   1106 
   1107         // Save in the project as UTF-8
   1108         InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$
   1109         destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
   1110     }
   1111 
   1112     /**
   1113      * Returns an image descriptor for the wizard logo.
   1114      */
   1115     private void setImageDescriptor() {
   1116         ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE);
   1117         setDefaultPageImageDescriptor(desc);
   1118     }
   1119 
   1120     /**
   1121      * Replaces placeholders found in a string with values.
   1122      *
   1123      * @param str the string to search for placeholders.
   1124      * @param parameters a map of <placeholder, Value> to search for in the string
   1125      * @return A new String object with the placeholder replaced by the values.
   1126      */
   1127     private String replaceParameters(String str, Map<String, Object> parameters) {
   1128 
   1129         if (parameters == null) {
   1130             AdtPlugin.log(IStatus.ERROR,
   1131                     "NPW replace parameters: null parameter map. String: '%s'", str);  //$NON-NLS-1$
   1132             return str;
   1133         } else if (str == null) {
   1134             AdtPlugin.log(IStatus.ERROR,
   1135                     "NPW replace parameters: null template string");  //$NON-NLS-1$
   1136             return str;
   1137         }
   1138 
   1139         for (Entry<String, Object> entry : parameters.entrySet()) {
   1140             if (entry != null && entry.getValue() instanceof String) {
   1141                 Object value = entry.getValue();
   1142                 if (value == null) {
   1143                     AdtPlugin.log(IStatus.ERROR,
   1144                     "NPW replace parameters: null value for key '%s' in template '%s'",  //$NON-NLS-1$
   1145                     entry.getKey(),
   1146                     str);
   1147                 } else {
   1148                     str = str.replaceAll(entry.getKey(), (String) value);
   1149                 }
   1150             }
   1151         }
   1152 
   1153         return str;
   1154     }
   1155 }
   1156