/*******************************************************************************
 * Copyright (c) 2005, 2024 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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
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.IFile;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
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.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextViewer;
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.ITargetDefinition;
import org.eclipse.pde.core.target.ITargetHandle;
import org.eclipse.pde.core.target.ITargetPlatformService;
import org.eclipse.pde.core.target.LoadTargetDefinitionJob;
import org.eclipse.pde.core.target.TargetEvents;
import org.eclipse.pde.internal.core.ICoreConstants;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.PDEPreferencesManager;
import org.eclipse.pde.internal.core.target.P2TargetUtils;
import org.eclipse.pde.internal.core.target.TargetDefinitionPersistenceHelper;
import org.eclipse.pde.internal.core.target.TargetPlatformService;
import org.eclipse.pde.internal.core.target.WorkspaceFileTargetHandle;
import org.eclipse.pde.internal.ui.IHelpContextIds;
import org.eclipse.pde.internal.ui.PDEPlugin;
import org.eclipse.pde.internal.ui.PDEPluginImages;
import org.eclipse.pde.internal.ui.PDEUIMessages;
import org.eclipse.pde.internal.ui.preferences.TargetPlatformPreferencePage;
import org.eclipse.pde.internal.ui.shared.target.ITargetChangedListener;
import org.eclipse.pde.internal.ui.shared.target.TargetContentsGroup;
import org.eclipse.pde.internal.ui.shared.target.TargetLocationsGroup;
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.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.forms.AbstractFormPart;
import org.eclipse.ui.forms.HyperlinkGroup;
import org.eclipse.ui.forms.IFormPart;
import org.eclipse.ui.forms.IManagedForm;
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.FormToolkit;
import org.eclipse.ui.forms.widgets.ImageHyperlink;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.progress.UIJob;
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 final List<IManagedForm> fManagedFormPages = new ArrayList<>(2);
	private TextEditor fTextualEditor;
	private int fSourceTabIndex;
	private IDocument fTargetDocument;
	private IDocumentListener fTargetDocumentListener;

	private final InputHandler fInputHandler = new InputHandler();
	private TargetChangedListener fTargetChangedListener;
	private boolean fDirty;

	private ImageHyperlink fLoadHyperlink;

	private final 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);
		}

		IEventBroker eventBroker = PlatformUI.getWorkbench().getService(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(Status.error(e.getMessage(), e));
			showError(PDEUIMessages.TargetEditor_5, ce);
		}
	}

	@Override
	public void doSave(IProgressMonitor monitor) {
		ITargetHandle handle = fInputHandler.getTarget().getHandle();
		fInputHandler.setSaving(true);
		if (!isActiveTabTextualEditor()) {
			markStale();
			updateTextualEditor();
		}
		fTextualEditor.doSave(monitor);
		fDirty = false;
		editorDirtyStateChanged();
		fInputHandler.setSaving(false);

		TargetPlatformService.scheduleEvent(TargetEvents.TOPIC_TARGET_SAVED, handle);
	}

	@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() {
		IEventBroker eventBroker = PlatformUI.getWorkbench().getService(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();
	}

	@Override
	public <T> T getAdapter(Class<T> adapter) {
		if (adapter.equals(ITargetHandle.class)) {
			ITargetDefinition target = getTarget();
			if (target != null) {
				return adapter.cast(target.getHandle());
			}
		}
		return super.getAdapter(adapter);
	}
	/**
	 * 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 memento = null;
				String hyperLinkText = PDEUIMessages.AbstractTargetPage_setTarget;
				String 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);

		Action openTPPreference = new Action("openTPPreference") { //$NON-NLS-1$
			@Override
			public void run() {
				PreferencesUtil.createPreferenceDialogOn(getSite().getShell(), TargetPlatformPreferencePage.PAGE_ID,
						null, null).open();
			}
		};
		openTPPreference.setToolTipText(PDEUIMessages.AbstractTargetPage_openPreferences);
		openTPPreference.setImageDescriptor(PDEPluginImages.DESC_SETTINGS_OBJ);

		form.getToolBarManager().add(setAsTarget);
		form.getToolBarManager().add(export);
		form.getToolBarManager().add(openTPPreference);
		form.getToolBarManager().add(help);
		form.updateToolBar();

	}

	/**
	 * Adds the given form to the list of forms to be refreshed when reverting
	 */
	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();
				IPath path = IPath.fromOSString(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(Status.error("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
	 */

	private void addTextualEditorPage() throws PartInitException {
		@SuppressWarnings("restriction")
		TextEditor newEditor = new org.eclipse.ui.internal.genericeditor.ExtensionBasedTextEditor();
		fTextualEditor = newEditor;
		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 (TransformerException 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 final 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());
					fLocationTree.setExpandCollapseState(false);
				}
				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 = UIJob.create(PDEUIMessages.TargetEditor_2, monitor -> {
							if (fContentTree != null) {
								if (status.getSeverity() == IStatus.CANCEL) {
									fContentTree.setCancelled();
								} else {
									fContentTree.setInput(getTarget());
								}
							}
							if (fLocationTree != null) {
								fLocationTree.setInput(getTarget());
								fLocationTree.setExpandCollapseState(true);
							}
						});
						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 extension5) {
			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;
	}

}
