001: /*******************************************************************************
002: * Copyright (c) 2005, 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.util;
011:
012: import java.util.ArrayList;
013: import java.util.Hashtable;
014: import java.util.Iterator;
015:
016: import org.eclipse.core.filebuffers.FileBuffers;
017: import org.eclipse.core.filebuffers.ITextFileBuffer;
018: import org.eclipse.core.filebuffers.ITextFileBufferManager;
019: import org.eclipse.core.filebuffers.LocationKind;
020: import org.eclipse.core.resources.IFile;
021: import org.eclipse.core.resources.IProject;
022: import org.eclipse.core.runtime.CoreException;
023: import org.eclipse.core.runtime.IProgressMonitor;
024: import org.eclipse.jface.text.BadLocationException;
025: import org.eclipse.jface.text.IDocument;
026: import org.eclipse.ltk.core.refactoring.TextFileChange;
027: import org.eclipse.pde.core.IBaseModel;
028: import org.eclipse.pde.core.plugin.IPluginModelBase;
029: import org.eclipse.pde.core.plugin.ISharedExtensionsModel;
030: import org.eclipse.pde.internal.core.bundle.BundleFragmentModel;
031: import org.eclipse.pde.internal.core.bundle.BundlePluginModel;
032: import org.eclipse.pde.internal.core.ibundle.IBundleModel;
033: import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
034: import org.eclipse.pde.internal.core.text.AbstractEditingModel;
035: import org.eclipse.pde.internal.core.text.IEditingModel;
036: import org.eclipse.pde.internal.core.text.IModelTextChangeListener;
037: import org.eclipse.pde.internal.core.text.build.BuildModel;
038: import org.eclipse.pde.internal.core.text.build.PropertiesTextChangeListener;
039: import org.eclipse.pde.internal.core.text.bundle.BundleModel;
040: import org.eclipse.pde.internal.core.text.bundle.BundleTextChangeListener;
041: import org.eclipse.pde.internal.core.text.plugin.FragmentModel;
042: import org.eclipse.pde.internal.core.text.plugin.PluginModel;
043: import org.eclipse.pde.internal.core.text.plugin.XMLTextChangeListener;
044: import org.eclipse.pde.internal.ui.IPDEUIConstants;
045: import org.eclipse.pde.internal.ui.PDEPlugin;
046: import org.eclipse.pde.internal.ui.editor.PDEFormEditor;
047: import org.eclipse.pde.internal.ui.editor.build.BuildEditor;
048: import org.eclipse.pde.internal.ui.editor.build.BuildInputContext;
049: import org.eclipse.pde.internal.ui.editor.build.BuildSourcePage;
050: import org.eclipse.pde.internal.ui.editor.context.InputContext;
051: import org.eclipse.pde.internal.ui.editor.plugin.ManifestEditor;
052: import org.eclipse.pde.internal.ui.editor.schema.SchemaEditor;
053: import org.eclipse.pde.internal.ui.editor.schema.SchemaInputContext;
054: import org.eclipse.pde.internal.ui.editor.site.SiteEditor;
055: import org.eclipse.swt.widgets.Display;
056: import org.eclipse.text.edits.MalformedTreeException;
057: import org.eclipse.text.edits.MultiTextEdit;
058: import org.eclipse.text.edits.TextEdit;
059: import org.eclipse.text.edits.TextEditGroup;
060: import org.eclipse.ui.IEditorInput;
061: import org.eclipse.ui.IEditorPart;
062: import org.eclipse.ui.IFileEditorInput;
063: import org.eclipse.ui.forms.editor.IFormPage;
064: import org.osgi.framework.Constants;
065:
066: /**
067: * Your one stop shop for preforming changes to your plug-in models.
068: *
069: */
070: public class PDEModelUtility {
071:
072: public static final String F_MANIFEST = "MANIFEST.MF"; //$NON-NLS-1$
073: public static final String F_MANIFEST_FP = "META-INF/" + F_MANIFEST; //$NON-NLS-1$
074: public static final String F_PLUGIN = "plugin.xml"; //$NON-NLS-1$
075: public static final String F_FRAGMENT = "fragment.xml"; //$NON-NLS-1$
076: public static final String F_PROPERTIES = ".properties"; //$NON-NLS-1$
077: public static final String F_BUILD = "build" + F_PROPERTIES; //$NON-NLS-1$
078:
079: // bundle / xml various Object[] indices
080: private static final int F_Bi = 0; // the manifest.mf-related object will always be 1st
081: private static final int F_Xi = 1; // the xml-related object will always be 2nd
082:
083: private static Hashtable fOpenPDEEditors = new Hashtable();
084:
085: /**
086: * PDE editors should call this during their creation.
087: *
088: * Currently the pde editor superclass (PDEFormEditor)
089: * connects during its createPages method and so this
090: * method does not need to be invoked anywhere else.
091: * @param editor the editor to connect to
092: */
093: public static void connect(PDEFormEditor editor) {
094: IProject project = editor.getCommonProject();
095: if (project == null)
096: return;
097: if (fOpenPDEEditors.containsKey(project)) {
098: ArrayList list = (ArrayList) fOpenPDEEditors.get(project);
099: if (!list.contains(editor))
100: list.add(editor);
101: } else {
102: ArrayList list = new ArrayList();
103: list.add(editor);
104: fOpenPDEEditors.put(project, list);
105: }
106: }
107:
108: /**
109: * PDE editors should call this when they are closing down.
110: * @param editor the pde editor to disconnect from
111: */
112: public static void disconnect(PDEFormEditor editor) {
113: IProject project = editor.getCommonProject();
114: if (project == null)
115: return;
116: if (!fOpenPDEEditors.containsKey(project))
117: return;
118: ArrayList list = (ArrayList) fOpenPDEEditors.get(project);
119: list.remove(editor);
120: if (list.size() == 0)
121: fOpenPDEEditors.remove(project);
122: }
123:
124: /**
125: * Returns an open ManifestEditor that is associated with this project.
126: * @param project
127: * @return null if no ManifestEditor is open for this project
128: */
129: public static ManifestEditor getOpenManifestEditor(IProject project) {
130: return (ManifestEditor) getOpenEditor(project,
131: IPDEUIConstants.MANIFEST_EDITOR_ID);
132: }
133:
134: /**
135: * Returns an open BuildEditor that is associated with this project.
136: * @param project
137: * @return null if no BuildEditor is open for this project
138: */
139: public static BuildEditor getOpenBuildPropertiesEditor(
140: IProject project) {
141: return (BuildEditor) getOpenEditor(project,
142: IPDEUIConstants.BUILD_EDITOR_ID);
143: }
144:
145: /**
146: * Returns an open SiteEditor that is associated with this project.
147: * @param project
148: * @return null if no SiteEditor is open for this project
149: */
150: public static SiteEditor getOpenUpdateSiteEditor(IProject project) {
151: return (SiteEditor) getOpenEditor(project,
152: IPDEUIConstants.SITE_EDITOR_ID);
153: }
154:
155: private static PDEFormEditor getOpenEditor(IProject project,
156: String editorId) {
157: ArrayList list = (ArrayList) fOpenPDEEditors.get(project);
158: if (list == null)
159: return null;
160: for (int i = 0; i < list.size(); i++) {
161: PDEFormEditor editor = (PDEFormEditor) list.get(i);
162: if (editor.getEditorSite().getId().equals(editorId))
163: return editor;
164: }
165: return null;
166: }
167:
168: /**
169: * Get the open schema editor rooted at the specified underlying file
170: * @param file
171: * @return editor if found or null
172: */
173: public static SchemaEditor getOpenSchemaEditor(IFile file) {
174: return (SchemaEditor) getOpenEditor(
175: IPDEUIConstants.SCHEMA_EDITOR_ID,
176: SchemaInputContext.CONTEXT_ID, file);
177: }
178:
179: /**
180: * @param editorID
181: * @param inputContextID
182: * @param file
183: * @return
184: */
185: private static PDEFormEditor getOpenEditor(String editorID,
186: String inputContextID, IFile file) {
187: // Get the file's project
188: IProject project = file.getProject();
189: // Check for open editors housed in the specified project
190: ArrayList list = (ArrayList) fOpenPDEEditors.get(project);
191: // No open editors found
192: if (list == null) {
193: return null;
194: }
195: // Get the open editor whose
196: // (1) Editor ID matches the specified editor ID
197: // (2) Underlying file matches the specified file
198: // Check all open editors
199: for (int i = 0; i < list.size(); i++) {
200: // Get the editor
201: PDEFormEditor editor = (PDEFormEditor) list.get(i);
202: // Check for the specified type
203: // Get the editor ID
204: String currentEditorID = editor.getEditorSite().getId();
205: if (currentEditorID.equals(editorID) == false) {
206: continue;
207: }
208: // Check for the specified file
209: // Find the editor's input context
210: InputContext context = editor.getContextManager()
211: .findContext(inputContextID);
212: // Ensure we have an input context
213: if (context == null) {
214: continue;
215: }
216: // Get the editor input
217: IEditorInput input = context.getInput();
218: // Ensure we have a file editor input
219: if ((input instanceof IFileEditorInput) == false) {
220: continue;
221: }
222: // Get the editor's underlying file
223: IFile currentFile = ((IFileEditorInput) input).getFile();
224: // If the file matches the specified file, we have found the
225: // specified editor
226: if (currentFile.equals(file)) {
227: return editor;
228: }
229: }
230: return null;
231: }
232:
233: /**
234: * Returns an IPluginModelBase from the active ManifestEditor or null
235: * if no manifest editor is open.
236: * @return the active IPluginModelBase
237: */
238: public static IPluginModelBase getActivePluginModel() {
239: IEditorPart editor = PDEPlugin.getActivePage()
240: .getActiveEditor();
241: if (editor instanceof ManifestEditor) {
242: IBaseModel model = ((ManifestEditor) editor)
243: .getAggregateModel();
244: if (model instanceof IPluginModelBase)
245: return (IPluginModelBase) model;
246: }
247: return null;
248: }
249:
250: /**
251: *
252: * @param doc
253: * @return
254: */
255: public static IEditingModel getOpenModel(IDocument doc) {
256: Iterator it = fOpenPDEEditors.values().iterator();
257: while (it.hasNext()) {
258: ArrayList list = (ArrayList) it.next();
259: for (int i = 0; i < list.size(); i++) {
260: PDEFormEditor e = (PDEFormEditor) list.get(i);
261: IPluginModelBase model = (IPluginModelBase) e
262: .getAggregateModel();
263: if (model instanceof IBundlePluginModelBase) {
264: IBundleModel bModel = ((IBundlePluginModelBase) model)
265: .getBundleModel();
266: if (bModel instanceof IEditingModel
267: && doc == ((IEditingModel) bModel)
268: .getDocument())
269: return (IEditingModel) bModel;
270: ISharedExtensionsModel eModel = ((IBundlePluginModelBase) model)
271: .getExtensionsModel();
272: if (eModel instanceof IEditingModel
273: && doc == ((IEditingModel) eModel)
274: .getDocument())
275: return (IEditingModel) eModel;
276: }
277:
278: // IBuildModel bModel = model.getBuildModel();
279: // if (bModel instanceof IEditingModel &&
280: // doc == ((IEditingModel)bModel).getDocument())
281: // return (IEditingModel)bModel;
282:
283: if (model instanceof IEditingModel
284: && doc == ((IEditingModel) model).getDocument())
285: return (IEditingModel) model;
286: }
287: }
288: return null;
289: }
290:
291: /**
292: * Modify a model based on the specifications provided by the ModelModification parameter.
293: *
294: * A model will be searched for in the open editors, if it is found changes will be applied
295: * and the editor will be saved.
296: * If no model is found one will be created and text edit operations will be generated / applied.
297: *
298: * NOTE: If a MANIFEST.MF file is specified in the ModelModification a BundlePluginModel will be
299: * searched for / created and passed to ModelModification#modifyModel(IBaseModel).
300: * (not a BundleModel - which can be retreived from the BundlePluginModel)
301: * @param modification
302: * @param monitor
303: * @throws CoreException
304: */
305: public static void modifyModel(
306: final ModelModification modification,
307: final IProgressMonitor monitor) {
308: // ModelModification was not supplied with the right files
309: // TODO should we just fail silently?
310: if (modification.getFile() == null)
311: return;
312:
313: PDEFormEditor editor = getOpenEditor(modification);
314: IBaseModel model = getModelFromEditor(editor, modification);
315:
316: if (model != null) {
317: // open editor found, should have underlying text listeners -> apply modification
318: modifyEditorModel(modification, editor, model, monitor);
319: } else {
320: generateModelEdits(modification, monitor, true);
321: }
322: }
323:
324: public static TextFileChange[] changesForModelModication(
325: final ModelModification modification,
326: final IProgressMonitor monitor) {
327: final PDEFormEditor editor = getOpenEditor(modification);
328: if (editor != null) {
329: Display.getDefault().syncExec(new Runnable() {
330: public void run() {
331: if (editor.isDirty())
332: editor.flushEdits();
333: }
334: });
335: }
336: return generateModelEdits(modification, monitor, false);
337: }
338:
339: private static TextFileChange[] generateModelEdits(
340: final ModelModification modification,
341: final IProgressMonitor monitor, boolean performEdits) {
342: ArrayList edits = new ArrayList();
343: // create own model, attach listeners and grab text edits
344: ITextFileBufferManager manager = FileBuffers
345: .getTextFileBufferManager();
346: IFile[] files;
347: if (modification.isFullBundleModification()) {
348: files = new IFile[2];
349: files[F_Bi] = modification.getManifestFile();
350: files[F_Xi] = modification.getXMLFile();
351: } else {
352: files = new IFile[] { modification.getFile() };
353: }
354: // need to monitor number of successful buffer connections for disconnection purposes
355: // @see } finally { statement
356: int sc = 0;
357: try {
358: ITextFileBuffer[] buffers = new ITextFileBuffer[files.length];
359: IDocument[] documents = new IDocument[files.length];
360: for (int i = 0; i < files.length; i++) {
361: if (files[i] == null || !files[i].exists())
362: continue;
363: manager.connect(files[i].getFullPath(),
364: LocationKind.NORMALIZE, monitor);
365: sc++;
366: buffers[i] = manager.getTextFileBuffer(files[i]
367: .getFullPath(), LocationKind.NORMALIZE);
368: if (performEdits && buffers[i].isDirty())
369: buffers[i].commit(monitor, true);
370: documents[i] = buffers[i].getDocument();
371: }
372:
373: IBaseModel editModel;
374: if (modification.isFullBundleModification())
375: editModel = prepareBundlePluginModel(files, documents,
376: !performEdits);
377: else
378: editModel = prepareAbstractEditingModel(files[0],
379: documents[0], !performEdits);
380:
381: modification.modifyModel(editModel, monitor);
382:
383: IModelTextChangeListener[] listeners = gatherListeners(editModel);
384: for (int i = 0; i < listeners.length; i++) {
385: if (listeners[i] == null)
386: continue;
387: TextEdit[] currentEdits = listeners[i]
388: .getTextOperations();
389: if (currentEdits.length > 0) {
390: MultiTextEdit multi = new MultiTextEdit();
391: multi.addChildren(currentEdits);
392: if (performEdits) {
393: multi.apply(documents[i]);
394: buffers[i].commit(monitor, true);
395: }
396: TextFileChange change = new TextFileChange(files[i]
397: .getName(), files[i]);
398: change.setEdit(multi);
399: // If the edits were performed right away (performEdits == true) then
400: // all the names are null and we don't need the granular detail anyway.
401: if (!performEdits) {
402: for (int j = 0; j < currentEdits.length; j++) {
403: String name = listeners[i]
404: .getReadableName(currentEdits[j]);
405: if (name != null)
406: change
407: .addTextEditGroup(new TextEditGroup(
408: name, currentEdits[j]));
409: }
410: }
411: // save the file after the change applied
412: change.setSaveMode(TextFileChange.FORCE_SAVE);
413: setChangeTextType(change, files[i]);
414: edits.add(change);
415: }
416: }
417: } catch (CoreException e) {
418: PDEPlugin.log(e);
419: } catch (MalformedTreeException e) {
420: PDEPlugin.log(e);
421: } catch (BadLocationException e) {
422: PDEPlugin.log(e);
423: } finally {
424: // don't want to over-disconnect in case we ran into an exception during connections
425: // dc <= sc stops this from happening
426: int dc = 0;
427: for (int i = 0; i < files.length && dc <= sc; i++) {
428: if (files[i] == null || !files[i].exists())
429: continue;
430: try {
431: manager.disconnect(files[i].getFullPath(),
432: LocationKind.NORMALIZE, monitor);
433: dc++;
434: } catch (CoreException e) {
435: PDEPlugin.log(e);
436: }
437: }
438: }
439: return (TextFileChange[]) edits
440: .toArray(new TextFileChange[edits.size()]);
441: }
442:
443: public static void setChangeTextType(TextFileChange change,
444: IFile file) {
445: // null guard in case a folder gets passed for whatever reason
446: String name = file.getName();
447: if (name == null)
448: return;
449: // mark a plugin.xml or a fragment.xml as PLUGIN2 type so they will be compared
450: // with the PluginContentMergeViewer
451: String textType = name.equals("plugin.xml") || //$NON-NLS-1$
452: name.equals("fragment.xml") ? //$NON-NLS-1$
453: "PLUGIN2"
454: : file.getFileExtension(); //$NON-NLS-1$
455: // if the file extension is null, the setTextType method will use type "txt", so no null guard needed
456: change.setTextType(textType);
457: }
458:
459: private static void modifyEditorModel(final ModelModification mod,
460: final PDEFormEditor editor, final IBaseModel model,
461: final IProgressMonitor monitor) {
462: getDisplay().syncExec(new Runnable() {
463: public void run() {
464: try {
465: mod.modifyModel(model, monitor);
466: IFile[] files = new IFile[] {
467: mod.getManifestFile(), mod.getXMLFile(),
468: mod.getPropertiesFile() };
469: for (int i = 0; i < files.length; i++) {
470: if (files[i] == null)
471: continue;
472: InputContext con = editor.getContextManager()
473: .findContext(files[i]);
474: if (con != null)
475: con.flushEditorInput();
476: }
477: if (mod.saveOpenEditor())
478: editor.doSave(monitor);
479: } catch (CoreException e) {
480: PDEPlugin.log(e);
481: }
482: }
483: });
484: }
485:
486: private static PDEFormEditor getOpenEditor(
487: ModelModification modification) {
488: IProject project = modification.getFile().getProject();
489: String name = modification.getFile().getName();
490: if (name.equals(F_PLUGIN) || name.equals(F_FRAGMENT)
491: || name.equals(F_MANIFEST)) {
492: return getOpenManifestEditor(project);
493: } else if (name.equals(F_BUILD)) {
494: PDEFormEditor openEditor = getOpenBuildPropertiesEditor(project);
495: if (openEditor == null)
496: openEditor = getOpenManifestEditor(project);
497: return openEditor;
498: }
499: return null;
500: }
501:
502: private static IBaseModel getModelFromEditor(
503: PDEFormEditor openEditor, ModelModification modification) {
504: if (openEditor == null)
505: return null;
506: String name = modification.getFile().getName();
507: IBaseModel model = null;
508: if (name.equals(F_PLUGIN) || name.equals(F_FRAGMENT)) {
509: model = openEditor.getAggregateModel();
510: if (model instanceof IBundlePluginModelBase)
511: model = ((IBundlePluginModelBase) model)
512: .getExtensionsModel();
513: } else if (name.equals(F_BUILD)) {
514: if (openEditor instanceof BuildEditor) {
515: model = openEditor.getAggregateModel();
516: } else if (openEditor instanceof ManifestEditor) {
517: IFormPage page = openEditor
518: .findPage(BuildInputContext.CONTEXT_ID);
519: if (page instanceof BuildSourcePage)
520: model = ((BuildSourcePage) page).getInputContext()
521: .getModel();
522: }
523: } else if (name.equals(F_MANIFEST)) {
524: model = openEditor.getAggregateModel();
525: if (model instanceof IBundlePluginModelBase)
526: return model;
527: }
528: if (model instanceof AbstractEditingModel)
529: return model;
530: return null;
531: }
532:
533: private static IModelTextChangeListener createListener(
534: String filename, IDocument doc, boolean generateEditNames) {
535: if (filename.equals(F_PLUGIN) || filename.equals(F_FRAGMENT))
536: return new XMLTextChangeListener(doc, generateEditNames);
537: else if (filename.equals(F_MANIFEST))
538: return new BundleTextChangeListener(doc, generateEditNames);
539: else if (filename.endsWith(F_PROPERTIES))
540: return new PropertiesTextChangeListener(doc,
541: generateEditNames);
542: return null;
543: }
544:
545: private static AbstractEditingModel prepareAbstractEditingModel(
546: IFile file, IDocument doc, boolean generateEditNames) {
547: AbstractEditingModel model;
548: String filename = file.getName();
549: if (filename.equals(F_MANIFEST))
550: model = new BundleModel(doc, true);
551: else if (filename.equals(F_FRAGMENT))
552: model = new FragmentModel(doc, true);
553: else if (filename.equals(F_PLUGIN))
554: model = new PluginModel(doc, true);
555: else if (filename.endsWith(F_PROPERTIES))
556: model = new BuildModel(doc, true);
557: else
558: return null;
559: model.setUnderlyingResource(file);
560: try {
561: model.load();
562: IModelTextChangeListener listener = createListener(
563: filename, doc, generateEditNames);
564: model.addModelChangedListener(listener);
565: } catch (CoreException e) {
566: PDEPlugin.log(e);
567: }
568: return model;
569: }
570:
571: private static IBaseModel prepareBundlePluginModel(IFile[] files,
572: IDocument[] docs, boolean generateEditNames)
573: throws CoreException {
574: AbstractEditingModel[] models = new AbstractEditingModel[docs.length];
575:
576: boolean isFragment = false;
577: models[F_Bi] = prepareAbstractEditingModel(files[F_Bi],
578: docs[F_Bi], generateEditNames);
579: if (models[F_Bi] instanceof IBundleModel)
580: isFragment = ((IBundleModel) models[F_Bi]).getBundle()
581: .getHeader(Constants.FRAGMENT_HOST) != null;
582:
583: IBundlePluginModelBase pluginModel;
584: if (isFragment)
585: pluginModel = new BundleFragmentModel();
586: else
587: pluginModel = new BundlePluginModel();
588:
589: pluginModel.setBundleModel((IBundleModel) models[F_Bi]);
590: if (files.length > F_Xi && files[F_Xi] != null) {
591: models[F_Xi] = prepareAbstractEditingModel(files[F_Xi],
592: docs[F_Xi], generateEditNames);
593: pluginModel
594: .setExtensionsModel((ISharedExtensionsModel) models[F_Xi]);
595: }
596: return pluginModel;
597: }
598:
599: private static IModelTextChangeListener[] gatherListeners(
600: IBaseModel editModel) {
601: IModelTextChangeListener[] listeners = new IModelTextChangeListener[0];
602: if (editModel instanceof AbstractEditingModel)
603: listeners = new IModelTextChangeListener[] { ((AbstractEditingModel) editModel)
604: .getLastTextChangeListener() };
605: if (editModel instanceof IBundlePluginModelBase) {
606: IBundlePluginModelBase modelBase = (IBundlePluginModelBase) editModel;
607: listeners = new IModelTextChangeListener[2];
608: listeners[F_Bi] = gatherListener(modelBase.getBundleModel());
609: listeners[F_Xi] = gatherListener(modelBase
610: .getExtensionsModel());
611: return listeners;
612: }
613: return listeners;
614: }
615:
616: private static IModelTextChangeListener gatherListener(
617: IBaseModel model) {
618: if (model instanceof AbstractEditingModel)
619: return ((AbstractEditingModel) model)
620: .getLastTextChangeListener();
621: return null;
622: }
623:
624: private static Display getDisplay() {
625: Display display = Display.getCurrent();
626: if (display == null)
627: display = Display.getDefault();
628: return display;
629: }
630: }
|