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