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