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