1 /******************************************************************************* 2 * Copyright (c) 2005, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * EclipseSource Corporation - ongoing enhancements 14 *******************************************************************************/ 15 package org.eclipse.pde.internal.ui.wizards.product; 16 17 import java.lang.reflect.InvocationTargetException; 18 import java.util.Locale; 19 import org.eclipse.core.resources.IFile; 20 import org.eclipse.core.resources.IProject; 21 import org.eclipse.core.runtime.*; 22 import org.eclipse.osgi.util.NLS; 23 import org.eclipse.pde.core.IBaseModel; 24 import org.eclipse.pde.core.plugin.*; 25 import org.eclipse.pde.internal.core.TargetPlatformHelper; 26 import org.eclipse.pde.internal.core.iproduct.*; 27 import org.eclipse.pde.internal.core.iproduct.IProduct; 28 import org.eclipse.pde.internal.core.plugin.WorkspacePluginModelBase; 29 import org.eclipse.pde.internal.core.product.SplashInfo; 30 import org.eclipse.pde.internal.core.text.plugin.PluginElementNode; 31 import org.eclipse.pde.internal.ui.PDEPlugin; 32 import org.eclipse.pde.internal.ui.PDEUIMessages; 33 import org.eclipse.pde.internal.ui.util.*; 34 import org.eclipse.swt.widgets.Shell; 35 import org.eclipse.ui.branding.IProductConstants; 36 37 public class ProductDefinitionOperation extends BaseManifestOperation { 38 39 private static final String APPLICATION_CSS = "applicationCSS"; //$NON-NLS-1$ 40 41 private String fProductId; 42 private String fApplication; 43 private IProduct fProduct; 44 45 protected IProject fProject; 46 47 private UpdateSplashHandlerAction fUpdateSplashAction; 48 49 private RemoveSplashHandlerBindingAction fRemoveSplashAction; 50 51 private UpdateSplashProgressOperation fUpdateSplashProgressOperation; 52 ProductDefinitionOperation(IProduct product, String pluginId, String productId, String application, Shell shell)53 public ProductDefinitionOperation(IProduct product, String pluginId, String productId, String application, Shell shell) { 54 super(shell, pluginId); 55 fProductId = productId; 56 fApplication = application; 57 fProduct = product; 58 fProject = null; 59 } 60 ProductDefinitionOperation(IProduct product, String pluginId, String productId, String application, Shell shell, IProject project)61 public ProductDefinitionOperation(IProduct product, String pluginId, String productId, String application, Shell shell, IProject project) { 62 super(shell, pluginId); 63 fProductId = productId; 64 fApplication = application; 65 fProduct = product; 66 // Needed for splash handler updates (file copying) 67 fProject = project; 68 } 69 getFormattedPackageName(String id)70 protected String getFormattedPackageName(String id) { 71 StringBuilder buffer = new StringBuilder(); 72 for (int i = 0; i < id.length(); i++) { 73 char ch = id.charAt(i); 74 if (buffer.length() == 0) { 75 if (Character.isJavaIdentifierStart(ch)) 76 buffer.append(Character.toLowerCase(ch)); 77 } else { 78 if (Character.isJavaIdentifierPart(ch) || ch == '.') 79 buffer.append(ch); 80 } 81 } 82 return buffer.toString().toLowerCase(Locale.ENGLISH); 83 } 84 createTargetPackage()85 protected String createTargetPackage() { 86 // Package name addition to create a location for containing 87 // any classes required by the splash handlers. 88 String packageName = getFormattedPackageName(fPluginId); 89 // Unqualifed 90 if (packageName.length() == 0) { 91 return ISplashHandlerConstants.F_UNQUALIFIED_EXTENSION_ID; 92 } 93 // Qualified 94 return packageName + '.' + ISplashHandlerConstants.F_UNQUALIFIED_EXTENSION_ID; 95 } 96 97 /** 98 * @return fully-qualified class (with package) 99 */ createAttributeValueClass()100 private String createAttributeValueClass() { 101 String targetPackage = createTargetPackage(); 102 String targetClass = createTargetClass(); 103 // Ensure target class is defined 104 if (targetClass == null) { 105 return null; 106 } 107 108 return targetPackage + "." + //$NON-NLS-1$ 109 targetClass; 110 } 111 112 /** 113 * @return unqualified class 114 */ createTargetClass()115 private String createTargetClass() { 116 // Get the splash handler type 117 String splashHandlerType = getSplashHandlerType(); 118 // Ensure splash handler type was specfied 119 if (splashHandlerType == null) { 120 return null; 121 } 122 // Update the class name depending on the splash screen type 123 for (int i = 0; i < ISplashHandlerConstants.F_SPLASH_SCREEN_TYPE_CHOICES.length; i++) { 124 String choice = ISplashHandlerConstants.F_SPLASH_SCREEN_TYPE_CHOICES[i][0]; 125 if (splashHandlerType.equals(choice)) { 126 return ISplashHandlerConstants.F_SPLASH_SCREEN_CLASSES[i]; 127 } 128 } 129 return null; 130 } 131 132 /** 133 * @return splash screen type qualified with package name 134 */ createAttributeValueID()135 private String createAttributeValueID() { 136 // Create the ID based on the splash screen type 137 return createTargetPackage() + "." + //$NON-NLS-1$ 138 getSplashHandlerType(); 139 } 140 getUpdateSplashProgressOperation()141 private UpdateSplashProgressOperation getUpdateSplashProgressOperation() { 142 if (fUpdateSplashProgressOperation == null) { 143 fUpdateSplashProgressOperation = new UpdateSplashProgressOperation(); 144 } else { 145 fUpdateSplashProgressOperation.reset(); 146 } 147 return fUpdateSplashProgressOperation; 148 } 149 updateSplashProgress(IPluginModelBase model, IProgressMonitor monitor)150 private void updateSplashProgress(IPluginModelBase model, IProgressMonitor monitor) throws CoreException { 151 // Sanity checks 152 if (fProject == null) { 153 return; 154 } else if (model == null) { 155 return; 156 } else if (monitor == null) { 157 return; 158 } 159 // Get the action 160 UpdateSplashProgressOperation operation = getUpdateSplashProgressOperation(); 161 operation.setModel(model); 162 operation.setShowProgress(isProgressDefined()); 163 operation.setProject(fProject); 164 operation.setProductID(fProduct.getProductId()); 165 operation.setPluginID(fPluginId); 166 // Execute the action 167 operation.run(monitor); 168 } 169 isProgressDefined()170 private boolean isProgressDefined() { 171 // Get the splash info from the model 172 ISplashInfo info = fProduct.getProduct().getSplashInfo(); 173 // Ensure splash info was defined 174 if (info == null) { 175 return false; 176 } 177 // Ensure splash progress was defined 178 return info.isDefinedGeometry(); 179 } 180 getSplashHandlerType()181 private String getSplashHandlerType() { 182 // Get the splash info from the model 183 ISplashInfo info = fProduct.getProduct().getSplashInfo(); 184 // Ensure splash info was defined 185 if (info == null) { 186 return null; 187 } 188 // Ensure splash type was defined 189 if (info.isDefinedSplashHandlerType() == false) { 190 return null; 191 } 192 return info.getFieldSplashHandlerType(); 193 } 194 updateSplashHandler(IPluginModelBase model, IProgressMonitor monitor)195 private void updateSplashHandler(IPluginModelBase model, IProgressMonitor monitor) throws CoreException { 196 // Copy the applicable splash handler artifacts and perform parameter 197 // substitution (like in templates plug-in) 198 // Artifacts may include code, images and extension point schemas 199 updateSplashHandlerFiles(model, monitor); 200 // Update the plug-in model with the applicable splash handler extension 201 // and extension point related mark-up 202 updateSplashHandlerModel(model, monitor); 203 } 204 updateSplashHandlerFiles(IPluginModelBase model, IProgressMonitor monitor)205 private void updateSplashHandlerFiles(IPluginModelBase model, IProgressMonitor monitor) throws CoreException { 206 // If the project is not defined, abort this operation 207 if (fProject == null) { 208 return; 209 } 210 // Get the splash handler type 211 String splashHandlerType = getSplashHandlerType(); 212 // If the splash handler type was not defined, abort this operation 213 if (splashHandlerType == null) { 214 return; 215 } 216 // Create and configure the template file generator 217 // Note: Plug-in ID must be passed in separately from model, because 218 // the underlying model does not contain the ID (even when it is 219 // a workspace model) 220 TemplateFileGenerator generator = new TemplateFileGenerator(fProject, model, fPluginId, createTargetPackage(), createTargetClass(), splashHandlerType); 221 // Generate the necessary files 222 generator.generateFiles(monitor); 223 } 224 updateSplashHandlerModel(IPluginModelBase model, IProgressMonitor monitor)225 private void updateSplashHandlerModel(IPluginModelBase model, IProgressMonitor monitor) throws CoreException { 226 // Get the splash handler type 227 String splashHandlerType = getSplashHandlerType(); 228 // If the splash handler type is not defined, abort this operation 229 if (splashHandlerType == null) { 230 runRemoveSplashAction(model, monitor); 231 } else { 232 runUpdateSplashAction(model, monitor, splashHandlerType); 233 } 234 } 235 runRemoveSplashAction(IPluginModelBase model, IProgressMonitor monitor)236 private void runRemoveSplashAction(IPluginModelBase model, IProgressMonitor monitor) throws CoreException { 237 // Create the remove splash handler action 238 fRemoveSplashAction = new RemoveSplashHandlerBindingAction(); 239 // Configure the action 240 fRemoveSplashAction.setFieldProductID(fProduct.getProductId()); 241 fRemoveSplashAction.setFieldTargetPackage(createTargetPackage()); 242 243 fRemoveSplashAction.setModel(model); 244 fRemoveSplashAction.setMonitor(monitor); 245 // Execute the action 246 fRemoveSplashAction.run(); 247 // If an core exception was thrown and caught, release it 248 fRemoveSplashAction.hasException(); 249 } 250 runUpdateSplashAction(IPluginModelBase model, IProgressMonitor monitor, String splashHandlerType)251 private void runUpdateSplashAction(IPluginModelBase model, IProgressMonitor monitor, String splashHandlerType) throws CoreException { 252 // Create the update splash handler action 253 fUpdateSplashAction = new UpdateSplashHandlerAction(); 254 // Configure the action 255 String id = createAttributeValueID(); 256 fUpdateSplashAction.setFieldID(id); 257 fUpdateSplashAction.setFieldClass(createAttributeValueClass()); 258 fUpdateSplashAction.setFieldSplashID(id); 259 fUpdateSplashAction.setFieldProductID(fProduct.getProductId()); 260 fUpdateSplashAction.setFieldTemplate(splashHandlerType); 261 fUpdateSplashAction.setFieldPluginID(fPluginId); 262 263 fUpdateSplashAction.setModel(model); 264 fUpdateSplashAction.setMonitor(monitor); 265 // Execute the action 266 fUpdateSplashAction.run(); 267 // If an core exception was thrown and caught, release it 268 fUpdateSplashAction.hasException(); 269 } 270 271 @Override run(IProgressMonitor monitor)272 public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { 273 try { 274 IFile file = getFile(); 275 if (!file.exists()) { 276 createNewFile(file, monitor); 277 } else { 278 modifyExistingFile(file, monitor); 279 } 280 updateSingleton(monitor); 281 } catch (CoreException e) { 282 throw new InvocationTargetException(e); 283 } 284 } 285 createNewFile(IFile file, IProgressMonitor monitor)286 private void createNewFile(IFile file, IProgressMonitor monitor) throws CoreException { 287 WorkspacePluginModelBase model = (WorkspacePluginModelBase) getModel(file); 288 IPluginBase base = model.getPluginBase(); 289 base.setSchemaVersion(TargetPlatformHelper.getSchemaVersion()); 290 base.add(createExtension(model)); 291 // Update the splash handler. Update plug-in model and copy files 292 updateSplashHandler(model, monitor); 293 // Update splash progress. Update plug-in model and copy files 294 updateSplashProgress(model, monitor); 295 296 model.save(); 297 } 298 createExtension(IPluginModelBase model)299 private IPluginExtension createExtension(IPluginModelBase model) throws CoreException { 300 IPluginExtension extension = model.getFactory().createExtension(); 301 extension.setPoint("org.eclipse.core.runtime.products"); //$NON-NLS-1$ 302 extension.setId(fProductId); 303 extension.add(createExtensionContent(extension)); 304 return extension; 305 } 306 createExtensionContent(IPluginExtension extension)307 private IPluginElement createExtensionContent(IPluginExtension extension) throws CoreException { 308 IPluginElement element = extension.getModel().getFactory().createElement(extension); 309 element.setName("product"); //$NON-NLS-1$ 310 element.setAttribute("name", fProduct.getName()); //$NON-NLS-1$ 311 element.setAttribute("application", fApplication); //$NON-NLS-1$ 312 313 IPluginElement child = createElement(element, IProductConstants.WINDOW_IMAGES, getWindowImagesString()); 314 if (child != null) 315 element.add(child); 316 317 child = createElement(element, IProductConstants.ABOUT_TEXT, getAboutText()); 318 if (child != null) 319 element.add(child); 320 321 child = createElement(element, IProductConstants.ABOUT_IMAGE, getAboutImage()); 322 if (child != null) 323 element.add(child); 324 325 child = createElement(element, IProductConstants.STARTUP_FOREGROUND_COLOR, getForegroundColor()); 326 if (child != null) 327 element.add(child); 328 329 child = createElement(element, IProductConstants.STARTUP_PROGRESS_RECT, getProgressRect()); 330 if (child != null) 331 element.add(child); 332 333 child = createElement(element, IProductConstants.STARTUP_MESSAGE_RECT, getMessageRect()); 334 if (child != null) 335 element.add(child); 336 337 child = createElement(element, IProductConstants.PREFERENCE_CUSTOMIZATION, getPreferenceCustomization()); 338 if (child != null) 339 element.add(child); 340 341 child = createElement(element, APPLICATION_CSS, getApplicationCSS()); 342 if (child != null) 343 element.add(child); 344 345 return element; 346 } 347 createElement(IPluginElement parent, String name, String value)348 private IPluginElement createElement(IPluginElement parent, String name, String value) throws CoreException { 349 IPluginElement element = null; 350 if (value != null && value.length() > 0) { 351 element = parent.getModel().getFactory().createElement(parent); 352 element.setName("property"); //$NON-NLS-1$ 353 element.setAttribute("name", name); //$NON-NLS-1$ 354 element.setAttribute("value", value); //$NON-NLS-1$ 355 } 356 return element; 357 } 358 getAboutText()359 private String getAboutText() { 360 IAboutInfo info = fProduct.getAboutInfo(); 361 if (info != null) { 362 String text = info.getText(); 363 return text == null || text.length() == 0 ? null : text; 364 } 365 return null; 366 } 367 getAboutImage()368 private String getAboutImage() { 369 IAboutInfo info = fProduct.getAboutInfo(); 370 return info != null ? getURL(info.getImagePath()) : null; 371 } 372 getURL(String location)373 private String getURL(String location) { 374 if (location == null || location.trim().length() == 0) 375 return null; 376 IPath path = new Path(location); 377 if (!path.isAbsolute()) 378 return location; 379 String projectName = path.segment(0); 380 IProject project = PDEPlugin.getWorkspace().getRoot().getProject(projectName); 381 if (project.exists()) { 382 IPluginModelBase model = PluginRegistry.findModel(project); 383 if (model != null) { 384 String id = model.getPluginBase().getId(); 385 if (fPluginId.equals(id)) 386 return path.removeFirstSegments(1).toString(); 387 return "platform:/plugin/" + id + "/" + path.removeFirstSegments(1); //$NON-NLS-1$ //$NON-NLS-2$ 388 } 389 } 390 return location; 391 } 392 getFullyQualifiedURL(String location)393 private String getFullyQualifiedURL(String location) { 394 if (location == null || location.trim().length() == 0) 395 return null; 396 IPath path = new Path(location); 397 if (!path.isAbsolute()) 398 return location; 399 String projectName = path.segment(0); 400 IProject project = PDEPlugin.getWorkspace().getRoot().getProject(projectName); 401 if (project.exists()) { 402 IPluginModelBase model = PluginRegistry.findModel(project); 403 if (model != null) { 404 String id = model.getPluginBase().getId(); 405 return "platform:/plugin/" + id + "/" + path.removeFirstSegments(1); //$NON-NLS-1$ //$NON-NLS-2$ 406 } 407 } 408 return location; 409 } 410 getWindowImagesString()411 private String getWindowImagesString() { 412 IWindowImages images = fProduct.getWindowImages(); 413 StringBuilder buffer = new StringBuilder(); 414 if (images != null) { 415 for (int i = 0; i < IWindowImages.TOTAL_IMAGES; i++) { 416 String image = getURL(images.getImagePath(i)); 417 if (image != null) { 418 if (buffer.length() > 0) 419 buffer.append(","); //$NON-NLS-1$ 420 buffer.append(image); 421 } 422 423 } 424 } 425 return buffer.length() == 0 ? null : buffer.toString(); 426 } 427 getForegroundColor()428 private String getForegroundColor() { 429 ISplashInfo info = fProduct.getSplashInfo(); 430 return info != null ? info.getForegroundColor() : null; 431 } 432 getProgressRect()433 private String getProgressRect() { 434 ISplashInfo info = fProduct.getSplashInfo(); 435 return info != null ? SplashInfo.getGeometryString(info.getProgressGeometry()) : null; 436 } 437 getMessageRect()438 private String getMessageRect() { 439 ISplashInfo info = fProduct.getSplashInfo(); 440 return info != null ? SplashInfo.getGeometryString(info.getMessageGeometry()) : null; 441 } 442 getPreferenceCustomization()443 private String getPreferenceCustomization() { 444 IPreferencesInfo info = fProduct.getPreferencesInfo(); 445 if (info != null) { 446 String text = info.getPreferenceCustomizationPath(); 447 return text == null || text.length() == 0 ? null : getFullyQualifiedURL(text); 448 } 449 return null; 450 } 451 getApplicationCSS()452 private String getApplicationCSS() { 453 ICSSInfo info = fProduct.getCSSInfo(); 454 if (info != null) { 455 String text = info.getFilePath(); 456 return text == null || text.length() == 0 ? null : getFullyQualifiedURL(text); 457 } 458 return null; 459 } 460 modifyExistingFile(IFile file, IProgressMonitor monitor)461 private void modifyExistingFile(IFile file, IProgressMonitor monitor) throws CoreException { 462 IStatus status = PDEPlugin.getWorkspace().validateEdit(new IFile[] {file}, getShell()); 463 if (status.getSeverity() != IStatus.OK) 464 throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.pde.ui", IStatus.ERROR, NLS.bind(PDEUIMessages.ProductDefinitionOperation_readOnly, fPluginId), null)); //$NON-NLS-1$ 465 466 ModelModification mod = new ModelModification(file) { 467 @Override 468 protected void modifyModel(IBaseModel model, IProgressMonitor monitor) throws CoreException { 469 if (!(model instanceof IPluginModelBase)) 470 return; 471 IPluginExtension extension = findProductExtension((IPluginModelBase) model); 472 if (extension == null) 473 insertNewExtension((IPluginModelBase) model); 474 else 475 modifyExistingExtension(extension); 476 // Update the splash handler. Update plug-in model and copy files 477 updateSplashHandler((IPluginModelBase) model, monitor); 478 // Update splash progress. Update plug-in model and copy files 479 updateSplashProgress((IPluginModelBase) model, monitor); 480 } 481 }; 482 PDEModelUtility.modifyModel(mod, monitor); 483 } 484 findProductExtension(IPluginModelBase model)485 private IPluginExtension findProductExtension(IPluginModelBase model) { 486 IPluginExtension[] extensions = model.getPluginBase().getExtensions(); 487 for (IPluginExtension extension : extensions) { 488 String point = extension.getPoint(); 489 String id = extension.getId(); 490 if (fProductId.equals(id) && "org.eclipse.core.runtime.products".equals(point)) { //$NON-NLS-1$ 491 return extension; 492 } 493 } 494 return null; 495 } 496 insertNewExtension(IPluginModelBase model)497 private void insertNewExtension(IPluginModelBase model) throws CoreException { 498 IPluginExtension extension = createExtension(model); 499 model.getPluginBase().add(extension); 500 } 501 modifyExistingExtension(IPluginExtension extension)502 private void modifyExistingExtension(IPluginExtension extension) throws CoreException { 503 if (extension.getChildCount() == 0) { 504 insertNewProductElement(extension); 505 return; 506 } 507 508 PluginElementNode element = (PluginElementNode) extension.getChildren()[0]; 509 510 if (!"product".equals(element.getName())) { //$NON-NLS-1$ 511 insertNewProductElement(extension); 512 return; 513 } 514 515 element.setAttribute("application", fApplication); //$NON-NLS-1$ 516 element.setAttribute("name", fProduct.getName()); //$NON-NLS-1$ 517 synchronizeChild(element, IProductConstants.APP_NAME, fProduct.getName()); 518 519 synchronizeChild(element, IProductConstants.ABOUT_IMAGE, getAboutImage()); 520 synchronizeChild(element, IProductConstants.ABOUT_TEXT, getAboutText()); 521 synchronizeChild(element, IProductConstants.WINDOW_IMAGES, getWindowImagesString()); 522 synchronizeChild(element, IProductConstants.STARTUP_FOREGROUND_COLOR, getForegroundColor()); 523 synchronizeChild(element, IProductConstants.STARTUP_MESSAGE_RECT, getMessageRect()); 524 synchronizeChild(element, IProductConstants.STARTUP_PROGRESS_RECT, getProgressRect()); 525 synchronizeChild(element, IProductConstants.PREFERENCE_CUSTOMIZATION, getPreferenceCustomization()); 526 if (fProduct.getCSSInfo() != null) { 527 synchronizeChild(element, APPLICATION_CSS, getApplicationCSS()); 528 } 529 530 } 531 synchronizeChild(IPluginElement element, String propertyName, String value)532 private void synchronizeChild(IPluginElement element, String propertyName, String value) throws CoreException { 533 IPluginElement child = null; 534 IPluginObject[] children = element.getChildren(); 535 for (IPluginObject childObject : children) { 536 IPluginElement candidate = (IPluginElement) childObject; 537 if (candidate.getName().equals("property")) { //$NON-NLS-1$ 538 IPluginAttribute attr = candidate.getAttribute("name"); //$NON-NLS-1$ 539 if (attr != null && attr.getValue().equals(propertyName)) { 540 child = candidate; 541 break; 542 } 543 } 544 } 545 if (child != null && value == null) 546 element.remove(child); 547 548 if (value == null) 549 return; 550 551 if (child == null) { 552 child = element.getModel().getFactory().createElement(element); 553 child.setName("property"); //$NON-NLS-1$ 554 element.add(child); 555 } 556 child.setAttribute("value", value); //$NON-NLS-1$ 557 child.setAttribute("name", propertyName); //$NON-NLS-1$ 558 } 559 insertNewProductElement(IPluginExtension extension)560 private void insertNewProductElement(IPluginExtension extension) throws CoreException { 561 IPluginElement element = createExtensionContent(extension); 562 extension.add(element); 563 } 564 565 } 566