blob: 6a1b9591dfaeb8c6a330d09218bcbb372948e7f6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2021 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Lucas Bullen (Red Hat Inc.) - Bug 520216 - Add generic editor as a tab
* - Bug 531226 - Update to reflect addition of source tab
* - Bug 531602 - formatting munged by editor
* Alexander Fedorov <alexander.fedorov@arsysop.ru> - Bug 541067
*******************************************************************************/
package org.eclipse.pde.internal.ui.editor.targetdefinition;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ControlContribution;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.source.ISourceViewerExtension5;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.target.*;
import org.eclipse.pde.internal.core.*;
import org.eclipse.pde.internal.core.target.*;
import org.eclipse.pde.internal.ui.*;
import org.eclipse.pde.internal.ui.shared.target.*;
import org.eclipse.pde.internal.ui.wizards.exports.TargetDefinitionExportWizard;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.*;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.forms.*;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.events.IHyperlinkListener;
import org.eclipse.ui.forms.widgets.*;
import org.eclipse.ui.internal.genericeditor.ExtensionBasedTextEditor;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.progress.UIJob;
import org.osgi.framework.BundleContext;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.xml.sax.SAXException;
/**
* Editor for target definition (*.target) files. Interacts with the ITargetDefinition model
* to modify target attributes. Uses the target platform service to persist the modified model
* to the backing file.
*
* @see ITargetDefinition
* @see ITargetPlatformService
*/
public class TargetEditor extends FormEditor {
private List<IManagedForm> fManagedFormPages = new ArrayList<>(2);
private ExtensionBasedTextEditor fTextualEditor;
private int fSourceTabIndex;
private IDocument fTargetDocument;
private IDocumentListener fTargetDocumentListener;
private InputHandler fInputHandler = new InputHandler();
private TargetChangedListener fTargetChangedListener;
private boolean fDirty;
private ImageHyperlink fLoadHyperlink;
private EventHandler fEventHandler = this::handleBrokerEvent;
@Override
protected FormToolkit createToolkit(Display display) {
return new FormToolkit(PDEPlugin.getDefault().getFormColors(display));
}
@Override
protected void addPages() {
try {
setActiveEditor(this);
addPage(new DefinitionPage(this));
addPage(new ContentPage(this));
addPage(new EnvironmentPage(this));
addTextualEditorPage();
} catch (CoreException e) {
PDEPlugin.log(e);
}
BundleContext bundleContext = PDECore.getDefault().getBundleContext();
IEclipseContext context = EclipseContextFactory.getServiceContext(bundleContext);
IEventBroker eventBroker = context.get(IEventBroker.class);
eventBroker.subscribe(TargetEvents.TOPIC_WORKSPACE_TARGET_CHANGED, fEventHandler);
}
@Override
protected void pageChange(int newPageIndex) {
try {
if (newPageIndex != fSourceTabIndex && getCurrentPage() == fSourceTabIndex) {
InputStream stream = new ByteArrayInputStream(fTargetDocument.get().getBytes(StandardCharsets.UTF_8));
TargetDefinitionPersistenceHelper.initFromXML(getTarget(), stream);
if (!getTarget().isResolved()) {
getTargetChangedListener().contentsChanged(getTarget(), this, true, false);
}
}
super.pageChange(newPageIndex);
} catch (CoreException e) {
setActivePage(fSourceTabIndex);
showError(PDEUIMessages.TargetEditor_5, e);
} catch (ParserConfigurationException | SAXException | IOException e) {
setActivePage(fSourceTabIndex);
CoreException ce = new CoreException(new Status(IStatus.ERROR, PDEPlugin.getPluginId(), e.getMessage(), e));
showError(PDEUIMessages.TargetEditor_5, ce);
}
}
@Override
public void doSave(IProgressMonitor monitor) {
fInputHandler.setSaving(true);
if (!isActiveTabTextualEditor()) {
markStale();
updateTextualEditor();
}
fTextualEditor.doSave(monitor);
fDirty = false;
editorDirtyStateChanged();
fInputHandler.setSaving(false);
}
@Override
public void doSaveAs() {
commitPages(true);
ITargetDefinition target = getTarget();
SaveAsDialog dialog = new SaveAsDialog(getSite().getShell());
dialog.create();
dialog.setMessage(PDEUIMessages.TargetEditor_0, IMessageProvider.NONE);
if (target.getHandle() instanceof WorkspaceFileTargetHandle) {
WorkspaceFileTargetHandle currentTargetHandle = (WorkspaceFileTargetHandle) target.getHandle();
dialog.setOriginalFile(currentTargetHandle.getTargetFile());
}
dialog.open();
IPath path = dialog.getResult();
if (path == null) {
return;
}
if (!"target".equalsIgnoreCase(path.getFileExtension())) { //$NON-NLS-1$
path = path.addFileExtension("target"); //$NON-NLS-1$
}
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IFile file = workspace.getRoot().getFile(path);
if (workspace.validateEdit(new IFile[] {file}, getSite().getShell()).isOK()) {
try {
WorkspaceFileTargetHandle newFileTarget = new WorkspaceFileTargetHandle(file);
newFileTarget.save(target);
setInput(new FileEditorInput(file));
} catch (CoreException e) {
PDEPlugin.log(e);
showError(PDEUIMessages.TargetEditor_3, e);
}
}
}
@Override
public boolean isSaveAsAllowed() {
return true;
}
protected void setDirty(boolean dirty) {
fDirty = fDirty || dirty;
editorDirtyStateChanged();
if (fDirty && isActiveTabTextualEditor()) {
updateTextualEditor();
}
}
@Override
public boolean isDirty() {
return fDirty || super.isDirty();
}
public void markStale() {
for (IManagedForm form : fManagedFormPages) {
IFormPart[] parts = form.getParts();
for (IFormPart part : parts) {
if (part instanceof AbstractFormPart) {
((AbstractFormPart) part).markStale();
}
}
}
editorDirtyStateChanged();
}
/*
* @see org.eclipse.ui.forms.editor.FormEditor#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
* @since 3.7
*/
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
if (!(input instanceof IFileEditorInput) && !(input instanceof IURIEditorInput))
throw new PartInitException(NLS.bind(PDEUIMessages.TargetEditor_6, input.getClass().getName()));
super.init(site, input);
}
@Override
protected void setInput(IEditorInput input) {
super.setInput(input);
setPartName(getEditorInput().getName());
fInputHandler.setInput(input);
}
@Override
public void dispose() {
BundleContext bundleContext = PDECore.getDefault().getBundleContext();
IEclipseContext context = EclipseContextFactory.getServiceContext(bundleContext);
IEventBroker eventBroker = context.get(IEventBroker.class);
eventBroker.unsubscribe(fEventHandler);
// Cancel any resolution jobs that are runnning
Job.getJobManager().cancel(getTargetChangedListener().getJobFamily());
getTargetChangedListener().setContentTree(null);
getTargetChangedListener().setLocationTree(null);
fInputHandler.dispose();
super.dispose();
}
/**
* Returns the target model backing this editor
* @return target model
*/
public ITargetDefinition getTarget() {
return fInputHandler.getTarget();
}
/**
* @return a shared listener that will refresh UI components when the target is modified
*/
public TargetChangedListener getTargetChangedListener() {
if (fTargetChangedListener == null) {
fTargetChangedListener = new TargetChangedListener();
}
return fTargetChangedListener;
}
/**
* Handles the revert action
*/
public void doRevert() {
fInputHandler.reset();
for (IManagedForm form : fManagedFormPages) {
IFormPart[] parts = form.getParts();
for (IFormPart part : parts) {
if (part instanceof AbstractFormPart) {
((AbstractFormPart) part).markStale();
}
}
}
setActivePage(getActivePage());
editorDirtyStateChanged();
}
public void contributeToToolbar(final ScrolledForm form, String contextID) {
ControlContribution setAsTarget = new ControlContribution("Set") { //$NON-NLS-1$
@Override
protected Control createControl(Composite parent) {
PDEPreferencesManager preferences = PDECore.getDefault().getPreferencesManager();
String mementoPref = null;
String memento = null;
String hyperLinkText = PDEUIMessages.AbstractTargetPage_setTarget;
mementoPref = preferences.getString(ICoreConstants.WORKSPACE_TARGET_HANDLE);
try {
memento = getTarget().getHandle().getMemento();
} catch (CoreException e) {
}
if (mementoPref != null && memento != null) {
if (memento.equals(mementoPref)) {
hyperLinkText = PDEUIMessages.AbstractTargetPage_reloadTarget;
}
}
fLoadHyperlink = new ImageHyperlink(parent, SWT.NONE | SWT.NO_FOCUS);
fLoadHyperlink.setText(hyperLinkText);
fLoadHyperlink.setUnderlined(true);
fLoadHyperlink.setForeground(getToolkit().getHyperlinkGroup().getForeground());
fLoadHyperlink.addHyperlinkListener(new IHyperlinkListener() {
@Override
public void linkActivated(HyperlinkEvent e) {
IEditorPart editorPart = TargetEditor.this;
IWorkbenchPage page = editorPart.getSite().getPage();
if (TargetEditor.this.isDirty()) {
page.saveEditor(editorPart, true);
}
ITargetDefinition target = getTarget();
LoadTargetDefinitionJob.load(target);
}
@Override
public void linkEntered(HyperlinkEvent e) {
HyperlinkGroup hyperlinkGroup = getHyperlinkGroup();
if (hyperlinkGroup != null) {
fLoadHyperlink.setForeground(hyperlinkGroup.getActiveForeground());
}
}
@Override
public void linkExited(HyperlinkEvent e) {
HyperlinkGroup hyperlinkGroup = getHyperlinkGroup();
if (hyperlinkGroup != null) {
fLoadHyperlink.setForeground(hyperlinkGroup.getForeground());
}
}
private HyperlinkGroup getHyperlinkGroup() {
FormToolkit toolkit = getToolkit();
HyperlinkGroup hyperlinkGroup = null;
if (toolkit != null) {
hyperlinkGroup = toolkit.getHyperlinkGroup();
}
return hyperlinkGroup;
}
});
return fLoadHyperlink;
}
};
final String helpContextID = contextID;
Action help = new Action("help") { //$NON-NLS-1$
@Override
public void run() {
BusyIndicator.showWhile(form.getForm().getDisplay(), () -> PlatformUI.getWorkbench().getHelpSystem().displayHelp(helpContextID));
}
};
help.setToolTipText(PDEUIMessages.PDEFormPage_help);
help.setImageDescriptor(PDEPluginImages.DESC_HELP);
Action export = new Action("export") { //$NON-NLS-1$
@Override
public void run() {
TargetDefinitionExportWizard wizard = new TargetDefinitionExportWizard(getTarget());
wizard.setWindowTitle(PDEUIMessages.ExportActiveTargetDefinition);
WizardDialog dialog = new WizardDialog(getSite().getShell(), wizard);
dialog.open();
}
};
export.setToolTipText("Export"); //$NON-NLS-1$
export.setImageDescriptor(PDEPluginImages.DESC_EXPORT_TARGET_TOOL);
form.getToolBarManager().add(setAsTarget);
form.getToolBarManager().add(export);
form.getToolBarManager().add(help);
form.updateToolBar();
}
/**
* Adds the given form to the list of forms to be refreshed when reverting
* @param managedForm
*/
public void addForm(IManagedForm managedForm) {
fManagedFormPages.add(managedForm);
PlatformUI.getWorkbench().getHelpSystem().setHelp(managedForm.getForm().getBody(), IHelpContextIds.TARGET_EDITOR);
}
/**
* Opens an error dialog using the editor's shell.
* @param message error message to display
* @param exception exception to display in details section
*/
public void showError(final String message, final CoreException exception) {
Display display = getSite().getShell().getDisplay();
display.asyncExec(() -> ErrorDialog.openError(getSite().getShell(), PDEUIMessages.TargetEditor_4, message, exception.getStatus()));
}
/**
* The InputHandler serves as a bridge between the input file and the TargetDefinition model.
*/
private class InputHandler implements IResourceChangeListener {
private IEditorInput fInput;
private ITargetDefinition fTarget;
private IFile fTargetFileInWorkspace;
private boolean fSaving = false;
public void dispose() {
PDEPlugin.getWorkspace().removeResourceChangeListener(this);
}
public void reset() {
setInput(fInput);
}
public void setSaving(boolean saving) {
this.fSaving = saving;
}
public void setInput(IEditorInput input) {
fInput = input;
fTargetFileInWorkspace = null;
fTarget = null;
File targetFile = null;
if (input instanceof IFileEditorInput) {
fTargetFileInWorkspace = ((IFileEditorInput) input).getFile();
targetFile = fTargetFileInWorkspace.getLocation().toFile();
} else if (input instanceof IURIEditorInput) {
String part = ((IURIEditorInput) input).getURI().getSchemeSpecificPart();
Path path = new Path(part);
fTargetFileInWorkspace = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
targetFile = path.toFile();
}
//If the file is not available or invalid, close the target editor
if (fTargetFileInWorkspace == null || targetFile == null || !targetFile.exists()) {
TargetEditor.this.close(false);
}
PDEPlugin.getWorkspace().addResourceChangeListener(this);
}
/**
* @return the target definition that is the input to the editor
*/
public ITargetDefinition getTarget() {
if (fTarget == null) {
try {
loadTargetDefinition();
TargetEditor.this.fDirty = false;
TargetEditor.this.editorDirtyStateChanged();
} catch (CoreException e) {
PDEPlugin.log(e);
setActivePage(fSourceTabIndex);
}
}
return fTarget;
}
private ITargetDefinition loadTargetDefinition() throws CoreException {
ITargetPlatformService service = getTargetPlatformService();
try {
if (fInput instanceof IFileEditorInput) {
ITargetHandle fileHandle = service.getTarget(((IFileEditorInput) fInput).getFile());
fTarget = fileHandle.getTargetDefinition();
} else if (fInput instanceof IURIEditorInput) {
ITargetHandle externalTarget = service.getTarget(((IURIEditorInput) fInput).getURI());
fTarget = externalTarget.getTargetDefinition();
}
} catch (CoreException e) {
fTarget = service.newTarget();
throw e;
}
TargetEditor.this.getTargetChangedListener().contentsChanged(fTarget, this, true, false);
return fTarget;
}
private ITargetPlatformService getTargetPlatformService() throws CoreException {
ITargetPlatformService service = PDECore.getDefault().acquireService(ITargetPlatformService.class);
if (service == null) {
throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, "ITargetPlatformService not available")); //$NON-NLS-1$
}
return service;
}
@Override
public void resourceChanged(IResourceChangeEvent event) {
if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
IResourceDelta delta = event.getDelta().findMember(fTargetFileInWorkspace.getFullPath());
if (delta != null) {
if (delta.getKind() == IResourceDelta.REMOVED) {
TargetEditor.this.close(false);
} else if (delta.getKind() == IResourceDelta.CHANGED || delta.getKind() == IResourceDelta.REPLACED) {
if (!fSaving) {
Display display = getSite().getShell().getDisplay();
display.asyncExec(() -> {
if (getActivePage() != -1)
TargetEditor.this.doRevert();
});
}
}
}
}
}
}
/**
* initializes fTargetDocument and fTargetDocumentListener
*
* @throws PartInitException
*/
private void addTextualEditorPage() throws PartInitException {
fTextualEditor = new ExtensionBasedTextEditor();
fSourceTabIndex = addPage(fTextualEditor, getEditorInput());
Control editorControl = fTextualEditor.getAdapter(Control.class);
PlatformUI.getWorkbench().getHelpSystem().setHelp(editorControl, IHelpContextIds.TARGET_EDITOR_SOURCE_PAGE);
setPageText(fSourceTabIndex, PDEUIMessages.GenericEditorTab_title);
fTargetDocument = fTextualEditor.getDocumentProvider().getDocument(getEditorInput());
fTargetDocumentListener = new IDocumentListener() {
@Override
public void documentChanged(DocumentEvent event) {
markStale();
}
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
// documentChanged used instead
}
};
fTargetDocument.addDocumentListener(fTargetDocumentListener);
}
private void updateTextualEditor() {
fTargetDocument.removeDocumentListener(fTargetDocumentListener);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
TargetDefinitionPersistenceHelper.persistXML(getTarget(), byteArrayOutputStream);
fTargetDocument.set(new String(byteArrayOutputStream.toByteArray()));
} catch (CoreException | ParserConfigurationException | TransformerException | IOException | SAXException e) {
PDEPlugin.log(e);
}
fTargetDocument.addDocumentListener(fTargetDocumentListener);
}
private boolean isActiveTabTextualEditor() {
return getActivePage() == getPageCount() - 1;
}
/**
* When changes are noticed in the target, this listener will resolve the
* target and update the necessary pages in the editor.
*/
class TargetChangedListener implements ITargetChangedListener {
private TargetLocationsGroup fLocationTree;
private TargetContentsGroup fContentTree;
private Object fJobFamily = new Object();
public void setLocationTree(TargetLocationsGroup locationTree) {
fLocationTree = locationTree;
}
public void setContentTree(TargetContentsGroup contentTree) {
fContentTree = contentTree;
}
/**
* @return non-null object identifier for any jobs created by this listener
*/
public Object getJobFamily() {
return fJobFamily;
}
@Override
public void contentsChanged(ITargetDefinition definition, Object source, boolean resolve, boolean forceResolve) {
if (!forceResolve && (!resolve || definition.isResolved())) {
if (fContentTree != null && source != fContentTree) {
ITargetDefinition target = getTarget();
// Check to see if we are resolved, resolving, or cancelled
if (target != null && target.isResolved()) {
fContentTree.setInput(getTarget());
} else if (Job.getJobManager().find(getJobFamily()).length > 0) {
fContentTree.setInput(null);
} else {
fContentTree.setCancelled();
}
}
if (fLocationTree != null && source != fLocationTree) {
fLocationTree.setInput(getTarget());
}
} else {
if (fContentTree != null) {
fContentTree.setInput(null);
}
if (fLocationTree != null) {
fLocationTree.setInput(getTarget());
}
Job.getJobManager().cancel(getJobFamily());
String name = getTarget().getName();
if (name == null)
name = ""; //$NON-NLS-1$
Job resolveJob = new Job(NLS.bind(PDEUIMessages.TargetEditor_1, name)) {
@Override
protected IStatus run(IProgressMonitor monitor) {
// delete profile
try {
P2TargetUtils.forceCheckTarget(getTarget());
P2TargetUtils.deleteProfile(getTarget().getHandle());
} catch (CoreException e) {
PDEPlugin.log(e);
}
getTarget().resolve(monitor);
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
// Don't return any problems because we don't want an error dialog
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return family.equals(getJobFamily());
}
};
resolveJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(org.eclipse.core.runtime.jobs.IJobChangeEvent event) {
final IStatus status = event.getResult();
UIJob job = new UIJob(PDEUIMessages.TargetEditor_2) {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (fContentTree != null) {
if (status.getSeverity() == IStatus.CANCEL) {
fContentTree.setCancelled();
} else {
fContentTree.setInput(getTarget());
}
}
if (fLocationTree != null) {
fLocationTree.setInput(getTarget());
}
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
});
resolveJob.schedule();
}
}
}
private void updateHyperlinkText(String s) {
if (fLoadHyperlink != null && !fLoadHyperlink.isDisposed()) {
fLoadHyperlink.setText(s);
}
ITextViewer viewer = fTextualEditor.getAdapter(ITextViewer.class);
if (viewer instanceof ISourceViewerExtension5) {
ISourceViewerExtension5 extension5 = (ISourceViewerExtension5) viewer;
extension5.updateCodeMinings();
}
}
private void handleBrokerEvent(Event event) {
String topic = event.getTopic();
if (TargetEvents.TOPIC_WORKSPACE_TARGET_CHANGED.equals(topic)) {
ITargetDefinition workspaceTarget = extractTargetDefinition(event);
handleWorkspaceTargetChanged(workspaceTarget);
}
}
private void handleWorkspaceTargetChanged(ITargetDefinition workspaceTarget) {
ITargetDefinition editorTarget = getTarget();
if (editorTarget == null) {
return;
}
ITargetHandle editorHandle = editorTarget.getHandle();
ITargetHandle changedHandle = workspaceTarget.getHandle();
try {
final boolean isCurrent = Objects.equals(editorHandle.getMemento(), changedHandle.getMemento());
final String label = isCurrent ? PDEUIMessages.AbstractTargetPage_reloadTarget
: PDEUIMessages.AbstractTargetPage_setTarget;
Display.getDefault().asyncExec(() -> updateHyperlinkText(label));
} catch (CoreException e) {
PDECore.log(e.getStatus());
}
}
private ITargetDefinition extractTargetDefinition(Event event) {
Object data = event.getProperty(IEventBroker.DATA);
if (data instanceof ITargetDefinition) {
return (ITargetDefinition) data;
}
return null;
}
}