1 /******************************************************************************* 2 * Copyright (c) 2007, 2017 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 * Benjamin Cabe <benjamin.cabe@anyware-tech.com> - bug 274107 14 * Johannes Ahlers <Johannes.Ahlers@gmx.de> - bug 477677 15 *******************************************************************************/ 16 17 package org.eclipse.pde.internal.ui.wizards.product; 18 19 import java.nio.charset.Charset; 20 import org.eclipse.core.filebuffers.*; 21 import org.eclipse.core.resources.*; 22 import org.eclipse.core.runtime.*; 23 import org.eclipse.jface.text.BadLocationException; 24 import org.eclipse.jface.text.IDocument; 25 import org.eclipse.pde.core.build.IBuildEntry; 26 import org.eclipse.pde.core.build.IBuildModel; 27 import org.eclipse.pde.core.plugin.*; 28 import org.eclipse.pde.internal.core.build.WorkspaceBuildModel; 29 import org.eclipse.pde.internal.core.project.PDEProject; 30 import org.eclipse.pde.internal.core.text.build.BuildModel; 31 import org.eclipse.pde.internal.core.text.build.PropertiesTextChangeListener; 32 import org.eclipse.pde.internal.core.util.PDETextHelper; 33 import org.eclipse.pde.internal.ui.IPDEUIConstants; 34 import org.eclipse.pde.internal.ui.PDEUIMessages; 35 import org.eclipse.text.edits.*; 36 37 public class UpdateSplashProgressOperation implements IWorkspaceRunnable { 38 39 public static final String F_EXTENSION_PRODUCT = "org.eclipse.core.runtime.products"; //$NON-NLS-1$ 40 public static final String F_ELEMENT_PRODUCT = "product"; //$NON-NLS-1$ 41 public static final String F_ELEMENT_PROPERTY = "property"; //$NON-NLS-1$ 42 public static final String F_ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ 43 public static final String F_ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$ 44 public static final String F_ATTRIBUTE_NAME_PREFCUST = "preferenceCustomization"; //$NON-NLS-1$ 45 public static final String F_KEY_SHOW_PROGRESS = "org.eclipse.ui/SHOW_PROGRESS_ON_STARTUP"; //$NON-NLS-1$ 46 public static final String F_FILE_NAME_PLUGIN_CUSTOM = "plugin_customization.ini"; //$NON-NLS-1$ 47 48 private static final String PLUGIN_URL_PREFIX = "platform:/plugin/"; //$NON-NLS-1$ 49 50 private IPluginModelBase fModel; 51 private boolean fShowProgress; 52 private IProject fProject; 53 private String fProductID; 54 protected String fPluginId; 55 private ITextFileBufferManager fTextFileBufferManager; 56 private ITextFileBuffer fTextFileBuffer; 57 private PropertiesTextChangeListener fPropertiesListener; 58 UpdateSplashProgressOperation()59 public UpdateSplashProgressOperation() { 60 reset(); 61 } 62 reset()63 public void reset() { 64 // External Fields 65 fModel = null; 66 fProductID = null; 67 fShowProgress = true; 68 fProject = null; 69 fPluginId = null; 70 // Internal Fields 71 fTextFileBufferManager = null; 72 fPropertiesListener = null; 73 fTextFileBuffer = null; 74 } 75 setPluginID(String pluginID)76 public void setPluginID(String pluginID) { 77 fPluginId = pluginID; 78 } 79 setModel(IPluginModelBase model)80 public void setModel(IPluginModelBase model) { 81 fModel = model; 82 } 83 setShowProgress(boolean showProgress)84 public void setShowProgress(boolean showProgress) { 85 fShowProgress = showProgress; 86 } 87 setProductID(String productID)88 public void setProductID(String productID) { 89 fProductID = productID; 90 } 91 setProject(IProject project)92 public void setProject(IProject project) { 93 fProject = project; 94 } 95 96 @Override run(IProgressMonitor monitor)97 public void run(IProgressMonitor monitor) throws CoreException { 98 SubMonitor subMonitor = SubMonitor.convert(monitor, 99 PDEUIMessages.UpdateSplashProgressAction_msgProgressCustomizingSplash, 1); 100 // Perform the operation 101 update(subMonitor.split(1)); 102 } 103 update(IProgressMonitor monitor)104 private void update(IProgressMonitor monitor) throws CoreException { 105 SubMonitor subMonitor = SubMonitor.convert(monitor, 4); 106 // Find the product extension 107 IPluginExtension productExtension = findProductExtension(); 108 subMonitor.split(1); 109 // Ensure product extension exists 110 if (productExtension == null) { 111 // Something is seriously wrong 112 return; 113 } 114 // Find the product element 115 IPluginElement productElement = findProductElement(productExtension); 116 subMonitor.split(1); 117 // Ensure product element exists 118 if (productElement == null) { 119 // Something is seriously wrong 120 return; 121 } 122 // Find the preference customization property 123 IPluginElement propertyElement = findPrefCustPropertyElement(productElement); 124 subMonitor.split(1); 125 if ((propertyElement == null) && fShowProgress) { 126 // Operation: Add progress 127 // The preference customization property does not exist 128 // Create it 129 addPreferenceCustomizationElement(productElement, subMonitor.split(1)); 130 } else if (propertyElement == null) { 131 // Operation: Remove progress 132 // The preference customization property does not exist 133 // NO-OP 134 // Note: If plugin_customization.ini exists in the root of the 135 // plug-in, this is the default file name in the default location 136 // Its values will be loaded. 137 // Therefore, since it is possible for a the show progress on 138 // startup key to be present and true, make it false 139 updateDefaultPluginCustomizationFile(subMonitor.split(1)); 140 } else { 141 // Operations: Add progress, Remove progress 142 // The preference customization property exists 143 // Update it 144 updatePreferenceCustomizationElement(propertyElement, subMonitor.split(1)); 145 } 146 } 147 isAttributeValueDefined(IPluginAttribute valueAttribute)148 private boolean isAttributeValueDefined(IPluginAttribute valueAttribute) { 149 if (valueAttribute == null) { 150 return false; 151 } 152 return PDETextHelper.isDefined(valueAttribute.getValue()); 153 } 154 isFileExist(IResource resource)155 private boolean isFileExist(IResource resource) { 156 if (resource == null) { 157 return false; 158 } 159 return (resource instanceof IFile); 160 } 161 updatePreferenceCustomizationElement(IPluginElement propertyElement, IProgressMonitor monitor)162 private void updatePreferenceCustomizationElement(IPluginElement propertyElement, IProgressMonitor monitor) 163 throws CoreException { 164 // Get the plug-in customization ini file name 165 IPluginAttribute valueAttribute = propertyElement.getAttribute(F_ATTRIBUTE_VALUE); 166 // Ensure we have a plug-in customization ini file value 167 boolean isAttributeValueNotDefined = !isAttributeValueDefined(valueAttribute); 168 if (isAttributeValueNotDefined && fShowProgress) { 169 // Operation: Add progress 170 // Value is not defined 171 // Create the default plugin customization ini file 172 createDefaultPluginCustomizationFile(propertyElement, monitor); 173 return; 174 } else if (isAttributeValueNotDefined) { 175 // Operation: Remove progress 176 // Fall-back to the default plugin customization ini file 177 updateDefaultPluginCustomizationFile(monitor); 178 return; 179 } 180 // Get the plugin customization ini file name 181 String pluginCustomizationFileName = valueAttribute.getValue(); 182 183 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=435452 184 // Qualified paths should be searched in the workspace, and non-qualified paths 185 // should be assumed to refer to the product's defining plugin project. 186 int index = pluginCustomizationFileName.indexOf(PLUGIN_URL_PREFIX); 187 if (index >= 0) { 188 pluginCustomizationFileName = pluginCustomizationFileName.substring(index + PLUGIN_URL_PREFIX.length()); 189 } else if (pluginCustomizationFileName.equals(F_FILE_NAME_PLUGIN_CUSTOM)) { 190 pluginCustomizationFileName = fPluginId + IPath.SEPARATOR + pluginCustomizationFileName; 191 } 192 // Find the file in the workspace 193 IResource resource = fProject.getWorkspace().getRoot().findMember(pluginCustomizationFileName); 194 // Ensure the plug-in customization ini file exists 195 boolean isFileNotExist = !isFileExist(resource); 196 if (isFileNotExist && fShowProgress) { 197 // Operation: Add progress 198 // File does not exist in the project 199 // Create the default plugin customization ini file 200 createDefaultPluginCustomizationFile(propertyElement, monitor); 201 return; 202 } else if (isFileNotExist) { 203 // Operation: Remove progress 204 // NO-OP 205 return; 206 } 207 // Operations: Add progress, Remove progress 208 // File exists in the project 209 // Update it 210 updatePluginCustomizationFile((IFile) resource, monitor); 211 } 212 createCoreException(String message, Throwable exception)213 private CoreException createCoreException(String message, Throwable exception) { 214 IStatus status = new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, message, exception); 215 return new CoreException(status); 216 } 217 createCoreException(String message)218 private CoreException createCoreException(String message) { 219 IStatus status = new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, message); 220 return new CoreException(status); 221 } 222 getTextFileBufferManager()223 private ITextFileBufferManager getTextFileBufferManager() throws CoreException { 224 if (fTextFileBufferManager == null) { 225 // Get the text file buffer manager 226 fTextFileBufferManager = FileBuffers.getTextFileBufferManager(); 227 } 228 // Ensure manager is defined 229 if (fTextFileBufferManager == null) { 230 throw createCoreException(PDEUIMessages.UpdateSplashProgressAction_msgErrorTextFileBufferManager); 231 } 232 return fTextFileBufferManager; 233 } 234 getPluginCustomizationBuffer(IFile file)235 private ITextFileBuffer getPluginCustomizationBuffer(IFile file) throws CoreException { 236 IPath path = file.getFullPath(); 237 LocationKind kind = LocationKind.IFILE; 238 // Get the text file buffer 239 fTextFileBuffer = getTextFileBufferManager().getTextFileBuffer(path, kind); 240 // Ensure buffer is defined 241 if (fTextFileBuffer == null) { 242 throw createCoreException(PDEUIMessages.UpdateSplashProgressAction_msgErrorTextFileBuffer); 243 } 244 return fTextFileBuffer; 245 } 246 getBuildModel(IFile file)247 private BuildModel getBuildModel(IFile file) throws CoreException { 248 // Convert the file to a document 249 // Defines a text file buffer 250 IDocument document = getPluginCustomizationBuffer(file).getDocument(); 251 // Create the plugin customization model 252 BuildModel pluginCustomModel = new BuildModel(document, false); 253 pluginCustomModel.setUnderlyingResource(file); 254 pluginCustomModel.setCharset(Charset.forName(file.getCharset())); 255 // Create the listener to listen to text edit operations 256 // (Operations need to be collected and applied to the document before 257 // saving) 258 fPropertiesListener = new PropertiesTextChangeListener(document); 259 pluginCustomModel.addModelChangedListener(fPropertiesListener); 260 261 return pluginCustomModel; 262 } 263 updatePluginCustomizationFile(IFile file, IProgressMonitor monitor)264 private void updatePluginCustomizationFile(IFile file, IProgressMonitor monitor) throws CoreException { 265 IPath path = file.getFullPath(); 266 LocationKind kind = LocationKind.IFILE; 267 // Connect to the text file buffer manager 268 SubMonitor subMonitor = SubMonitor.convert(monitor, 3); 269 getTextFileBufferManager().connect(path, kind, subMonitor.split(1)); 270 try { 271 // Create the plugin customization model 272 BuildModel pluginCustomModel = getBuildModel(file); 273 // Load the plugin customization file 274 pluginCustomModel.load(); 275 // Find the show progress on startup key 276 IBuildEntry showProgressEntry = pluginCustomModel.getBuild().getEntry(F_KEY_SHOW_PROGRESS); 277 // Check to see if we found the entry 278 if (showProgressEntry == null) { 279 // No show progress entry 280 // Create one 281 addShowProgressEntry(pluginCustomModel); 282 } else { 283 // Show progress entry exists 284 // Update it 285 updateShowProgressEntry(showProgressEntry); 286 } 287 // Save plugin customization file changes 288 savePluginCustomFileChanges(pluginCustomModel, subMonitor.split(1)); 289 } catch (MalformedTreeException | BadLocationException e) { 290 throw createCoreException(PDEUIMessages.UpdateSplashProgressAction_msgErrorCustomFileSaveFailed, e); 291 } finally { 292 // Disconnect from the text file buffer manager 293 getTextFileBufferManager().disconnect(path, kind, subMonitor.split(1)); 294 } 295 } 296 savePluginCustomFileChanges(BuildModel pluginCustomModel, IProgressMonitor monitor)297 private void savePluginCustomFileChanges(BuildModel pluginCustomModel, IProgressMonitor monitor) 298 throws CoreException, MalformedTreeException, BadLocationException { 299 SubMonitor subMonitor = SubMonitor.convert(monitor, 1); 300 // Ensure there is something to save 301 if (pluginCustomModel.isDirty() == false) { 302 // Nothing to save 303 return; 304 } else if (fPropertiesListener == null) { 305 // Prereq: Serious setup problem 306 return; 307 } else if (fTextFileBuffer == null) { 308 // Prereq: Serious setup problem 309 return; 310 } 311 // Get the accumulated text operations (if any) 312 TextEdit[] edits = fPropertiesListener.getTextOperations(); 313 if (edits.length == 0) { 314 // Nothing to save 315 return; 316 } 317 // Apply text editor operations to the document 318 MultiTextEdit multi = new MultiTextEdit(); 319 multi.addChildren(edits); 320 multi.apply(pluginCustomModel.getDocument()); 321 // Ensure there is something to save 322 if (fTextFileBuffer.isDirty() == false) { 323 // Nothing to save 324 return; 325 } 326 // Perform the actual save 327 fTextFileBuffer.commit(subMonitor.split(1), true); 328 } 329 getBooleanValue(boolean value)330 private String getBooleanValue(boolean value) { 331 if (value) { 332 return Boolean.TRUE.toString(); 333 } 334 return Boolean.FALSE.toString(); 335 } 336 updateShowProgressEntry(IBuildEntry showProgressEntry)337 private void updateShowProgressEntry(IBuildEntry showProgressEntry) throws CoreException { 338 // Convert boolean to String 339 String newBooleanValue = getBooleanValue(fShowProgress); 340 // Get the value of the show progress entry 341 String[] values = showProgressEntry.getTokens(); 342 // There should only be one value (the first one) 343 if (values.length == 0) { 344 // No values 345 // Define true value 346 showProgressEntry.addToken(newBooleanValue); 347 return; 348 } else if (values.length > 1) { 349 // Too many values 350 // Remove all values and add the true value 351 removeEntryTokens(showProgressEntry, values); 352 showProgressEntry.addToken(newBooleanValue); 353 return; 354 } 355 // Get the boolean value 356 String oldBooleanValue = values[0]; 357 // If the old value is not the same as the new value, replace the old 358 // with the new 359 if (oldBooleanValue.equals(newBooleanValue) == false) { 360 showProgressEntry.renameToken(oldBooleanValue, newBooleanValue); 361 } 362 // Nothing to do if the value is the same already 363 } 364 removeEntryTokens(IBuildEntry showProgressEntry, String[] values)365 private void removeEntryTokens(IBuildEntry showProgressEntry, String[] values) throws CoreException { 366 // Remove each token 367 for (String value : values) { 368 showProgressEntry.removeToken(value); 369 } 370 } 371 addShowProgressEntry(IBuildModel pluginCustomModel)372 private void addShowProgressEntry(IBuildModel pluginCustomModel) throws CoreException { 373 // Create the show progress key 374 IBuildEntry showProgressEntry = pluginCustomModel.getFactory().createEntry(F_KEY_SHOW_PROGRESS); 375 // Set the show progress value 376 showProgressEntry.addToken(getBooleanValue(fShowProgress)); 377 // Add the show progress entry to the model 378 pluginCustomModel.getBuild().add(showProgressEntry); 379 } 380 createPluginCustomizationFile()381 private void createPluginCustomizationFile() throws CoreException { 382 // Create a handle to the workspace file 383 // (Does not exist yet) 384 IFile file = fProject.getFile(F_FILE_NAME_PLUGIN_CUSTOM); 385 // Create the plugin customization model 386 WorkspaceBuildModel pluginCustomModel = new WorkspaceBuildModel(file); 387 // Add the show progress entry to the model 388 addShowProgressEntry(pluginCustomModel); 389 // Create the file by saving the model 390 pluginCustomModel.save(); 391 392 // add the file to build.properties 393 IFile buildProps = PDEProject.getBuildProperties(fProject); 394 if (buildProps.exists()) { 395 WorkspaceBuildModel model = new WorkspaceBuildModel(buildProps); 396 model.load(); 397 if (model.isLoaded()) { 398 IBuildEntry entry = model.getBuild().getEntry("bin.includes"); //$NON-NLS-1$ 399 if (entry == null) { 400 entry = model.getFactory().createEntry("bin.includes"); //$NON-NLS-1$ 401 model.getBuild().add(entry); 402 } 403 if (!entry.contains(F_FILE_NAME_PLUGIN_CUSTOM)) 404 entry.addToken(F_FILE_NAME_PLUGIN_CUSTOM); 405 model.save(); 406 } 407 } 408 } 409 addPreferenceCustomizationElement(IPluginElement productElement, IProgressMonitor monitor)410 private void addPreferenceCustomizationElement(IPluginElement productElement, IProgressMonitor monitor) 411 throws CoreException { 412 // Get the factory 413 IExtensionsModelFactory factory = productElement.getModel().getFactory(); 414 // Create a property element 415 IPluginElement propertyElement = factory.createElement(productElement); 416 propertyElement.setName(F_ELEMENT_PROPERTY); 417 // Create the name attribute 418 propertyElement.setAttribute(F_ATTRIBUTE_NAME, F_ATTRIBUTE_NAME_PREFCUST); 419 // Add the property element to the product element 420 productElement.add(propertyElement); 421 // Create the default plugin customization ini file 422 createDefaultPluginCustomizationFile(propertyElement, monitor); 423 } 424 createDefaultPluginCustomizationFile(IPluginElement propertyElement, IProgressMonitor monitor)425 private void createDefaultPluginCustomizationFile(IPluginElement propertyElement, IProgressMonitor monitor) 426 throws CoreException { 427 // Define the value as the default plugin customization ini file name 428 propertyElement.setAttribute(F_ATTRIBUTE_VALUE, F_FILE_NAME_PLUGIN_CUSTOM); 429 // Check to see if the default file already exists in the project 430 IResource resource = fProject.findMember(F_FILE_NAME_PLUGIN_CUSTOM); 431 // Ensure the plug-in customization ini file exists 432 SubMonitor subMonitor = SubMonitor.convert(monitor, 1); 433 if (isFileExist(resource)) { 434 // File exists in the project 435 // Update it 436 updatePluginCustomizationFile((IFile) resource, subMonitor.split(1)); 437 } else { 438 subMonitor.split(1); 439 // File does not exist in the project 440 // Create the plugin customization ini file 441 createPluginCustomizationFile(); 442 } 443 } 444 updateDefaultPluginCustomizationFile(IProgressMonitor monitor)445 private void updateDefaultPluginCustomizationFile(IProgressMonitor monitor) throws CoreException { 446 SubMonitor subMonitor = SubMonitor.convert(monitor, 1); 447 // Check to see if the default file already exists in the project 448 IResource resource = fProject.findMember(F_FILE_NAME_PLUGIN_CUSTOM); 449 if (isFileExist(resource)) { 450 // File exists in the project 451 // Update it 452 updatePluginCustomizationFile((IFile) resource, subMonitor.split(1)); 453 } 454 } 455 findPrefCustPropertyElement(IPluginElement productElement)456 private IPluginElement findPrefCustPropertyElement(IPluginElement productElement) { 457 // Ensure the produce element has children 458 if (productElement.getChildCount() == 0) { 459 return null; 460 } 461 // Get the product element children 462 IPluginObject[] objects = productElement.getChildren(); 463 // Process all children 464 for (IPluginObject object : objects) { 465 // Ensure we have an element 466 if ((object instanceof IPluginElement) == false) { 467 continue; 468 } 469 // Property elements are the only legitimate children of product elements 470 if (object.getName().equals(F_ELEMENT_PROPERTY) == false) { 471 continue; 472 } 473 IPluginElement element = (IPluginElement) object; 474 // Get the name 475 IPluginAttribute nameAttribute = element.getAttribute(F_ATTRIBUTE_NAME); 476 // Ensure we have a preference customization property 477 if (nameAttribute == null) { 478 continue; 479 } else if (PDETextHelper.isDefined(nameAttribute.getValue()) == false) { 480 continue; 481 } else if (nameAttribute.getValue().equals(F_ATTRIBUTE_NAME_PREFCUST) == false) { 482 continue; 483 } 484 485 return element; 486 } 487 return null; 488 } 489 findProductElement(IPluginExtension extension)490 private IPluginElement findProductElement(IPluginExtension extension) { 491 // The product extension is only allowed one child 492 if (extension.getChildCount() != 1) { 493 return null; 494 } 495 // Get the one child 496 IPluginObject pluginObject = extension.getChildren()[0]; 497 // Ensure that the child is an element 498 if ((pluginObject instanceof IPluginElement) == false) { 499 return null; 500 } 501 // Ensure that the child is a product element 502 if (pluginObject.getName().equals(F_ELEMENT_PRODUCT) == false) { 503 return null; 504 } 505 return (IPluginElement) pluginObject; 506 } 507 findProductExtension()508 private IPluginExtension findProductExtension() { 509 // Get all the extensions 510 IPluginExtension[] extensions = fModel.getPluginBase().getExtensions(); 511 // Get the extension matching the product extension point ID 512 // and product ID 513 for (IPluginExtension extension : extensions) { 514 // Get the extension point 515 String point = extension.getPoint(); 516 // Ensure we have a product extension 517 if (point.equals(F_EXTENSION_PRODUCT) == false) { 518 continue; 519 } 520 // Ensure we have the exact product 521 // Get the fully qualified product ID 522 String id = fPluginId + '.' + extension.getId(); 523 if (id.equals(fProductID) == false) { 524 continue; 525 } 526 return extension; 527 } 528 return null; 529 } 530 531 } 532