blob: 2d63e3c5271be7ba43a31270d3dd94db80b42c24 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2010 Sonatype, Inc.
* All rights reserved. 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:
* Sonatype, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.m2e.editor.pom;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.ARTIFACT_ID;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.GROUP_ID;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.PARENT;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.VERSION;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.findChild;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.getTextValue;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.performOnDOMDocument;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.removeChild;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.removeIfNoChildElement;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.setText;
import static org.eclipse.m2e.editor.pom.FormUtils.isEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
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.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.apache.maven.project.MavenProject;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.internal.IMavenConstants;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.IMavenProjectRegistry;
import org.eclipse.m2e.core.ui.internal.actions.OpenPomAction;
import org.eclipse.m2e.core.ui.internal.dialogs.InputHistory;
import org.eclipse.m2e.core.ui.internal.editing.PomEdits;
import org.eclipse.m2e.core.ui.internal.editing.PomEdits.OperationTuple;
import org.eclipse.m2e.editor.MavenEditorImages;
import org.eclipse.m2e.editor.internal.Messages;
import org.eclipse.m2e.editor.xml.internal.FormHoverProvider;
/**
* This class provides basic page editor functionality (event listeners, readonly, etc)
*
* @author Anton Kraev
* @author Eugene Kuleshov
*/
public abstract class MavenPomEditorPage extends FormPage {
private static final String MODIFY_LISTENER = "MODIFY_LISTENER";
private static final String VALUE_PROVIDER = "VALUE_PROVIDER";
private static final Logger LOG = LoggerFactory.getLogger(MavenPomEditorPage.class);
// parent editor
protected final MavenPomEditor pomEditor;
private boolean updatingModel2 = false;
// have we loaded data?
private boolean dataLoaded;
private InputHistory inputHistory;
private Action selectParentAction;
private IModelStateListener listener;
private boolean alreadyShown = false;
public MavenPomEditorPage(MavenPomEditor pomEditor, String id, String title) {
super(pomEditor, id, title);
this.pomEditor = pomEditor;
this.inputHistory = new InputHistory(id);
listener = new IModelStateListener() {
public void modelResourceMoved(IStructuredModel oldModel, IStructuredModel newModel) {
}
public void modelResourceDeleted(IStructuredModel model) {
}
public void modelReinitialized(IStructuredModel structuredModel) {
}
public void modelDirtyStateChanged(IStructuredModel model, boolean isDirty) {
}
public void modelChanged(IStructuredModel model) {
if(!updatingModel2) {
loadData();
}
}
public void modelAboutToBeReinitialized(IStructuredModel structuredModel) {
}
public void modelAboutToBeChanged(IStructuredModel model) {
}
};
}
public MavenPomEditor getPomEditor() {
return pomEditor;
}
/**
* all edits in the editor to be channeled through this method..
*
* @param operation
* @param logger
* @param logMessage
*/
public final void performEditOperation(PomEdits.Operation operation, Logger logger, String logMessage) {
try {
updatingModel2 = true;
PomEdits.performOnDOMDocument(new PomEdits.OperationTuple(getPomEditor().getDocument(), operation));
} catch(Exception e) {
logger.error(logMessage, e);
} finally {
updatingModel2 = false;
}
}
@Override
protected void createFormContent(IManagedForm managedForm) {
ScrolledForm form = managedForm.getForm();
IToolBarManager toolBarManager = form.getToolBarManager();
// toolBarManager.add(pomEditor.showAdvancedTabsAction);
selectParentAction = new Action(Messages.MavenPomEditorPage_action_open, MavenEditorImages.PARENT_POM) {
public void run() {
final String[] ret = new String[3];
try {
performOnDOMDocument(new OperationTuple(getPomEditor().getDocument(), document -> {
Element parent = findChild(document.getDocumentElement(), PARENT);
ret[0] = getTextValue(findChild(parent, GROUP_ID));
ret[1] = getTextValue(findChild(parent, ARTIFACT_ID));
ret[2] = getTextValue(findChild(parent, VERSION));
}, true));
// XXX listen to parent modification and accordingly enable/disable action
if(!isEmpty(ret[0]) && !isEmpty(ret[1]) && !isEmpty(ret[2])) {
new Job(Messages.MavenPomEditorPage_job_opening) {
protected IStatus run(IProgressMonitor monitor) {
OpenPomAction.openEditor(ret[0], ret[1], ret[2], getPomEditor().getMavenProject(), monitor);
return Status.OK_STATUS;
}
}.schedule();
}
} catch(Exception e) {
LOG.error("Error finding parent element", e);
}
}
};
toolBarManager.add(selectParentAction);
updateParentAction();
toolBarManager.add(new Action(Messages.MavenPomEditorPage_actio_refresh, MavenEditorImages.REFRESH) {
public void run() {
pomEditor.reload();
}
});
form.updateToolBar();
// compatibility proxy to support Eclipse 3.2
FormUtils.decorateHeader(managedForm.getToolkit(), form.getForm());
inputHistory.load();
}
public void setActive(boolean active) {
super.setActive(active);
doLoadData(active);
if(active) {
getPomEditor().getModel().addModelStateListener(listener);
} else {
getPomEditor().getModel().removeModelStateListener(listener);
}
if(active && alreadyShown) {
loadData();
updateParentAction();
}
alreadyShown = true;
//MNGECLIPSE-2674 checkreadonly is only calculated once, no need
// to update everytime this page gets active
boolean readOnly = pomEditor.checkReadOnly();
if(readOnly) {
// only perform when readonly==true, to prevent enabling all buttons on the page.
FormUtils.setReadonly((Composite) getPartControl(), readOnly);
}
}
public boolean isReadOnly() {
return pomEditor.checkReadOnly();
}
private void doLoadData(boolean active) {
try {
if(active && !dataLoaded) {
dataLoaded = true;
if(getPartControl() != null) {
getPartControl().getDisplay().asyncExec(() -> {
try {
loadData();
updateParentAction();
} catch(Throwable e) {
LOG.error("Error loading data", e); //$NON-NLS-1$
}
});
}
}
//error markers have to be always updated..
IFile pomFile = pomEditor.getPomFile();
if(pomFile != null) {
String text = ""; //$NON-NLS-1$
IMarker[] markers = pomFile.findMarkers(IMavenConstants.MARKER_ID, true, IResource.DEPTH_ZERO);
IMarker max = null;
int maxSev = -1;
if(markers != null) {
for(IMarker mark : markers) {
IMarker toAdd = max;
int sev = mark.getAttribute(IMarker.SEVERITY, -1);
if(sev > maxSev) {
max = mark;
maxSev = sev;
} else {
toAdd = mark;
}
if(toAdd != null) {
//errors get prepended while warnings get appended.
if(toAdd.getAttribute(IMarker.SEVERITY, -1) == IMarker.SEVERITY_ERROR) {
text = NLS.bind(Messages.MavenPomEditorPage_error_add, toAdd.getAttribute(IMarker.MESSAGE, "")) + text; //$NON-NLS-2$
} else {
text = text
+ NLS.bind(Messages.MavenPomEditorPage_warning_add, toAdd.getAttribute(IMarker.MESSAGE, "")); //$NON-NLS-2$
}
}
}
}
if(max != null) {
String head;
String maxText = max.getAttribute(IMarker.MESSAGE, Messages.MavenPomEditorPage_error_unknown);
if(text.length() > 0) {
//if we have multiple errors
text = NLS.bind(Messages.MavenPomEditorPage_add_desc, maxText, text);
if(markers != null) {
String number = Integer.toString(markers.length - 1);
head = NLS.bind(Messages.FormUtils_click_for_details2,
maxText.length() > FormUtils.MAX_MSG_LENGTH ? maxText.substring(0, FormUtils.MAX_MSG_LENGTH)
: maxText,
number);
} else {
head = maxText;
if(head.length() > FormUtils.MAX_MSG_LENGTH) {
head = NLS.bind(Messages.FormUtils_click_for_details, head.substring(0, FormUtils.MAX_MSG_LENGTH));
}
}
} else {
//only this one
text = maxText;
head = maxText;
if(head.length() > FormUtils.MAX_MSG_LENGTH) {
head = NLS.bind(Messages.FormUtils_click_for_details, head.substring(0, FormUtils.MAX_MSG_LENGTH));
}
}
int severity;
switch(max.getAttribute(IMarker.SEVERITY, -1)) {
case IMarker.SEVERITY_ERROR: {
severity = IMessageProvider.ERROR;
break;
}
case IMarker.SEVERITY_WARNING: {
severity = IMessageProvider.WARNING;
break;
}
case IMarker.SEVERITY_INFO: {
severity = IMessageProvider.INFORMATION;
break;
}
default: {
severity = IMessageProvider.NONE;
}
}
setErrorMessageForMarkers(head, text, severity, markers);
} else {
setErrorMessageForMarkers(null, null, IMessageProvider.NONE, new IMarker[0]);
}
}
} catch(final CoreException ex) {
LOG.error(ex.getMessage(), ex);
final String msg = ex.getMessage();
setErrorMessageForMarkers(msg, msg, IMessageProvider.ERROR, new IMarker[0]);
}
}
private void setErrorMessageForMarkers(final String msg, final String tip, final int severity,
final IMarker[] markers) {
if(getPartControl() != null && !getPartControl().isDisposed()) {
getPartControl().getDisplay().asyncExec(() -> {
if(!getManagedForm().getForm().isDisposed()) {
FormHoverProvider.Execute runnable = FormHoverProvider.createHoverRunnable(
getManagedForm().getForm().getShell(), markers, getPomEditor().getSourcePage().getTextViewer());
if(runnable != null) {
FormUtils.setMessageWithPerformer(getManagedForm().getForm(), msg, severity, runnable);
} else {
FormUtils.setMessageAndTTip(getManagedForm().getForm(), msg, tip, severity);
}
}
});
}
}
public void setErrorMessage(final String msg, final int severity) {
if(getPartControl() != null && !getPartControl().isDisposed()) {
getPartControl().getDisplay().asyncExec(() -> {
if(!getManagedForm().getForm().isDisposed()) {
FormUtils.setMessage(getManagedForm().getForm(), msg, severity);
}
});
}
}
public boolean isAdapterForType(Object type) {
return false;
}
private void updateParentAction() {
if(selectParentAction != null) {
final boolean[] ret = new boolean[1];
try {
performOnDOMDocument(new OperationTuple(getPomEditor().getDocument(), document -> {
Element parent = findChild(document.getDocumentElement(), PARENT);
String g = getTextValue(findChild(parent, GROUP_ID));
String a = getTextValue(findChild(parent, ARTIFACT_ID));
String v = getTextValue(findChild(parent, VERSION));
ret[0] = g != null && a != null && v != null;
}, true));
} catch(Exception e) {
ret[0] = false;
}
if(ret[0]) {
selectParentAction.setEnabled(true);
} else {
selectParentAction.setEnabled(false);
}
}
}
/**
* creates a text field/Ccombo decoration that shows the evaluated value
*
* @param control
*/
public final void createEvaluatorInfo(final Control control) {
if(!(control instanceof Text || control instanceof CCombo)) {
throw new IllegalArgumentException("Not a Text or CCombo");
}
FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault()
.getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION);
final ControlDecoration decoration = new ControlDecoration(control, SWT.RIGHT | SWT.TOP) {
/* (non-Javadoc)
* @see org.eclipse.jface.fieldassist.ControlDecoration#getDescriptionText()
*/
@Override
public String getDescriptionText() {
MavenProject mp = getPomEditor().getMavenProject();
if(mp != null) {
return FormUtils.simpleInterpolate(mp,
control instanceof Text ? ((Text) control).getText() : ((CCombo) control).getText());
}
return "Cannot interpolate expressions, not resolvable file.";
}
};
decoration.setShowOnlyOnFocus(false);
decoration.setImage(fieldDecoration.getImage());
decoration.setShowHover(true);
decoration.hide(); //hide and wait for the value to be set.
decoration.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
decoration.showHoverText(decoration.getDescriptionText());
}
});
ModifyListener listener = e -> {
String text = control instanceof Text ? ((Text) control).getText() : ((CCombo) control).getText();
if(text.indexOf("${") != -1 && text.indexOf("}") != -1) {
decoration.show();
} else {
decoration.hide();
}
};
if(control instanceof Text) {
((Text) control).addModifyListener(listener);
} else {
((CCombo) control).addModifyListener(listener);
}
control.addMouseTrackListener(new MouseTrackListener() {
public void mouseHover(MouseEvent e) {
decoration.showHoverText(decoration.getDescriptionText());
}
public void mouseExit(MouseEvent e) {
decoration.hideHover();
}
public void mouseEnter(MouseEvent e) {
}
});
}
public void dispose() {
inputHistory.save();
MavenPomEditor pe = getPomEditor();
if(pe != null && pe.getModel() != null) {
pe.getModel().removeModelStateListener(listener);
}
super.dispose();
}
public abstract void loadData();
public void setElementValueProvider(Control control, ElementValueProvider provider) {
control.setData(VALUE_PROVIDER, provider);
}
public void setModifyListener(final Control control) {
Assert.isTrue(control instanceof CCombo || control instanceof Text || control instanceof Combo);
ModifyListener ml = new ModifyListener() {
String getText(Control control) {
if(control instanceof Text) {
return ((Text) control).getText();
}
if(control instanceof Combo) {
return ((Combo) control).getText();
}
if(control instanceof CCombo) {
return ((CCombo) control).getText();
}
throw new IllegalStateException();
}
public void modifyText(ModifyEvent e) {
final ElementValueProvider provider = (ElementValueProvider) control.getData(VALUE_PROVIDER);
if(provider == null) {
throw new IllegalStateException("no value provider for " + control);
}
performEditOperation(document -> {
String text = getText(control);
if(isEmpty(text) || text.equals(provider.getDefaultValue())) {
//remove value
Element el1 = provider.find(document);
if(el1 != null) {
Node parent = el1.getParentNode();
if(parent instanceof Element) {
removeChild((Element) parent, el1);
removeIfNoChildElement((Element) parent);
}
}
} else {
//set value and any parents..
Element el2 = provider.get(document);
setText(el2, text);
}
}, LOG, "Error updating document");
}
};
control.setData(MODIFY_LISTENER, ml);
}
public void removeNotifyListener(Text control) {
if(!control.isDisposed()) {
ModifyListener listener = (ModifyListener) control.getData(MODIFY_LISTENER);
if(listener != null) {
control.removeModifyListener(listener);
}
}
}
public void addNotifyListener(Text control) {
if(!control.isDisposed()) {
ModifyListener listener = (ModifyListener) control.getData(MODIFY_LISTENER);
if(listener != null) {
control.addModifyListener(listener);
}
}
}
public void removeNotifyListener(CCombo control) {
if(!control.isDisposed()) {
ModifyListener listener = (ModifyListener) control.getData(MODIFY_LISTENER);
if(listener != null) {
control.removeModifyListener(listener);
}
}
}
public void addNotifyListener(CCombo control) {
if(!control.isDisposed()) {
ModifyListener listener = (ModifyListener) control.getData(MODIFY_LISTENER);
if(listener != null) {
control.addModifyListener(listener);
}
}
}
public void removeNotifyListener(Combo control) {
if(!control.isDisposed()) {
ModifyListener listener = (ModifyListener) control.getData(MODIFY_LISTENER);
if(listener != null) {
control.removeModifyListener(listener);
}
}
}
public void addNotifyListener(Combo control) {
if(!control.isDisposed()) {
ModifyListener listener = (ModifyListener) control.getData(MODIFY_LISTENER);
if(listener != null) {
control.addModifyListener(listener);
}
}
}
public IMavenProjectFacade findModuleProject(String moduleName) {
IFile pomFile = pomEditor.getPomFile();
if(pomFile != null) {
return findModuleProject(pomFile, moduleName);
}
return null;
}
private IMavenProjectFacade findModuleProject(IFile pomFile, String module) {
IPath modulePath = pomFile.getParent().getLocation();
if(modulePath == null)
return null;
modulePath = modulePath.append(module);
//it's possible to have the pom file name in the module path..
if(!modulePath.lastSegment().endsWith("pom.xml")) { //$NON-NLS-1$
modulePath = modulePath.append("pom.xml"); //$NON-NLS-1$
}
IMavenProjectRegistry projectManager = MavenPlugin.getMavenProjectRegistry();
IMavenProjectFacade[] facades = projectManager.getProjects();
for(int i = 0; i < facades.length; i++ ) {
if(modulePath.equals(facades[i].getPom().getLocation())) {
return facades[i];
}
}
return null;
}
public IFile findModuleFile(String moduleName) {
IFile pomFile = pomEditor.getPomFile();
if(pomFile != null) {
IPath modulePath = pomFile.getParent().getLocation();
if(modulePath == null)
return null;
modulePath = modulePath.append(moduleName);
//it's possible to have the pom file name in the module path..
if(!modulePath.lastSegment().endsWith("pom.xml")) { //$NON-NLS-1$
modulePath = modulePath.append("pom.xml"); //$NON-NLS-1$
}
IFile file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(modulePath);
return file;
}
return null;
}
public void initPopupMenu(Viewer viewer, String id) {
MenuManager menuMgr = new MenuManager("#PopupMenu-" + id); //$NON-NLS-1$
menuMgr.setRemoveAllWhenShown(true);
Menu menu = menuMgr.createContextMenu(viewer.getControl());
viewer.getControl().setMenu(menu);
getEditorSite().registerContextMenu(MavenPomEditor.EDITOR_ID + id, menuMgr, viewer, false);
}
/**
* Adapter for Text, Combo and CCombo widgets
*/
public interface TextAdapter {
String getText();
void addModifyListener(ModifyListener listener);
}
public IProject getProject() {
IFile pomFile = pomEditor.getPomFile();
return pomFile != null ? pomFile.getProject() : null;
}
protected void addToHistory(Control control) {
inputHistory.add(control);
}
/**
* pages gets notified when cached effective model has changed.
*/
public void mavenProjectHasChanged() {
}
}