blob: 9a5b07091e26587df82fbcbdd0aa178042c54383 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.ui.util;
import java.util.*;
import org.eclipse.core.filebuffers.*;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.pde.core.IBaseModel;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.core.plugin.ISharedExtensionsModel;
import org.eclipse.pde.internal.core.bundle.BundleFragmentModel;
import org.eclipse.pde.internal.core.bundle.BundlePluginModel;
import org.eclipse.pde.internal.core.ibundle.IBundleModel;
import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
import org.eclipse.pde.internal.core.text.*;
import org.eclipse.pde.internal.core.text.build.BuildModel;
import org.eclipse.pde.internal.core.text.build.PropertiesTextChangeListener;
import org.eclipse.pde.internal.core.text.bundle.BundleModel;
import org.eclipse.pde.internal.core.text.bundle.BundleTextChangeListener;
import org.eclipse.pde.internal.core.text.plugin.*;
import org.eclipse.pde.internal.ui.IPDEUIConstants;
import org.eclipse.pde.internal.ui.PDEPlugin;
import org.eclipse.pde.internal.ui.editor.PDEFormEditor;
import org.eclipse.pde.internal.ui.editor.build.*;
import org.eclipse.pde.internal.ui.editor.context.InputContext;
import org.eclipse.pde.internal.ui.editor.plugin.ManifestEditor;
import org.eclipse.pde.internal.ui.editor.schema.SchemaEditor;
import org.eclipse.pde.internal.ui.editor.schema.SchemaInputContext;
import org.eclipse.pde.internal.ui.editor.site.SiteEditor;
import org.eclipse.swt.widgets.Display;
import org.eclipse.text.edits.*;
import org.eclipse.ui.*;
import org.eclipse.ui.forms.editor.IFormPage;
import org.eclipse.ui.part.FileEditorInput;
import org.osgi.framework.Constants;
/**
* Your one stop shop for preforming changes to your plug-in models.
*
*/
public class PDEModelUtility {
public static final String F_MANIFEST = "MANIFEST.MF"; //$NON-NLS-1$
public static final String F_MANIFEST_FP = "META-INF/" + F_MANIFEST; //$NON-NLS-1$
public static final String F_PLUGIN = "plugin.xml"; //$NON-NLS-1$
public static final String F_FRAGMENT = "fragment.xml"; //$NON-NLS-1$
public static final String F_PROPERTIES = ".properties"; //$NON-NLS-1$
public static final String F_BUILD = "build" + F_PROPERTIES; //$NON-NLS-1$
// bundle / xml various Object[] indices
private static final int F_Bi = 0; // the manifest.mf-related object will always be 1st
private static final int F_Xi = 1; // the xml-related object will always be 2nd
private static Hashtable fOpenPDEEditors = new Hashtable();
/**
* PDE editors should call this during their creation.
*
* Currently the pde editor superclass (PDEFormEditor)
* connects during its createPages method and so this
* method does not need to be invoked anywhere else.
* @param editor the editor to connect to
*/
public static void connect(PDEFormEditor editor) {
IProject project = editor.getCommonProject();
if (project == null)
return;
if (fOpenPDEEditors.containsKey(project)) {
ArrayList list = (ArrayList) fOpenPDEEditors.get(project);
if (!list.contains(editor))
list.add(editor);
} else {
ArrayList list = new ArrayList();
list.add(editor);
fOpenPDEEditors.put(project, list);
}
}
/**
* PDE editors should call this when they are closing down.
* @param editor the pde editor to disconnect from
*/
public static void disconnect(PDEFormEditor editor) {
IProject project = editor.getCommonProject();
if (project == null) {
// getCommonProject will return null when project is deleted with editor open - bug 226788
// Solution is to use editor input if it is a FileEditorInput.
IEditorInput input = editor.getEditorInput();
if (input != null && input instanceof FileEditorInput) {
FileEditorInput fei = (FileEditorInput) input;
IFile file = fei.getFile();
project = file.getProject();
}
}
if (project == null)
return;
if (!fOpenPDEEditors.containsKey(project))
return;
ArrayList list = (ArrayList) fOpenPDEEditors.get(project);
list.remove(editor);
if (list.size() == 0)
fOpenPDEEditors.remove(project);
}
/**
* Returns an open ManifestEditor that is associated with this project.
* @param project
* @return null if no ManifestEditor is open for this project
*/
public static ManifestEditor getOpenManifestEditor(IProject project) {
return (ManifestEditor) getOpenEditor(project, IPDEUIConstants.MANIFEST_EDITOR_ID);
}
/**
* Returns an open BuildEditor that is associated with this project.
* @param project
* @return null if no BuildEditor is open for this project
*/
public static BuildEditor getOpenBuildPropertiesEditor(IProject project) {
return (BuildEditor) getOpenEditor(project, IPDEUIConstants.BUILD_EDITOR_ID);
}
/**
* Returns an open SiteEditor that is associated with this project.
* @param project
* @return null if no SiteEditor is open for this project
*/
public static SiteEditor getOpenUpdateSiteEditor(IProject project) {
return (SiteEditor) getOpenEditor(project, IPDEUIConstants.SITE_EDITOR_ID);
}
private static PDEFormEditor getOpenEditor(IProject project, String editorId) {
ArrayList list = (ArrayList) fOpenPDEEditors.get(project);
if (list == null)
return null;
for (int i = 0; i < list.size(); i++) {
PDEFormEditor editor = (PDEFormEditor) list.get(i);
if (editor.getEditorSite().getId().equals(editorId))
return editor;
}
return null;
}
/**
* Get the open schema editor rooted at the specified underlying file
* @param file
* @return editor if found or null
*/
public static SchemaEditor getOpenSchemaEditor(IFile file) {
return (SchemaEditor) getOpenEditor(IPDEUIConstants.SCHEMA_EDITOR_ID, SchemaInputContext.CONTEXT_ID, file);
}
/**
* @param editorID
* @param inputContextID
* @param file
* @return
*/
private static PDEFormEditor getOpenEditor(String editorID, String inputContextID, IFile file) {
// Get the file's project
IProject project = file.getProject();
// Check for open editors housed in the specified project
ArrayList list = (ArrayList) fOpenPDEEditors.get(project);
// No open editors found
if (list == null) {
return null;
}
// Get the open editor whose
// (1) Editor ID matches the specified editor ID
// (2) Underlying file matches the specified file
// Check all open editors
for (int i = 0; i < list.size(); i++) {
// Get the editor
PDEFormEditor editor = (PDEFormEditor) list.get(i);
// Check for the specified type
// Get the editor ID
String currentEditorID = editor.getEditorSite().getId();
if (currentEditorID.equals(editorID) == false) {
continue;
}
// Check for the specified file
// Find the editor's input context
InputContext context = editor.getContextManager().findContext(inputContextID);
// Ensure we have an input context
if (context == null) {
continue;
}
// Get the editor input
IEditorInput input = context.getInput();
// Ensure we have a file editor input
if ((input instanceof IFileEditorInput) == false) {
continue;
}
// Get the editor's underlying file
IFile currentFile = ((IFileEditorInput) input).getFile();
// If the file matches the specified file, we have found the
// specified editor
if (currentFile.equals(file)) {
return editor;
}
}
return null;
}
/**
* Returns an IPluginModelBase from the active ManifestEditor or null
* if no manifest editor is open.
* @return the active IPluginModelBase
*/
public static IPluginModelBase getActivePluginModel() {
IEditorPart editor = PDEPlugin.getActivePage().getActiveEditor();
if (editor instanceof ManifestEditor) {
IBaseModel model = ((ManifestEditor) editor).getAggregateModel();
if (model instanceof IPluginModelBase)
return (IPluginModelBase) model;
}
return null;
}
/**
*
* @param doc
* @return
*/
public static IEditingModel getOpenModel(IDocument doc) {
Iterator it = fOpenPDEEditors.values().iterator();
while (it.hasNext()) {
ArrayList list = (ArrayList) it.next();
for (int i = 0; i < list.size(); i++) {
PDEFormEditor e = (PDEFormEditor) list.get(i);
IPluginModelBase model = (IPluginModelBase) e.getAggregateModel();
if (model instanceof IBundlePluginModelBase) {
IBundleModel bModel = ((IBundlePluginModelBase) model).getBundleModel();
if (bModel instanceof IEditingModel && doc == ((IEditingModel) bModel).getDocument())
return (IEditingModel) bModel;
ISharedExtensionsModel eModel = ((IBundlePluginModelBase) model).getExtensionsModel();
if (eModel instanceof IEditingModel && doc == ((IEditingModel) eModel).getDocument())
return (IEditingModel) eModel;
}
// IBuildModel bModel = model.getBuildModel();
// if (bModel instanceof IEditingModel &&
// doc == ((IEditingModel)bModel).getDocument())
// return (IEditingModel)bModel;
if (model instanceof IEditingModel && doc == ((IEditingModel) model).getDocument())
return (IEditingModel) model;
}
}
return null;
}
/**
* Modify a model based on the specifications provided by the ModelModification parameter.
*
* A model will be searched for in the open editors, if it is found changes will be applied
* and the editor will be saved.
* If no model is found one will be created and text edit operations will be generated / applied.
*
* NOTE: If a MANIFEST.MF file is specified in the ModelModification a BundlePluginModel will be
* searched for / created and passed to ModelModification#modifyModel(IBaseModel).
* (not a BundleModel - which can be retreived from the BundlePluginModel)
* @param modification
* @param monitor
* @throws CoreException
*/
public static void modifyModel(final ModelModification modification, final IProgressMonitor monitor) {
// ModelModification was not supplied with the right files
// TODO should we just fail silently?
if (modification.getFile() == null)
return;
PDEFormEditor editor = getOpenEditor(modification);
IBaseModel model = getModelFromEditor(editor, modification);
if (model != null) {
// open editor found, should have underlying text listeners -> apply modification
modifyEditorModel(modification, editor, model, monitor);
} else {
generateModelEdits(modification, monitor, true);
}
}
public static TextFileChange[] changesForModelModication(final ModelModification modification, final IProgressMonitor monitor) {
final PDEFormEditor editor = getOpenEditor(modification);
if (editor != null) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
if (editor.isDirty())
editor.flushEdits();
}
});
}
return generateModelEdits(modification, monitor, false);
}
private static TextFileChange[] generateModelEdits(final ModelModification modification, final IProgressMonitor monitor, boolean performEdits) {
ArrayList edits = new ArrayList();
// create own model, attach listeners and grab text edits
ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager();
IFile[] files;
if (modification.isFullBundleModification()) {
files = new IFile[2];
files[F_Bi] = modification.getManifestFile();
files[F_Xi] = modification.getXMLFile();
} else {
files = new IFile[] {modification.getFile()};
}
// need to monitor number of successful buffer connections for disconnection purposes
// @see } finally { statement
int sc = 0;
try {
ITextFileBuffer[] buffers = new ITextFileBuffer[files.length];
IDocument[] documents = new IDocument[files.length];
for (int i = 0; i < files.length; i++) {
if (files[i] == null || !files[i].exists())
continue;
manager.connect(files[i].getFullPath(), LocationKind.NORMALIZE, monitor);
sc++;
buffers[i] = manager.getTextFileBuffer(files[i].getFullPath(), LocationKind.NORMALIZE);
if (performEdits && buffers[i].isDirty())
buffers[i].commit(monitor, true);
documents[i] = buffers[i].getDocument();
}
IBaseModel editModel;
if (modification.isFullBundleModification())
editModel = prepareBundlePluginModel(files, documents, !performEdits);
else
editModel = prepareAbstractEditingModel(files[0], documents[0], !performEdits);
modification.modifyModel(editModel, monitor);
IModelTextChangeListener[] listeners = gatherListeners(editModel);
for (int i = 0; i < listeners.length; i++) {
if (listeners[i] == null)
continue;
TextEdit[] currentEdits = listeners[i].getTextOperations();
if (currentEdits.length > 0) {
MultiTextEdit multi = new MultiTextEdit();
multi.addChildren(currentEdits);
if (performEdits) {
multi.apply(documents[i]);
buffers[i].commit(monitor, true);
}
TextFileChange change = new TextFileChange(files[i].getName(), files[i]);
change.setEdit(multi);
// If the edits were performed right away (performEdits == true) then
// all the names are null and we don't need the granular detail anyway.
if (!performEdits) {
for (int j = 0; j < currentEdits.length; j++) {
String name = listeners[i].getReadableName(currentEdits[j]);
if (name != null)
change.addTextEditGroup(new TextEditGroup(name, currentEdits[j]));
}
}
// save the file after the change applied
change.setSaveMode(TextFileChange.FORCE_SAVE);
setChangeTextType(change, files[i]);
edits.add(change);
}
}
} catch (CoreException e) {
PDEPlugin.log(e);
} catch (MalformedTreeException e) {
PDEPlugin.log(e);
} catch (BadLocationException e) {
PDEPlugin.log(e);
} finally {
// don't want to over-disconnect in case we ran into an exception during connections
// dc <= sc stops this from happening
int dc = 0;
for (int i = 0; i < files.length && dc <= sc; i++) {
if (files[i] == null || !files[i].exists())
continue;
try {
manager.disconnect(files[i].getFullPath(), LocationKind.NORMALIZE, monitor);
dc++;
} catch (CoreException e) {
PDEPlugin.log(e);
}
}
}
return (TextFileChange[]) edits.toArray(new TextFileChange[edits.size()]);
}
public static void setChangeTextType(TextFileChange change, IFile file) {
// null guard in case a folder gets passed for whatever reason
String name = file.getName();
if (name == null)
return;
// mark a plugin.xml or a fragment.xml as PLUGIN2 type so they will be compared
// with the PluginContentMergeViewer
String textType = name.equals("plugin.xml") || //$NON-NLS-1$
name.equals("fragment.xml") ? //$NON-NLS-1$
"PLUGIN2" //$NON-NLS-1$
: file.getFileExtension();
// if the file extension is null, the setTextType method will use type "txt", so no null guard needed
change.setTextType(textType);
}
private static void modifyEditorModel(final ModelModification mod, final PDEFormEditor editor, final IBaseModel model, final IProgressMonitor monitor) {
getDisplay().syncExec(new Runnable() {
public void run() {
try {
mod.modifyModel(model, monitor);
IFile[] files = new IFile[] {mod.getManifestFile(), mod.getXMLFile(), mod.getPropertiesFile()};
for (int i = 0; i < files.length; i++) {
if (files[i] == null)
continue;
InputContext con = editor.getContextManager().findContext(files[i]);
if (con != null)
con.flushEditorInput();
}
if (mod.saveOpenEditor())
editor.doSave(monitor);
} catch (CoreException e) {
PDEPlugin.log(e);
}
}
});
}
private static PDEFormEditor getOpenEditor(ModelModification modification) {
IProject project = modification.getFile().getProject();
String name = modification.getFile().getName();
if (name.equals(F_PLUGIN) || name.equals(F_FRAGMENT) || name.equals(F_MANIFEST)) {
return getOpenManifestEditor(project);
} else if (name.equals(F_BUILD)) {
PDEFormEditor openEditor = getOpenBuildPropertiesEditor(project);
if (openEditor == null)
openEditor = getOpenManifestEditor(project);
return openEditor;
}
return null;
}
private static IBaseModel getModelFromEditor(PDEFormEditor openEditor, ModelModification modification) {
if (openEditor == null)
return null;
String name = modification.getFile().getName();
IBaseModel model = null;
if (name.equals(F_PLUGIN) || name.equals(F_FRAGMENT)) {
model = openEditor.getAggregateModel();
if (model instanceof IBundlePluginModelBase)
model = ((IBundlePluginModelBase) model).getExtensionsModel();
} else if (name.equals(F_BUILD)) {
if (openEditor instanceof BuildEditor) {
model = openEditor.getAggregateModel();
} else if (openEditor instanceof ManifestEditor) {
IFormPage page = openEditor.findPage(BuildInputContext.CONTEXT_ID);
if (page instanceof BuildSourcePage)
model = ((BuildSourcePage) page).getInputContext().getModel();
}
} else if (name.equals(F_MANIFEST)) {
model = openEditor.getAggregateModel();
if (model instanceof IBundlePluginModelBase)
return model;
}
if (model instanceof AbstractEditingModel)
return model;
return null;
}
private static IModelTextChangeListener createListener(String filename, IDocument doc, boolean generateEditNames) {
if (filename.equals(F_PLUGIN) || filename.equals(F_FRAGMENT))
return new XMLTextChangeListener(doc, generateEditNames);
else if (filename.equals(F_MANIFEST))
return new BundleTextChangeListener(doc, generateEditNames);
else if (filename.endsWith(F_PROPERTIES))
return new PropertiesTextChangeListener(doc, generateEditNames);
return null;
}
private static AbstractEditingModel prepareAbstractEditingModel(IFile file, IDocument doc, boolean generateEditNames) {
AbstractEditingModel model;
String filename = file.getName();
if (filename.equals(F_MANIFEST))
model = new BundleModel(doc, true);
else if (filename.equals(F_FRAGMENT))
model = new FragmentModel(doc, true);
else if (filename.equals(F_PLUGIN))
model = new PluginModel(doc, true);
else if (filename.endsWith(F_PROPERTIES))
model = new BuildModel(doc, true);
else
return null;
model.setUnderlyingResource(file);
try {
model.load();
IModelTextChangeListener listener = createListener(filename, doc, generateEditNames);
model.addModelChangedListener(listener);
} catch (CoreException e) {
PDEPlugin.log(e);
}
return model;
}
private static IBaseModel prepareBundlePluginModel(IFile[] files, IDocument[] docs, boolean generateEditNames) throws CoreException {
AbstractEditingModel[] models = new AbstractEditingModel[docs.length];
boolean isFragment = false;
models[F_Bi] = prepareAbstractEditingModel(files[F_Bi], docs[F_Bi], generateEditNames);
if (models[F_Bi] instanceof IBundleModel)
isFragment = ((IBundleModel) models[F_Bi]).getBundle().getHeader(Constants.FRAGMENT_HOST) != null;
IBundlePluginModelBase pluginModel;
if (isFragment)
pluginModel = new BundleFragmentModel();
else
pluginModel = new BundlePluginModel();
pluginModel.setBundleModel((IBundleModel) models[F_Bi]);
if (files.length > F_Xi && files[F_Xi] != null) {
models[F_Xi] = prepareAbstractEditingModel(files[F_Xi], docs[F_Xi], generateEditNames);
pluginModel.setExtensionsModel((ISharedExtensionsModel) models[F_Xi]);
}
return pluginModel;
}
private static IModelTextChangeListener[] gatherListeners(IBaseModel editModel) {
IModelTextChangeListener[] listeners = new IModelTextChangeListener[0];
if (editModel instanceof AbstractEditingModel)
listeners = new IModelTextChangeListener[] {((AbstractEditingModel) editModel).getLastTextChangeListener()};
if (editModel instanceof IBundlePluginModelBase) {
IBundlePluginModelBase modelBase = (IBundlePluginModelBase) editModel;
listeners = new IModelTextChangeListener[2];
listeners[F_Bi] = gatherListener(modelBase.getBundleModel());
listeners[F_Xi] = gatherListener(modelBase.getExtensionsModel());
return listeners;
}
return listeners;
}
private static IModelTextChangeListener gatherListener(IBaseModel model) {
if (model instanceof AbstractEditingModel)
return ((AbstractEditingModel) model).getLastTextChangeListener();
return null;
}
private static Display getDisplay() {
Display display = Display.getCurrent();
if (display == null)
display = Display.getDefault();
return display;
}
}