Java tutorial
/*- * 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; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.List; import org.dawnsci.common.richbeans.beans.BeanUI; import org.dawnsci.common.richbeans.beans.BeansFactory; import org.dawnsci.common.richbeans.beans.IFieldProvider; import org.dawnsci.common.richbeans.beans.IFieldWidget; import org.dawnsci.common.richbeans.event.ValueEvent; import org.dawnsci.common.richbeans.event.ValueListener; import org.eclipse.core.commands.operations.IUndoContext; import org.eclipse.core.commands.operations.OperationHistoryFactory; 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.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IReusableEditor; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.WorkspaceModifyOperation; import org.eclipse.ui.part.EditorPart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.gda.common.rcp.util.EclipseUtils; import uk.ac.gda.util.beans.xml.XMLHelpers; /** * This class is designed to be extended and then the extending class edited with * RCP developer. By naming the data entry fields the same as the fields in the bean, * this class will automatically save and open the bean values into the UI. * * @author Matthew Gerring * */ public abstract class RichBeanEditorPart extends EditorPart implements ValueListener, IReusableEditor, IFieldProvider { protected static final Logger logger = LoggerFactory.getLogger(RichBeanEditorPart.class); /** * The bean used for state */ protected volatile Object editingBean; /** * A bean used in the undo stack assigned when the editor * changes IEditorInput (normally at start up) */ protected volatile Object previousUndoBean; /** * An interface which provides for if the editor is dirty. */ protected final DirtyContainer dirtyContainer; /** * The file path which can be null */ protected String path; /** * The URL used in in saving the editing bean */ protected final URL mappingURL; private boolean undoStackActive = true; private boolean isDisposed = false; /** * Return the name that the editor should be referred as in error messages and * in the multi-editor view. * @return string */ protected abstract String getRichEditorTabText(); /** * @param path * @param mappingURL * @param dirtyContainer * @param editingBean */ public RichBeanEditorPart(final String path, final URL mappingURL, final DirtyContainer dirtyContainer, final Object editingBean) { this.path = path; this.mappingURL = mappingURL; this.dirtyContainer = dirtyContainer; this.editingBean = editingBean; /** * The final undo state is recorded by cloning the editing bean and * not editing it further when this editor is */ try { this.previousUndoBean = BeansFactory.deepClone(editingBean); } catch (Exception e) { try { logger.error("Cannot clone editing bean.", e); this.previousUndoBean = editingBean.getClass().newInstance(); } catch (Exception e1) { logger.error("Cannot instantiate editing bean.", e1); } } } @Override public void doSave(IProgressMonitor monitor) { if (path == null) return; // Nothing to save. final File file = new File(path); monitor.beginTask(file.getName(), 100); try { try { updateFromUIAndReturnEditingBean(); WorkspaceModifyOperation saveOp = new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException { try { XMLHelpers.writeToXML(mappingURL, editingBean, path); final IFile ifile = getIFile(); if (ifile != null) { ifile.refreshLocal(IResource.DEPTH_ZERO, null); } } catch (Exception e) { logger.error( "Error - RichBeanEditorPart.doSave() failed. Path=" + path + e.getMessage()); MessageDialog dialog = new MessageDialog(getSite().getShell(), "File didn't save", null, "Path=" + path, MessageDialog.ERROR, new String[] {}, 0); int result = dialog.open(); System.out.println(result); throw new InvocationTargetException(e); } } }; PlatformUI.getWorkbench().getProgressService().busyCursorWhile(saveOp); notifyFileSaved(file); dirtyContainer.setDirty(false); } catch (Exception e) { // Saving is very important as it saves the state of the editors when switching between editors, perspectives, etc. logger.error("Error - RichBeanEditorPart.doSave() failed. Path=" + path + e.getMessage()); MessageDialog dialog = new MessageDialog(getSite().getShell(), "File didn't save", null, "Path=" + path, MessageDialog.ERROR, new String[] {}, 0); int result = dialog.open(); System.out.println(result); } } finally { monitor.done(); } } /** * Override to be called when a file is saved. * @param file */ protected void notifyFileSaved(@SuppressWarnings("unused") File file) { } @Override public void doSaveAs() { //System.out.println("Do Save as Part"); } @Override public void init(IEditorSite site, IEditorInput input) throws PartInitException { setSite(site); setInput(input); if (dirtyContainer != null) dirtyContainer.setDirty(false); } /** * Might be null * @return iFile */ public IFile getIFile() { return EclipseUtils.getIFile(getEditorInput()); } @Override public void setInput(IEditorInput input) { super.setInput(input); if (input != null) { setPartName(input.getName()); } } /** * @return the editingBean * @throws Exception */ public Object updateFromUIAndReturnEditingBean() throws Exception { BeanUI.uiToBean(getEditorUI(), editingBean); return editingBean; } /** * @return the path */ public String getPath() { return path; } /** Should only be used internally. * Do not override / change to be not final. * * @param editingBean the editingBean to set */ protected final void setEditingBean(Object editingBean) { this.editingBean = editingBean; } /** * @param path the path to set */ public void setPath(String path) { this.path = path; } protected IUndoContext undoableContext; public void setUndoableContext(IUndoContext context) { this.undoableContext = context; } @Override public void valueChangePerformed(ValueEvent e) { dirtyContainer.setDirty(true); recordUndoableEvent(e.getFieldName()); } protected void recordUndoableEvent(final String fieldName) { if (!isUndoStackActive()) return; try { if (Thread.currentThread() != getSite().getShell().getDisplay().getThread()) return; // Save current bean state final Object previousBean = BeansFactory.deepClone(previousUndoBean); // Be careful about messing with this, very sensitive to make // this code correctly generic, try with many editors before // committing a change. Object newBean = BeansFactory.deepClone(editingBean); updateFromUIAndReturnEditingBean(); newBean = BeansFactory.deepClone(newBean); // If the values are the same as last edited, do nothing. if (previousUndoBean != null && previousUndoBean.equals(newBean)) return; // Add operation to stack. final RichBeanEditorOperation undoableOperation = new RichBeanEditorOperation(fieldName, previousBean, newBean, this); undoableOperation.addContext(undoableContext); OperationHistoryFactory.getOperationHistory().add(undoableOperation); previousUndoBean = newBean; getEditorSite().getActionBars().updateActionBars(); } catch (Exception e1) { logger.error("Unable to add event to stack " + editingBean.toString(), e1); } } @Override public String getValueListenerName() { return "DirtyListener"; } @Override public boolean isDirty() { if (path == null) return false; return dirtyContainer.isDirty(); } @Override public boolean isSaveAsAllowed() { return true; } protected boolean addedListenersAndSwitchedOn = false; /** * Extending classes should normally overide this method with bounds and * choice information and then call this method with super.linkUI(); */ public void linkUI(@SuppressWarnings("unused") final boolean isPageChange) { // Call a method which assigns properties from the scan parameters bean to // the ui in this class. This class can be used to do this for any // bean and any UI object (editor etc.) try { BeanUI.switchState(editingBean, getEditorUI(), false); BeanUI.beanToUI(editingBean, getEditorUI()); BeanUI.switchState(editingBean, getEditorUI(), true); if (!addedListenersAndSwitchedOn) { BeanUI.addValueListener(editingBean, getEditorUI(), this); BeanUI.setBeanFields(editingBean, getEditorUI()); addedListenersAndSwitchedOn = true; // TODO resolve this - the DawnSci widgets do not allow expressions - a licensing issue?? // // We ensure that fields being edited which allow expressions, have the IExpressionManager // // available to evaluate the expressions for them. // BeanUI.notify(editingBean, getEditorUI(), new BeanProcessor() { // @Override // public void process(String name, Object value, IFieldWidget box) throws Exception { // if (box instanceof IExpressionWidget) { // final IExpressionWidget expressionBox = (IExpressionWidget)box; // if (expressionBox.isExpressionAllowed()){ // final BeanExpressionManager man = new BeanExpressionManager(expressionBox, RichBeanEditorPart.this); // man.setAllowedSymbols(getExpressionFields()); // expressionBox.setExpressionManager(man); // } // } // } // }); } } catch (Exception e) { logger.error("Cannot push values from bean to UI in linkUI()", e); } } /** * Override to define a different object for editing the UI. * @return the editorUI object */ protected Object getEditorUI() { return this; } protected List<String> expressionFields; /** * Override this method (usually by calling it too) to add values which should be available in expressions. * * NOTE when overriding that after the first call the expressionFields are cached to avoid too many * interogations of the bean. * * @return List<String> of possible expression vars. * @throws Exception */ protected List<String> getExpressionFields() throws Exception { if (expressionFields == null) { expressionFields = BeanUI.getEditingFields(editingBean, getEditorUI()); } return expressionFields; } @Override public IFieldWidget getField(final String fieldName) throws Exception { return BeanUI.getFieldWiget(fieldName, getEditorUI()); } @Override public Object getFieldValue(final String fieldName) throws Exception { return getField(fieldName).getValue(); } @Override public void dispose() { setDisposed(true); super.dispose(); try { BeanUI.dispose(editingBean, getEditorUI()); } catch (Exception e) { logger.error("Cannot dispose parts as expected", e); } } public boolean isDisposed() { return isDisposed; } public void setDisposed(boolean isDisposed) { this.isDisposed = isDisposed; } /** * @return Returns the undoStackActive. */ public boolean isUndoStackActive() { return undoStackActive; } /** * @param undoStackActive The undoStackActive to set. */ public void setUndoStackActive(boolean undoStackActive) { this.undoStackActive = undoStackActive; } }