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