001: /*******************************************************************************
002: * Copyright (c) 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.pde.internal.ui.wizards.toc;
011:
012: import java.lang.reflect.InvocationTargetException;
013: import java.util.ArrayList;
014:
015: import org.eclipse.core.resources.IFile;
016: import org.eclipse.core.runtime.CoreException;
017: import org.eclipse.core.runtime.IProgressMonitor;
018: import org.eclipse.core.runtime.IStatus;
019: import org.eclipse.core.runtime.Status;
020: import org.eclipse.core.runtime.jobs.ISchedulingRule;
021: import org.eclipse.pde.core.IBaseModel;
022: import org.eclipse.pde.core.build.IBuild;
023: import org.eclipse.pde.core.build.IBuildEntry;
024: import org.eclipse.pde.core.build.IBuildModel;
025: import org.eclipse.pde.core.plugin.IPluginAttribute;
026: import org.eclipse.pde.core.plugin.IPluginBase;
027: import org.eclipse.pde.core.plugin.IPluginElement;
028: import org.eclipse.pde.core.plugin.IPluginExtension;
029: import org.eclipse.pde.core.plugin.IPluginModelBase;
030: import org.eclipse.pde.core.plugin.IPluginObject;
031: import org.eclipse.pde.core.plugin.ISharedExtensionsModel;
032: import org.eclipse.pde.core.plugin.PluginRegistry;
033: import org.eclipse.pde.internal.core.ClasspathUtilCore;
034: import org.eclipse.pde.internal.core.ICoreConstants;
035: import org.eclipse.pde.internal.core.TargetPlatformHelper;
036: import org.eclipse.pde.internal.core.build.BuildObject;
037: import org.eclipse.pde.internal.core.build.WorkspaceBuildModel;
038: import org.eclipse.pde.internal.core.ibundle.IBundle;
039: import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
040: import org.eclipse.pde.internal.core.ibundle.IManifestHeader;
041: import org.eclipse.pde.internal.core.itoc.ITocConstants;
042: import org.eclipse.pde.internal.core.plugin.WorkspaceFragmentModel;
043: import org.eclipse.pde.internal.core.plugin.WorkspacePluginModel;
044: import org.eclipse.pde.internal.core.plugin.WorkspacePluginModelBase;
045: import org.eclipse.pde.internal.core.text.bundle.BundleSymbolicNameHeader;
046: import org.eclipse.pde.internal.core.text.bundle.RequireBundleHeader;
047: import org.eclipse.pde.internal.core.util.PDETextHelper;
048: import org.eclipse.pde.internal.ui.IPDEUIConstants;
049: import org.eclipse.pde.internal.ui.PDEPlugin;
050: import org.eclipse.pde.internal.ui.PDEUIMessages;
051: import org.eclipse.pde.internal.ui.util.ModelModification;
052: import org.eclipse.pde.internal.ui.util.PDEModelUtility;
053: import org.eclipse.swt.widgets.Shell;
054: import org.eclipse.ui.actions.WorkspaceModifyOperation;
055: import org.osgi.framework.Constants;
056:
057: /**
058: * RegisterTocOperation
059: *
060: */
061: public class RegisterTocOperation extends WorkspaceModifyOperation {
062:
063: public final static String F_TOC_EXTENSION_POINT_ID = "org.eclipse.help.toc"; //$NON-NLS-1$
064:
065: public static final String F_HELP_EXTENSION_ID = "org.eclipse.help"; //$NON-NLS-1$
066:
067: public static final String F_TOC_ATTRIBUTE_FILE = "file"; //$NON-NLS-1$
068:
069: public final static String F_TOC_ATTRIBUTE_PRIMARY = "primary"; //$NON-NLS-1$
070:
071: public final static String F_TOC_ATTRIBUTE_EXTRADIR = "extradir"; //$NON-NLS-1$
072:
073: public final static String F_TOC_ATTRIBUTE_CATEGORY = "category"; //$NON-NLS-1$
074:
075: private IRegisterTOCData fPage;
076:
077: private Shell fShell;
078:
079: /**
080: *
081: */
082: public RegisterTocOperation(IRegisterTOCData page, Shell shell) {
083: fPage = page;
084: fShell = shell;
085: }
086:
087: /**
088: * @param rule
089: */
090: public RegisterTocOperation(ISchedulingRule rule) {
091: super (rule);
092: }
093:
094: /* (non-Javadoc)
095: * @see org.eclipse.ui.actions.WorkspaceModifyOperation#execute(org.eclipse.core.runtime.IProgressMonitor)
096: */
097: protected void execute(IProgressMonitor monitor)
098: throws CoreException, InvocationTargetException,
099: InterruptedException {
100:
101: try {
102: boolean fragment = PluginRegistry.findModel(
103: fPage.getPluginProject()).isFragmentModel();
104: IFile file = fPage.getPluginProject().getFile(
105: fragment ? ICoreConstants.FRAGMENT_PATH
106: : ICoreConstants.PLUGIN_PATH);
107: // If the plug-in exists modify it accordingly; otherwise, create
108: // a new plug-in file
109: if (file.exists()) {
110: modifyExistingPluginFile(file, monitor);
111: } else {
112: createNewPluginFile(file, monitor);
113: }
114: } catch (CoreException e) {
115: throw new InvocationTargetException(e);
116: }
117: }
118:
119: /**
120: * FindCSExtensionResult
121: *
122: */
123: private static class FindTocExtensionResult {
124:
125: public IPluginExtension fTocExtension;
126:
127: public IPluginElement fTocElement;
128:
129: /**
130: *
131: */
132: public FindTocExtensionResult() {
133: fTocExtension = null;
134: fTocElement = null;
135: }
136:
137: /**
138: * @return
139: */
140: public boolean foundTocExtension() {
141: return (fTocExtension != null);
142: }
143:
144: /**
145: * @return
146: */
147: public boolean foundExactTocElement() {
148: return (fTocElement != null);
149: }
150: }
151:
152: /**
153: * @param file
154: * @param monitor
155: * @throws CoreException
156: */
157: private void modifyExistingPluginFile(IFile file,
158: IProgressMonitor monitor) throws CoreException {
159:
160: // Validate the operation
161: // Note: This is not accurate, we are validating the plugin.xml file
162: // but not the manifest.mf file
163: IStatus status = PDEPlugin.getWorkspace().validateEdit(
164: new IFile[] { file }, fShell);
165: if (status.getSeverity() != IStatus.OK) {
166: throw new CoreException(
167: new Status(
168: IStatus.ERROR,
169: IPDEUIConstants.PLUGIN_ID,
170: IStatus.ERROR,
171: PDEUIMessages.RegisterCSOperation_errorManifestReadOnly,
172: null));
173: }
174: // Perform the modification of the plugin manifest file
175: ModelModification mod = new ModelModification(fPage
176: .getPluginProject()) {
177: protected void modifyModel(IBaseModel model,
178: IProgressMonitor monitor) throws CoreException {
179: doModifyPluginModel(model, monitor);
180: doModifyManifestModel(model);
181: }
182: };
183: PDEModelUtility.modifyModel(mod, monitor);
184: }
185:
186: /**
187: * @param model
188: * @param monitor
189: * @throws CoreException
190: */
191: private void doModifyPluginModel(IBaseModel model,
192: IProgressMonitor monitor) throws CoreException {
193: if ((model instanceof IPluginModelBase) == false) {
194: return;
195: }
196: IPluginModelBase modelBase = (IPluginModelBase) model;
197: // Find an existing cheat sheet extension
198: FindTocExtensionResult result = findTocExtensionResult(modelBase);
199: // Check search results and act accordingly
200: if (result.foundTocExtension() && result.foundExactTocElement()) {
201: // An exact match to an existing TOC element was
202: // found. Update the element fields
203: modifyExistingElement(result.fTocElement, monitor);
204: } else if (result.foundTocExtension()) {
205: // No exact match to an existing TOC element found within
206: // the existing TOC extension. Update the
207: // existing extension by adding a new TOC element
208: // to it
209: modifyExistingExtension(result.fTocExtension, monitor);
210: } else {
211: // No existing TOC extension found, create a new
212: // extension
213: insertNewExtension(modelBase, monitor);
214: }
215: }
216:
217: /**
218: * @param modelBase
219: */
220: private void insertNewExtension(IPluginModelBase modelBase,
221: IProgressMonitor monitor) throws CoreException {
222: // Update progress work units
223: monitor
224: .beginTask(
225: PDEUIMessages.RegisterCSOperation_newCSExtensionExistingPlugin,
226: 1);
227: // Create the new extension
228: IPluginExtension extension = createExtensionToc(modelBase);
229: modelBase.getPluginBase().add(extension);
230: // Update progress work units
231: monitor.done();
232: }
233:
234: /**
235: * @param extension
236: */
237: private void modifyExistingExtension(IPluginExtension extension,
238: IProgressMonitor monitor) throws CoreException {
239: // Update progress work units
240: monitor
241: .beginTask(
242: PDEUIMessages.RegisterCSOperation_modCSExtensionExistingPlugin,
243: 1);
244: // Create new children for existing extension
245: createExtensionChildren(extension);
246: // Update progress work units
247: monitor.done();
248: }
249:
250: /**
251: * @param tocElement
252: * @param monitor
253: */
254: private void modifyExistingElement(IPluginElement tocElement,
255: IProgressMonitor monitor) throws CoreException {
256: // Update progress work units
257: monitor
258: .beginTask(
259: PDEUIMessages.RegisterCSOperation_modCSElementExistingPlugin,
260: 1);
261:
262: // Update the file
263: tocElement.setAttribute(F_TOC_ATTRIBUTE_FILE, fPage
264: .getDataTocFile());
265:
266: // Update the primary attribute
267: // But only if it already exists, or if this TOC will be primary
268: boolean primary = fPage.getDataPrimary();
269: if (primary
270: || tocElement.getAttribute(F_TOC_ATTRIBUTE_PRIMARY) != null) {
271: tocElement.setAttribute(F_TOC_ATTRIBUTE_PRIMARY, Boolean
272: .toString(primary));
273: }
274:
275: // Update progress work units
276: monitor.done();
277: }
278:
279: /**
280: * @param model
281: * @param extensionResult cheat sheet extension found or null
282: * @param elementResult cheat sheet element found or null
283: * @return
284: */
285: private FindTocExtensionResult findTocExtensionResult(
286: IPluginModelBase model) {
287: // Container for result
288: FindTocExtensionResult result = new FindTocExtensionResult();
289: // Find all cheat sheet extensions within the host plug-in
290: IPluginExtension[] extensions = findTOCExtensions(model);
291: // Process all TOC extensions
292: // Extension search results
293: // (1) An existing extension containing a TOC element with the
294: // exact TOC filename
295: // (2) An existing extension (last one found) containing 0 or more
296: // TOC elements
297: // (3) No existing extension
298: for (int i = 0; i < extensions.length; i++) {
299: // TOC extension match found
300: result.fTocExtension = extensions[i];
301: // Check for children
302: if (extensions[i].getChildCount() == 0) {
303: // Extension has no children, skip to the next extension
304: continue;
305: }
306:
307: IPluginObject[] pluginObjects = extensions[i].getChildren();
308: // Process all children
309: for (int j = 0; j < pluginObjects.length; j++) {
310: if (pluginObjects[j] instanceof IPluginElement) {
311: IPluginElement element = (IPluginElement) pluginObjects[j];
312: // Find TOC elements
313: if (element.getName().equals(
314: ITocConstants.ELEMENT_TOC)) {
315: // TOC element
316: // Get the file attribute
317: IPluginAttribute fileAttribute = element
318: .getAttribute(F_TOC_ATTRIBUTE_FILE);
319: // Check for the filename for this TOC element
320: if ((fileAttribute != null)
321: && PDETextHelper
322: .isDefined(fileAttribute
323: .getValue())
324: && fPage.getDataTocFile().equals(
325: fileAttribute.getValue())) {
326: // Matching TOC element found
327: result.fTocElement = element;
328: return result;
329: }
330: }
331: }
332: }
333: }
334:
335: return result;
336: }
337:
338: public static IPluginExtension[] findTOCExtensions(
339: ISharedExtensionsModel model) {
340: IPluginExtension[] extensions = model.getExtensions()
341: .getExtensions();
342:
343: ArrayList tocExtensions = new ArrayList();
344: for (int i = 0; i < extensions.length; i++) {
345: String point = extensions[i].getPoint();
346: if (F_TOC_EXTENSION_POINT_ID.equals(point)) {
347: tocExtensions.add(extensions[i]);
348: }
349: }
350: return (IPluginExtension[]) tocExtensions
351: .toArray(new IPluginExtension[tocExtensions.size()]);
352: }
353:
354: /**
355: * @param file
356: * @param monitor
357: */
358: private void createNewPluginFile(IFile file,
359: IProgressMonitor monitor) throws CoreException {
360:
361: // Update progress work units
362: monitor
363: .beginTask(
364: PDEUIMessages.RegisterCSOperation_addNewCSExtensionNewPlugin,
365: 4);
366: // Create the plug-in model
367: WorkspacePluginModelBase model = (WorkspacePluginModelBase) createModel(file);
368: // Update progress work units
369: monitor.worked(1);
370:
371: IPluginBase base = model.getPluginBase();
372: // Set schema version
373: double targetVersion = TargetPlatformHelper.getTargetVersion();
374: String version = null;
375: if (targetVersion < 3.2) {
376: version = ICoreConstants.TARGET30;
377: } else {
378: version = ICoreConstants.TARGET32;
379: }
380: base.setSchemaVersion(version);
381: // Create the cheat sheet extension
382: base.add(createExtensionToc(model));
383: // Update progress work units
384: monitor.worked(1);
385: // Save the model to file
386: model.save();
387: // Update progress work units
388: monitor.worked(1);
389: // Update the MANIFEST.MF file to ensure the singleton directive is set
390: // to true
391: modifyExistingManifestFile(file);
392: // Update progress work units
393: monitor.done();
394: }
395:
396: /**
397: * @param model
398: */
399: private void modifyExistingManifestFile(IFile file)
400: throws CoreException {
401: // Validate the operation
402: // Note: This is not accurate, we are validating the plugin.xml file rather
403: // than the manifest file
404: IStatus status = PDEPlugin.getWorkspace().validateEdit(
405: new IFile[] { file }, fShell);
406: if (status.getSeverity() != IStatus.OK) {
407: throw new CoreException(
408: new Status(
409: IStatus.ERROR,
410: IPDEUIConstants.PLUGIN_ID,
411: IStatus.ERROR,
412: PDEUIMessages.RegisterCSOperation_errorManifestReadOnly,
413: null));
414: }
415: // Perform the modification of the manifest file
416: ModelModification mod = new ModelModification(fPage
417: .getPluginProject()) {
418: protected void modifyModel(IBaseModel model,
419: IProgressMonitor monitor) throws CoreException {
420: doModifyManifestModel(model);
421: doModifyBuildModel(model);
422: }
423: };
424: PDEModelUtility.modifyModel(mod, null);
425: }
426:
427: /**
428: * @param model
429: */
430: private void doModifyManifestModel(IBaseModel model) {
431: // Make sure we have a base model
432: if ((model instanceof IBundlePluginModelBase) == false) {
433: return;
434: }
435: IBundlePluginModelBase modelBase = (IBundlePluginModelBase) model;
436: IBundle bundle = modelBase.getBundleModel().getBundle();
437: // Get the heading specifying the singleton declaration
438: IManifestHeader header = bundle
439: .getManifestHeader(Constants.BUNDLE_SYMBOLICNAME);
440: if (header instanceof BundleSymbolicNameHeader) {
441: BundleSymbolicNameHeader symbolic = (BundleSymbolicNameHeader) header;
442: // If the singleton declaration is false, change it to true
443: // This is required because plug-ins that specify extensions
444: // must be singletons.
445: if (symbolic.isSingleton() == false) {
446: symbolic.setSingleton(true);
447: }
448: }
449: // Add the cheat sheets plug-in to the list of required bundles
450: header = bundle.getManifestHeader(Constants.REQUIRE_BUNDLE);
451: if (header instanceof RequireBundleHeader) {
452: RequireBundleHeader require = (RequireBundleHeader) header;
453: if (require.hasElement(F_HELP_EXTENSION_ID) == false) {
454: require.addBundle(F_HELP_EXTENSION_ID);
455: }
456: }
457: }
458:
459: /**
460: * @param model
461: */
462: private void doModifyBuildModel(IBaseModel model)
463: throws CoreException {
464: // Make sure we have a base model
465: if ((model instanceof IPluginModelBase) == false) {
466: return;
467: }
468: IPluginModelBase modelBase = (IPluginModelBase) model;
469: IBuild build = ClasspathUtilCore.getBuild(modelBase);
470: // Make sure we have a plugin.properties file
471: if (build == null) {
472: return;
473: }
474: // Get the entry for bin.includes
475: IBuildEntry entry = build.getEntry(IBuildEntry.BIN_INCLUDES);
476: if (entry == null) {
477: // This should never happen since the manifest.mf file exists and
478: // it has to be in the bin.includes
479: return;
480: }
481: // Add the plugin.xml file to the bin.includes build entry if it does
482: // not exist
483: if (entry.contains(ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR) == false) {
484: entry.addToken(ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR);
485: }
486: // There does not seem to be any support in PDEModelUtility or the
487: // ModelModification framework to save build.properties modifications
488: // As a result, explicitly do that here
489: if (build instanceof BuildObject) {
490: IBuildModel buildModel = ((BuildObject) build).getModel();
491: if (buildModel instanceof WorkspaceBuildModel) {
492: ((WorkspaceBuildModel) buildModel).save();
493: }
494: }
495: }
496:
497: /**
498: * @param file
499: * @return
500: */
501: private IPluginModelBase createModel(IFile file) {
502: if (file.getProjectRelativePath().equals(
503: ICoreConstants.FRAGMENT_PATH)) {
504: return new WorkspaceFragmentModel(file, false);
505: }
506: return new WorkspacePluginModel(file, false);
507: }
508:
509: /**
510: * @param model
511: * @return
512: * @throws CoreException
513: */
514: private IPluginExtension createExtensionToc(IPluginModelBase model)
515: throws CoreException {
516: IPluginExtension extension = model.getFactory()
517: .createExtension();
518: // Point
519: extension.setPoint(F_TOC_EXTENSION_POINT_ID);
520:
521: createExtensionChildren(extension);
522:
523: return extension;
524: }
525:
526: /**
527: * @param extension
528: * @throws CoreException
529: */
530: private void createExtensionChildren(IPluginExtension extension)
531: throws CoreException {
532: // TOC element
533: IPluginElement tocElement = createElementToc(extension);
534: if (tocElement != null) {
535: extension.add(tocElement);
536: }
537: }
538:
539: /**
540: * @param extension
541: * @return
542: * @throws CoreException
543: */
544: private IPluginElement createElementToc(IPluginExtension extension)
545: throws CoreException {
546:
547: IPluginElement element = extension.getModel().getFactory()
548: .createElement(extension);
549:
550: // Element: toc
551: element.setName(ITocConstants.ELEMENT_TOC);
552:
553: // Attribute: file
554: element.setAttribute(F_TOC_ATTRIBUTE_FILE, fPage
555: .getDataTocFile());
556:
557: // Attribute: primary
558: boolean primary = fPage.getDataPrimary();
559:
560: if (primary) {
561: element.setAttribute(F_TOC_ATTRIBUTE_PRIMARY, Boolean.TRUE
562: .toString());
563: } else {
564: element.setAttribute(F_TOC_ATTRIBUTE_PRIMARY, Boolean.FALSE
565: .toString());
566: }
567:
568: return element;
569: }
570: }
|