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