package edu.ualberta.med.biobank.treeview;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.validation.ConstraintViolationException;
import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.web.bindery.event.shared.EventBus;
import edu.ualberta.med.biobank.BiobankPlugin;
import edu.ualberta.med.biobank.forms.BiobankFormBase;
import edu.ualberta.med.biobank.forms.input.FormInput;
import edu.ualberta.med.biobank.gui.common.BgcLogger;
import edu.ualberta.med.biobank.gui.common.BgcPlugin;
import edu.ualberta.med.biobank.treeview.listeners.AdapterChangedEvent;
import edu.ualberta.med.biobank.treeview.listeners.AdapterChangedListener;
import edu.ualberta.med.biobank.treeview.util.DeltaEvent;
import edu.ualberta.med.biobank.treeview.util.IDeltaListener;
import edu.ualberta.med.biobank.treeview.util.NullDeltaListener;
import gov.nih.nci.system.applicationservice.ApplicationException;
/**
* Base class for all "Session" tree view nodes. Generally, most of the nodes in
* the tree are adapters for classes in the ORM model.
*/
public abstract class AbstractAdapterBase implements
Comparable<AbstractAdapterBase> {
private static BgcLogger LOGGER = BgcLogger
.getLogger(AbstractAdapterBase.class.getName());
private Integer id;
private String label;
private final String tooltip;
protected AbstractAdapterBase parent;
protected boolean hasChildren;
protected List<AbstractAdapterBase> children;
protected EventBus eventBus;
// used when add or remove children. Initialised to a listener that does
// nothing. See NodeContentProvider for an implementation
private IDeltaListener deltaListener = NullDeltaListener.getSoleInstance();
// FIXME can we merge this list of listeners with the DeltaListener ?
private final List<AdapterChangedListener> listeners;
protected boolean isDeletable = false;
protected boolean isEditable = false;
protected boolean isReadable = false;
public AbstractAdapterBase(AbstractAdapterBase parent, Integer id,
String label, String tooltip, boolean hasChildren) {
this.parent = parent;
children = new ArrayList<AbstractAdapterBase>();
this.id = id;
this.label = label;
this.tooltip = tooltip;
this.hasChildren = hasChildren;
listeners = new ArrayList<AdapterChangedListener>();
// TODO: this class should be injected, not inject itself. Worst case,
// have some AdapterBase super-class with an EventBus that injects
// itself upon instantiation?
Injector injector = BiobankPlugin.getInjector();
injector.injectMembers(this);
}
@Inject
public void setEventBus(EventBus eventBus) {
this.eventBus = eventBus;
}
public void init() {
// TODO Auto-generated method stub
}
public void setParent(AbstractAdapterBase parent) {
this.parent = parent;
}
public AbstractAdapterBase getParent() {
return parent;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setLabel(String label) {
this.label = label;
}
/**
* @return the name for the node.
*/
public String getLabel() {
return label;
}
/**
* The string to display in the tooltip for the form.
*/
public final String getTooltipText() {
if (tooltip == null)
return getTooltipTextInternal();
return tooltip;
}
public abstract String getTooltipTextInternal();
protected String getTooltipText(String string) {
String label = getLabel();
if (label == null) {
return new StringBuilder(Messages.AdapterBase_new_label).append(
string).toString();
}
return new StringBuilder(string).append(" ").append(label).toString(); //$NON-NLS-1$
}
public List<AbstractAdapterBase> getChildren() {
return children;
}
public AbstractAdapterBase getChild(Integer id) {
return getChild(id, false);
}
public AbstractAdapterBase getChild(Integer id, boolean reloadChildren) {
if (reloadChildren) {
loadChildren(false);
}
if (children.size() == 0)
return null;
for (AbstractAdapterBase child : children) {
if (child.getId().equals(id))
return child;
}
return null;
}
public void addChild(AbstractAdapterBase child) {
hasChildren = true;
AbstractAdapterBase existingNode = contains(child);
if (existingNode != null) {
// don't add - assume our model is up to date
return;
}
child.setParent(this);
children.add(child);
child.addListener(deltaListener);
}
public void insertAfter(AbstractAdapterBase existingNode,
AbstractAdapterBase newNode) {
int pos = children.indexOf(existingNode);
Assert.isTrue(pos >= 0,
"existing node not found: " + existingNode.getLabel()); //$NON-NLS-1$
newNode.setParent(this);
children.add(pos + 1, newNode);
newNode.addListener(deltaListener);
}
public void removeChild(AbstractAdapterBase item) {
removeChild(item, true, true);
}
public void removeChild(AbstractAdapterBase item, boolean nodeOnly) {
removeChild(item, true, nodeOnly);
}
public void removeChild(AbstractAdapterBase item, boolean closeForm,
boolean nodeOnly) {
if (children.size() == 0)
return;
AbstractAdapterBase itemToRemove = null;
for (AbstractAdapterBase child : children) {
if ((child.getId() == null && item.getId() == null)
|| ((child.getId() != null)
&& child.getId().equals(item.getId())
&& (child.getLabel() != null)
&& child.getLabel().equals(item.getLabel())))
itemToRemove = child;
}
if (itemToRemove != null) {
if (closeForm) {
closeEditor(new FormInput(itemToRemove));
}
children.remove(itemToRemove);
fireRemove(itemToRemove);
}
if (!nodeOnly)
// node might need to remove completely the information from inside
// (not only node child)
removeChildInternal(itemToRemove.getId());
}
@SuppressWarnings("unused")
protected void removeChildInternal(Integer id) {
// do mothing by default
}
public void removeAll() {
for (AbstractAdapterBase child : new ArrayList<AbstractAdapterBase>(
getChildren())) {
removeChild(child);
}
this.hasChildren = false;
}
public AbstractAdapterBase contains(AbstractAdapterBase item) {
if (children.size() == 0)
return null;
for (AbstractAdapterBase child : children) {
if ((child.getId() != null && child.getId().equals(item.getId()))
&& child.getLabel().equals(item.getLabel()))
return child;
}
return null;
}
public void setHasChildren(boolean hasChildren) {
this.hasChildren = hasChildren;
}
public boolean hasChildren() {
return hasChildren;
}
protected void executeDoubleClick() {
openViewForm();
}
public void performDoubleClick() {
if (isReadable()) executeDoubleClick();
}
public abstract void performExpand();
/**
* Called to load it's children;
*
* @param updateNode If not null, the node in the treeview to update.
*/
public abstract void loadChildren(boolean updateNode);
public abstract void popupMenu(TreeViewer tv, Tree tree, Menu menu);
protected void addEditMenu(Menu menu, String objectName) {
if (isEditable()) {
MenuItem mi = new MenuItem(menu, SWT.PUSH);
mi.setText(Messages.AdapterBase_edit_label + objectName);
mi.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
AbstractAdapterBase.this.openEntryForm();
}
});
}
}
protected void addViewMenu(Menu menu, String objectName) {
if (isReadable()) {
MenuItem mi = new MenuItem(menu, SWT.PUSH);
mi.setText(Messages.AdapterBase_view_label + objectName);
mi.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
AbstractAdapterBase.this.openViewForm();
}
});
}
}
protected void addDeleteMenu(Menu menu, String objectName) {
if (isDeletable()) {
MenuItem mi = new MenuItem(menu, SWT.PUSH);
mi.setText(Messages.AdapterBase_delete_label + objectName);
mi.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
deleteWithConfirm();
}
});
}
}
public void deleteWithConfirm() {
String msg = getConfirmDeleteMessage();
if (msg == null) {
throw new RuntimeException("adapter has no confirm delete msg: " //$NON-NLS-1$
+ getClass().getName());
}
boolean doDelete = true;
doDelete =
BgcPlugin.openConfirm(Messages.AdapterBase_confirm_delete_title,
msg);
if (doDelete) {
BusyIndicator.showWhile(Display.getDefault(), new Runnable() {
@Override
public void run() {
if (getId() == null) return;
// the order is very important
IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
IEditorPart part = page.findEditor(
new FormInput(AbstractAdapterBase.this));
getParent().removeChild(AbstractAdapterBase.this,
false, false);
try {
runDelete();
page.closeEditor(part, true);
} catch (ApplicationException e) {
if (e.getCause() instanceof javax.validation.ConstraintViolationException) {
List<String> msgs = BiobankFormBase
.getConstraintViolationsMsgs(
(ConstraintViolationException) e.getCause());
BgcPlugin.openAsyncError(
Messages.AdapterBase_delete_error_title,
StringUtils.join(msgs, "\n")); //$NON-NLS-1$
} else if (e.getCause() instanceof org.hibernate.exception.ConstraintViolationException) {
BgcPlugin.openAsyncError(
Messages.AdapterBase_delete_error_title,
"delete not allowed");
} else {
BgcPlugin.openAsyncError(
Messages.AdapterBase_delete_error_title,
e.getLocalizedMessage());
}
} catch (Exception e) {
BgcPlugin.openAsyncError(
Messages.AdapterBase_delete_error_title, e);
getParent().addChild(AbstractAdapterBase.this);
return;
}
getParent().rebuild();
getParent().notifyListeners();
notifyListeners();
additionalRefreshAfterDelete();
}
});
}
}
protected void runDelete() throws Exception {
BgcPlugin.openAsyncError("Programming Error",
"This adapter is missing its implementation for runDelete()");
}
/**
* Create a adequate child node for this node
*
* @param child the child model object
*/
protected abstract AbstractAdapterBase createChildNode();
protected AbstractAdapterBase createChildNode(int id) {
AbstractAdapterBase adapter = createChildNode();
adapter.setId(id);
return adapter;
}
/**
* Create a adequate child node for this node
*
* @param child the child model object
*/
protected abstract AbstractAdapterBase createChildNode(Object child);
/**
* get the list of this model object children that this node should have as
* children nodes.
*
* @throws Exception
*/
protected abstract Map<Integer, ?> getChildrenObjects() throws Exception;
public static boolean closeEditor(FormInput input) {
IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage();
IEditorPart part = page.findEditor(input);
if (part != null) {
return page.closeEditor(part, true);
}
return false;
}
public static IEditorPart openForm(FormInput input, String id) {
return openForm(input, id, true);
}
public static IEditorPart openForm(FormInput input, String id,
boolean focusOnEditor) {
closeEditor(input);
try {
IEditorPart part = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getActivePage()
.openEditor(input, id, focusOnEditor);
return part;
} catch (PartInitException e) {
LOGGER.error("Can't open form with id " + id, e); //$NON-NLS-1$
return null;
}
}
@SuppressWarnings("unchecked")
public <E> E getParentFromClass(Class<E> parentClass) {
AbstractAdapterBase node = this;
while (node != null) {
if (node.getClass().equals(parentClass)) {
return (E) node;
}
node = node.getParent();
}
return null;
}
public void openViewForm() {
if (getViewFormId() != null && isReadable) {
openForm(new FormInput(this), getViewFormId());
}
}
public IEditorPart openEntryForm() {
return openEntryForm(false);
}
public IEditorPart openEntryForm(boolean hasPreviousForm) {
return openForm(new FormInput(this, hasPreviousForm), getEntryFormId());
}
public abstract String getViewFormId();
public abstract String getEntryFormId();
public abstract List<AbstractAdapterBase> search(Class<?> searchedClass,
Integer objectId);
protected List<AbstractAdapterBase> searchChildren(Class<?> searchedClass,
Integer objectId) {
loadChildren(false);
List<AbstractAdapterBase> result = new ArrayList<AbstractAdapterBase>();
for (AbstractAdapterBase child : getChildren()) {
List<AbstractAdapterBase> tmpRes = child.search(searchedClass,
objectId);
if (tmpRes.size() > 0)
result.addAll(tmpRes);
}
return result;
}
protected List<AbstractAdapterBase> findChildFromClass(
Class<?> searchedClass, Integer objectId, Class<?>... clazzList) {
for (Class<?> clazz : clazzList) {
if (clazz.isAssignableFrom(searchedClass)) {
List<AbstractAdapterBase> res =
new ArrayList<AbstractAdapterBase>();
AbstractAdapterBase child = null;
// if (Date.class.isAssignableFrom(clazz))
// child = getChild((int) ((Date) searchedObject).getTime());
// else if (Integer.class.isAssignableFrom(clazz))
// child = getChild(((Integer) searchedObject).intValue());
// else
child = getChild(objectId, true);
if (child != null) {
res.add(child);
}
return res;
}
}
return searchChildren(searchedClass, objectId);
}
public void rebuild() {
removeAll();
loadChildren(false);
}
protected void additionalRefreshAfterDelete() {
// default does nothing
}
public boolean isDeletable() {
return isDeletable;
}
public boolean isEditable() {
return isEditable;
}
public boolean isReadable() {
return isReadable;
}
protected String getConfirmDeleteMessage() {
return null;
}
public void addListener(IDeltaListener listener) {
this.deltaListener = listener;
}
public void removeListener(IDeltaListener listener) {
if (this.deltaListener.equals(listener)) {
this.deltaListener = NullDeltaListener.getSoleInstance();
}
}
protected void fireAdd(Object added) {
deltaListener.add(new DeltaEvent(added));
}
protected void fireRemove(Object removed) {
deltaListener.remove(new DeltaEvent(removed));
}
public void addChangedListener(AdapterChangedListener listener) {
listeners.add(listener);
}
public void removeChangedListener(AdapterChangedListener listener) {
listeners.remove(listener);
}
public void notifyListeners(AdapterChangedEvent event) {
for (AdapterChangedListener listener : listeners) {
listener.changed(event);
}
}
public void notifyListeners() {
notifyListeners(new AdapterChangedEvent(this));
}
/**
* Used when searching inside the tree
*/
@Override
public boolean equals(Object o) {
boolean same = this == o;
if (!same && o instanceof AbstractAdapterBase) {
Class<?> class1 = getClass();
Class<?> class2 = o.getClass();
if (class1.equals(class2)) {
Integer id1 = getId();
Integer id2 = ((AbstractAdapterBase) o).getId();
return id1 != null && id2 != null && id1.equals(id2);
}
}
return same;
}
/**
* Used when searching inside the tree
*/
@Override
public int hashCode() {
if (id != null)
return id.hashCode();
return super.hashCode();
}
public abstract void setValue(Object value);
}