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