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