/*-
* Copyright © 2009 Diamond Light Source Ltd.
*
* This file is part of GDA.
*
* GDA is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 3 as published by the Free
* Software Foundation.
*
* GDA is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with GDA. If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.gda.richbeans.editors.xml;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.richbeans.api.reflection.RichBeanUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.editors.text.TextEditor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.gda.common.rcp.util.EclipseUtils;
import uk.ac.gda.richbeans.editors.DirtyContainer;
import uk.ac.gda.richbeans.editors.xml.bean.ColorManager;
import uk.ac.gda.richbeans.editors.xml.bean.XMLConfiguration;
import uk.ac.gda.richbeans.editors.xml.bean.XMLDocumentProvider;
import uk.ac.gda.util.beans.BeansFactory;
import uk.ac.gda.util.beans.xml.XMLHelpers;
import com.swtdesigner.SWTResourceManager;
/**
* An XML editor for editing castor beans. Allows the user to edit the XML
* and will validate the XML they have written.
*
* @author Matthew Gerring
*
*/
public class XMLBeanEditor extends TextEditor {
private static final Logger logger = LoggerFactory.getLogger(XMLBeanEditor.class);
private ColorManager colorManager;
private DirtyContainer container;
private final URL mappingUrl, schemaUrl;
private Object editingBean;
private IDocumentListener documentListener;
/**
* @param container
* @param mappingUrl
* @param schemaUrl
* @param bean
* @throws Exception
*/
public XMLBeanEditor(final DirtyContainer container,
final URL mappingUrl,
final URL schemaUrl,
final Object bean) throws Exception {
super();
this.container = container;
this.mappingUrl = mappingUrl;
this.schemaUrl = schemaUrl;
this.editingBean= bean;
colorManager = new ColorManager();
setSourceViewerConfiguration(new XMLConfiguration(colorManager, schemaUrl));
final XMLDocumentProvider docProv = new XMLDocumentProvider();
setDocumentProvider(docProv);
}
/**
* @return the editingBean
*/
public Object getEditingBean() {
return editingBean;
}
/**
* @param editingBean the editingBean to set
*/
public void setEditingBean(Object editingBean) {
this.editingBean = editingBean;
}
/**
* @param parent
*/
@Override
public void createPartControl(Composite parent) {
final GridLayout gridLayout = new GridLayout();
gridLayout.verticalSpacing = 0;
gridLayout.marginWidth = 0;
gridLayout.marginHeight = 0;
gridLayout.horizontalSpacing = 0;
parent.setLayout(gridLayout);
super.createPartControl(parent);
final Control text = parent.getChildren()[0];
text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final Composite messageContainer = new Composite(parent, SWT.NONE);
final GridData gd_messageContainer = new GridData(SWT.LEFT, SWT.CENTER, true, false);
messageContainer.setLayoutData(gd_messageContainer);
final GridLayout gridLayout_1 = new GridLayout();
gridLayout_1.numColumns = 2;
messageContainer.setLayout(gridLayout_1);
final Label label = new Label(messageContainer, SWT.NONE);
label.setImage(SWTResourceManager.getImage(XMLBeanEditor.class, "/icons/page_error.png"));
final Label messageLabel = new Label(messageContainer, SWT.NONE);
messageLabel.setText("Comment tags cannot be saved.");
messageContainer.setVisible(false);
this.documentListener = new IDocumentListener() {
@Override
public void documentAboutToBeChanged(DocumentEvent event) {}
@Override
public void documentChanged(DocumentEvent event) {
final IDocument doc = event.getDocument();
try {
final IRegion region = doc.getLineInformationOfOffset(event.getOffset());
final String line = doc.get(region.getOffset(), region.getLength());
final boolean comm = line.indexOf("<!--")>-1;
if(!messageContainer.isDisposed())
messageContainer.setVisible(comm);
} catch (BadLocationException e) {
logger.error(e.getMessage(), e);
}
}
};
getDocumentProvider().getDocument(null).addDocumentListener(documentListener);
}
@Override
public void dispose() {
getDocumentProvider().getDocument(null).removeDocumentListener(documentListener);
colorManager.dispose();
super.dispose();
}
private boolean dirtyOveride = false;
@Override
public boolean isDirty() {
if (dirtyOveride) return false;
container.setDirty(super.isDirty());
return super.isDirty();
}
@Override
public void doSave(IProgressMonitor monitor) {
final IFile file = getIFile();
monitor.beginTask(file.getName(), 100);
try {
try {
// This way comments are lost but private fields are preserved.
xmlToBean();
PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new WorkspaceModifyOperation() {
@Override
protected void execute(IProgressMonitor monitor) throws CoreException, InvocationTargetException,
InterruptedException {
try {
XMLHelpers.writeToXML(mappingUrl, editingBean, file.getLocation().toFile());
final IFile ifile = getIFile();
if (ifile != null) {
ifile.refreshLocal(IResource.DEPTH_ZERO, null);
}
} catch (Exception e) {
throw new InvocationTargetException(e);
}
}
});
container.setDirty(false);
} catch (Exception e) {
try {
logger.debug(e.getMessage(), e);
MessageDialog.openError(getSite().getShell(),
"XML Validation Error",
"The XML is not valid and cannot be saved.\n\n"+
"Error message:\n"+getSantitizedExceptionMessage(e.getMessage()));
return;
} catch (Throwable ne) {
logger.error(ne.getMessage(), ne);
}
}
} finally {
monitor.done();
}
}
/**
* Might be null
* @return iFile
*/
public IFile getIFile() {
return EclipseUtils.getIFile(getEditorInput());
}
/**
* Should move this to a utility class at some point.
* @param exceptionMessage
* @return string
*/
public static String getSantitizedExceptionMessage(String exceptionMessage) {
if (exceptionMessage== null) return null;
final int index = exceptionMessage.indexOf(':');
if (index>-1) exceptionMessage = exceptionMessage.substring(index+1);
return exceptionMessage;
}
/**
* Call to send the bean to text.
* @throws Exception
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws InstantiationException
* @throws IllegalAccessException
*/
public void beanToXML(final List<String> privateFields) throws Exception {
try {
dirtyOveride = true;
final IDocument doc = getDocumentProvider().getDocument(null);
final StringWriter writer = new StringWriter();
final Object clone = BeansFactory.deepClone(editingBean);
if (privateFields!=null) {
for (String fieldName : privateFields) {
RichBeanUtils.setBeanValue(clone, fieldName, null);
}
}
try {
XMLHelpers.writeToXML(mappingUrl, clone, writer);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
doc.set(writer.toString());
} finally {
// The isDirty method in TextEditor is called on another thread
// after the text changed was notified. Therefore we start listening
// again with a separate runnable.
getSite().getShell().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
dirtyOveride = false;
}
});
}
}
/**
* Gets the XML into the bean. If the XML is invalid, will throw exception
* with appropriate message.
*
* @throws Exception
*/
public void xmlToBean() throws Exception {
final IDocument doc = getDocumentProvider().getDocument(null);
XMLHelpers.setFromXML(editingBean, mappingUrl, schemaUrl, doc.get());
}
/**
* Unused
* @param monitor
public void doSaveOld(IProgressMonitor monitor) {
// Check XML
try {
monitor.beginTask("Validating and Saving "+editingBean.getClass().getName(), 100);
xmlToBean();
final IFile ifile = getIFile();
if (ifile!=null) {
ifile.refreshLocal(IResource.DEPTH_ZERO, monitor);
}
updateState(getEditorInput());
validateState(getEditorInput());
performSave(true, monitor);
if (ifile!=null) {
ifile.refreshLocal(IResource.DEPTH_ZERO, monitor);
}
container.setDirty(false);
} catch (Exception e) { // XML Cannot be parsed.
try {
logger.debug(e.getMessage(), e);
MessageDialog.openError(getSite().getShell(),
"XML Validation Error",
"The XML is not valid and cannot be saved.\n\n"+
"Error message:\n"+getSantitizedExceptionMessage(e.getMessage()));
return;
} catch (Throwable ne) {
logger.error(ne.getMessage(), ne);
}
} finally {
monitor.done();
}
}
*/
}