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