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