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