GEF + EMF: Why doesn't my editor remove the Figure for a removed object when refreshChildren() is called? - eclipse

I have implemented a GEF editor for a graph-like EMF model, with a remove command for a certain type of node in the graph. I think I've done all the necessary steps in order to make this set up work (vainolo's blog has been a great help).
However, when I'm deleting a model element, the view doesn't get refreshed, i.e., the figure for the model element isn't removed from the editor view, and I have no idea why. I'd be extremely grateful if somebody could have a look at my sources and point me to any problems (and possibly solutions :)). Many thanks in advance!
Below are what I think are the important classes for this issue. Please do let me know should I add further code/edit the code, etc. (I've left out code that I thought doesn't help, e.g., getters and setters, class variables). Thanks!
DiagramEditPart
public class DiagramEditPart extends AbstractGraphicalEditPart {
public DiagramEditPart(Diagram model) {
this.setModel(model);
adapter = new DiagramAdapter();
}
#Override protected IFigure createFigure() {
Figure figure = new FreeformLayer();
return figure;
}
#Override protected void createEditPolicies() {
installEditPolicy(EditPolicy.LAYOUT_ROLE, new DiagramXYLayoutPolicy());
}
#Override protected List<EObject> getModelChildren() {
List<EObject> allModelObjects = new ArrayList<EObject>();
if (((Diagram) getModel()).getMyNodes() != null)
allModelObjects.addAll(((Diagram) getModel()).getMyNodes());
return allModelObjects;
}
#Override public void activate() {
if(!isActive()) {
((Diagram) getModel()).eAdapters().add(adapter);
}
super.activate();
}
#Override public void deactivate() {
if(isActive()) {
((Diagram) getModel()).eAdapters().remove(adapter);
}
super.deactivate();
}
public class DiagramAdapter implements Adapter {
#Override public void notifyChanged(Notification notification) {
switch (notification.getEventType()) {
case Notification.REMOVE: refreshChildren();
break;
default:
break;
}
}
#Override public Notifier getTarget() {
return (Diagram) getModel();
}
#Override public void setTarget(Notifier newTarget) {
// Do nothing.
}
#Override public boolean isAdapterForType(Object type) {
return type.equals(Diagram.class);
}
}
}
MyNodeEditPart
public class MyNodeEditPart extends AbstractGraphicalEditPart {
public MyNodeEditPart(MyNode model) {
this.setModel(model);
adapter = new MyNodeAdapter();
}
#Override protected IFigure createFigure() {
return new MyNodeFigure();
}
#Override protected void createEditPolicies() {
installEditPolicy(EditPolicy.COMPONENT_ROLE, new MyNodeComponentEditPolicy());
}
#Override protected void refreshVisuals() {
MyNodeFigure figure = (MyNodeFigure) getFigure();
DiagramEditPart parent = (DiagramEditPart) getParent();
Dimension labelSize = figure.getLabel().getPreferredSize();
Rectangle layout = new Rectangle((getParent().getChildren().indexOf(this) * 50),
(getParent().getChildren().indexOf(this) * 50), (labelSize.width + 20),
(labelSize.height + 20));
parent.setLayoutConstraint(this, figure, layout);
}
public List<Edge> getModelSourceConnections() {
if ((MyNode) getModel() != null && ((MyNode) getModel()).getDiagram() != null) {
ArrayList<Edge> sourceConnections = new ArrayList<Edge>();
for (Edge edge : ((MyNode) getModel()).getDiagram().getOutEdges(((MyNode) getModel()).getId())) {
sourceConnections.add(edge);
}
return sourceConnections;
}
return null;
}
// + the same method for targetconnections
#Override public void activate() {
if (!isActive()) {
((MyNode) getModel()).eAdapters().add(adapter);
}
super.activate();
}
#Override public void deactivate() {
if (isActive()) {
((MyNode) getModel()).eAdapters().remove(adapter);
}
super.deactivate();
}
public class MyNodeAdapter implements Adapter {
#Override
public void notifyChanged(Notification notification) {
refreshVisuals();
}
#Override
public Notifier getTarget() {
return (MyNode) getModel();
}
#Override
public void setTarget(Notifier newTarget) {
// Do nothing
}
#Override
public boolean isAdapterForType(Object type) {
return type.equals(MyNode.class);
}
}
}
MyNodeComponentEditPolicy
public class MyNodeComponentEditPolicy extends ComponentEditPolicy {
#Override
protected Command createDeleteCommand(GroupRequest deleteRequest) {
DeleteMyNodeCommand nodeDeleteCommand = new DeleteMyNodeCommand((MyNode) getHost().getModel());
return nodeDeleteCommand;
}
}
DeleteMyNodeCommand
public class DeleteMyNodeCommand extends Command {
public DeleteMyNodeCommand(MyNode model) {
this.node = model;
this.graph = node.getDiagram();
}
#Override public void execute() {
getMyNode().setDiagram(null);
System.out.println("Is the model still present in the graph? " + getGraph().getMyNodes().contains(getMyNode()));
// Returns false, i.e., graph doesn't contain model object at this point!
}
#Override public void undo() {
getMyNode().setDiagram(getGraph());
}
}
EDIT
Re execc's comment: Yes, refreshChildren() is being called. I've tested this by overriding it and adding a simple System.err line, which is being displayed on the console on deletion of a node:
#Override
public void refreshChildren() {
super.refreshChildren();
System.err.println("refreshChildren() IS being called!");
}
EDIT 2
The funny (well...) thing is, when I close the editor and persist the model, then re-open the same file, the node isn't painted anymore, and is not present in the model. But what does this mean? Am I working on a stale model? Or is refreshing/getting the model children not working properly?
EDIT 3
I've just found a peculiar thing, which might explain the isues I have? In the getModelChildren() method I call allModelObjects.addAll(((Diagram) getModel()).getMyNodes());, and getMyNodes() returns an unmodifiable EList. I found out when I tried to do something along the lines of ((Diagram) getModel()).getMyNodes().remove(getMyNode()) in the delete command, and it threw an UnsupportedOperationException... Hm.
EDIT 4
Er, somebody kill me please?
I've double-checked whether I'm handling the same Diagram object at all times, and while doing this I stumbled across a very embarassing thing:
The getModelChildren() method in DiagramEditPart in the last version read approx. like this:
#Override protected List<EObject> getModelChildren() {
List<EObject> allModelObjects = new ArrayList<EObject>();
EList<MyNode> nodes = ((Diagram) getModel()).getMyNodes();
for (MyNode node : nodes) {
if (node.getDiagram() != null); // ### D'Uh! ###
allModelObjects.add(node);
}
return allModelObjects;
}
I'd like to apologize for stealing everyone's time! Your suggestions were very helpful, and indeed helped my to finally track down the bug!
I've also learned a number of lessons, amongst them: Always paste the original code, over-simplifaction may cloak your bugs! And I've learned a lot about EMF, Adapter, and GEF. Still:

There is one semi-colon too many in line 5 of the following part of the code, namely after the if statement: if (node.getDiagram() != null);:
1 #Override protected List<EObject> getModelChildren() {
2 List<EObject> allModelObjects = new ArrayList<EObject>();
3 EList<MyNode> nodes = ((Diagram) getModel()).getMyNodes();
4 for (MyNode node : nodes) {
5 if (node.getDiagram() != null);
6 allModelObjects.add(node);
7 }
8 return allModelObjects;
9 }

Related

Eclipse EditorPart save on partDeactivated

My problem is that I have a custom application, using EditorParts, which are persisted to a database. The user can open several Editors, and switch between them. I need to ask the user to save any unsaved changes in an Editor, before switching to the next Editor (or else close it).
I have created an IPartListener2, and I receive the partDeactivated notification. If isDirty()==true, I bring up a MessageDialog asking to save or not; because I want to call editor.doSave().
My problem is that does not work. I never see the MessageDialog, because another partDeactivated fires. I guess, this is caused by the MessageDialog over the Editor.
I have researched How to listen to lose focus event of a part in Eclipse E4 RCP?, but that did not help me.
thanks to help a e4 beginner
public class DatasetAttachmentEditor {
... // code here
#Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
... // code here
site.getPage().addPartListener(new EditorsPartListener(this));
}
}
public class EditorsPartListener implements IPartListener2 {
private IEditorPart editor;
public EditorsPartListener(IEditorPart editor) {
this.editor = editor;
}
#Override
public void partClosed(IWorkbenchPartReference partRef) {
if (partRef.getPage().getActiveEditor().getClass().getName().equals(editor.getClass().getName())) {
partRef.getPage().removePartListener(this);
}
}
#Override
public void partDeactivated(IWorkbenchPartReference partRef) {
if (!partRef.getClass().getName().equals("org.eclipse.ui.internal.EditorReference")) {
System.out.println("partDeactivated: not a Editor="+partRef.getClass().getName());
return;
}
if (!editor.isDirty()) {
// if the editor is not dirty - do nothing
return;
}
// ask if to save
int choice = EditorPartSaveDialog(partRef.getPage().getActiveEditor());
if(choice == MessageDialog.OK) {
// save the Editor
try {
ProgressMonitorDialog progress = new ProgressMonitorDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell());
progress.setCancelable(false);
progress.run(false, false, new IRunnableWithProgress() {
#Override
public void run(IProgressMonitor monitor) {
// do the save
editor.doSave(monitor);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
else {
// don't save: just close it
partRef.getPage().closeEditor(editor, false);
}
}
#Override
public void partActivated(IWorkbenchPartReference partRef) {
}
#Override
public void partBroughtToTop(IWorkbenchPartReference partRef) {
}
#Override
public void partOpened(IWorkbenchPartReference partRef) {
}
#Override
public void partHidden(IWorkbenchPartReference partRef) {
}
#Override
public void partVisible(IWorkbenchPartReference partRef) {
}
#Override
public void partInputChanged(IWorkbenchPartReference partRef) {
}
/**
* Asks the user to Save changes
* #param editor
* #return MessageDialog.OK to save, MessageDialog.CANCEL otherwise
*/
private int EditorPartSaveDialog(IEditorPart editor) {
// If save confirmation is required ..
String message = NLS.bind("''{0}'' has been modified. Save changes?", LegacyActionTools.escapeMnemonics(editor.getTitle()));
// Show a dialog.
MessageDialog d = new MessageDialog(
Display.getCurrent().getActiveShell(),
"Save Editor", null, message,
MessageDialog.QUESTION,
0,
"Save",// MessageDialog 0x0 (OK)
"Don't Save: close"// MessageDialog 0x1 (CANCEL)
)
return d.open();
}
}
You probably need to run your code after the deactivate event has finished. You can do this using Display.asyncExec.
Something like:
#Override
public void partDeactivated(IWorkbenchPartReference partRef) {
if (!partRef.getClass().getName().equals("org.eclipse.ui.internal.EditorReference")) {
System.out.println("partDeactivated: not a Editor="+partRef.getClass().getName());
return;
}
if (!editor.isDirty()) {
// if the editor is not dirty - do nothing
return;
}
Display.getDefault().asyncExec(() ->
{
// TODO the rest of your deactivate code goes here
});
}
(Above code assumes you are using Java 8 or later)
This is 3.x compatibility mode code, not e4.
I have found a great solution, using the suggestions above and Enumerating all my Eclipse editors?
I am checking all editors first, then all persisted editors - skipping itself and the persisted objects.
Thanks for your comments!
public class ConceptAcronymValidator implements IValidator {
private ConceptInstanceEditor myEditor;
public ConceptAcronymValidator(ConceptInstanceEditor editor) {
super();
this.myEditor = editor;
}
#Override
public IStatus validate(Object value) {
// check all Editors
for (IEditorReference editorRef: PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getEditorReferences()) {
IEditorPart editor = editorRef.getEditor(false);
if (editor != null) {
// don't check our own Editor
if (!editor.equals(myEditor)) {
ConceptInstanceEditor conceptEditor = (ConceptInstanceEditor)editor;
if (conceptEditor.getTxtAcronym().equals(value.toString())) {
return ValidationStatus.error("This Concept is already used by Editor <"+
conceptEditor.getConceptModel().getName().getValue(MultilingualString.EN)+
">");
}
}
}
}
// check all persisted Concepts
List<Concept> concepts = ReferenceServiceFactory.getService().getConcepts();
for (Concept concept: concepts) {
Concept myConcept = (Concept) myEditor.getConceptModel().getInstance();
// check if a new Editor
if (myConcept == null) {
if (concept.getAcronym().equals(value.toString())) {
return ValidationStatus.error("This Concept is already used by <"+
concept.getName().getValue(MultilingualString.EN)+
">");
}
}
else {
// don't check own Instance
if (!concept.equals(myConcept)) {
if (concept.getAcronym().equals(value.toString())) {
return ValidationStatus.error("This Concept is already used by <"+
concept.getName().getValue(MultilingualString.EN)+
">");
}
}
}
}
return Status.OK_STATUS;
}
}

RecyclerView.Adapter notifyDataSetChanged not working with AsyncTask Callback

I am sure it's just a simple fault, but I'm not able to solve it.
My RecyclerView.Adapter loads its data with help of an AsyncTask (LoadAllPersonsFromDb) out of a SQLite DB. The response is handled by a callback interface (ILoadPersonFromDb.onFindAll).
Here is the code of the Adapter:
public class ListViewAdapter extends RecyclerView.Adapter<ListViewViewholder> implements LoadAllPersonsFromDb.ILoadPersonFromDb {
private int layout;
private List<Person> persons;
private Context context;
private AdapterDataSetListener adapterDataSetListener;
public ListViewAdapter(int layout, Context context,
AdapterDataSetListener adapterDataSetListener) {
this.layout = layout;
persons = new ArrayList<>();
this.context = context;
this.adapterDataSetListener = adapterDataSetListener;
new LoadAllPersonsFromDb(context, this).execute();
}
#Override
public ListViewViewholder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false);
return new ListViewViewholder(view, context);
}
#Override
public void onBindViewHolder(ListViewViewholder holder, int position) {
holder.assignData(persons.get(position));
}
#Override
public int getItemCount() {
return persons.size();
}
#Override
public void onFindAll(List<Person> persons) {
Log.d("LISTVIEW", "Counted: " + persons.size() + " elements in db");
if (this.persons != null) {
this.persons.clear();
this.persons.addAll(persons);
} else {
this.persons = persons;
}
adapterDataSetListener.onChangeDataSet();
//notifyDataSetChanged();
}
public interface AdapterDataSetListener {
void onChangeDataSet();
}
}
As you can see, I tried more than one way to get it running. The simple notifyDataSetChanged did not do anything, so I made another interface which is used to delegate the ui information to the relating fragment. Following code documents this interface which is implemented in the relating fragment:
#Override
public void onChangeDataSet() {
Log.d("Callback", "called");
listViewAdapter.notifyDataSetChanged();
/*
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
listViewAdapter.notifyDataSetChanged();
}
});
*/
}
Here I also tried to put it on the MainUiThread but nothing works. I'm just not able to see where my problem is. Hopefully any of you guys can give me a hint.
The logging works, which is the prove for the working callbacks.
Thank you in advance.
PS: If you need any more code, just tell me and I will provide it.
instead of using the interface-llistener pattern, try this
#Override
public void onFindAll(List<Person> persons) {
Log.d("LISTVIEW", "Counted: " + persons.size() + " elements in db");
if (this.persons != null) {
this.persons.clear();
this.persons.addAll(persons);
} else {
this.persons = persons;
}
refereshAdapter(persons);
}
public void refereshAdapter(List<Person> persons){
listViewAdapter.clear();
listViewAdapter.addAll(persons);
listViewAdapter.notifyDataSetChanged();
}
To tell the background, I used RecyclerView in Version 23.1.1 because the latest 23.2.0 had some weird behaviour in holding a huge space for each card.
//Update: the problem with the space between cards, was because of a failure of myself in the layout file (match_parent instead of wrap_content). -_-
The upshot was using the latest version again and everything worked just fine. I have no idea why, but at the moment I am just happy, that I can go on. This little problem wasted enough time.
Maybe somebody has a similar situation and can use this insight.
Thx anyway #yUdoDis.

When is it necessary to check if a subscriber is subscribed prior to calling onNext() and onError()?

Consider the following example, it creates an Observable that wraps another API that produces Widgets
public Observable<Widget> createWidgetObservable() {
return Observable.create(new Observable.OnSubscribe<Widget>() {
#Override
public void call(final Subscriber<? super Widget> subscriber) {
WidgetCreator widgetCreator = new WidgetCreator();
widgetCreator.setWidgetCreatorObserver(new WidgetCreator.WidgetCreatorObserver() {
#Override
public void onWidgetCreated(Widget widget) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(widget);
}
}
#Override
public void onWidgetError(Throwable e) {
if (!subscriber.isUnsubscribed()) {
subscriber.onError(e);
}
}
});
}
});
}
Are the subscriber.isUnsubscribed() checks necessary prior to calling subscriber.onNext() and subscriber.onError()?
If so, are the checks always necessary or does it depend on the composition / subscriber that's using the observable?
Is it best practice to include the checks?
You can use them to narrow the window between an emission and an unsubscription but if you don't have loops, it is unnecessary most of the time. The more important thing is that if an unsubscription happen, you'd have to "unset" the WidgetCreatorObserver otherwise it will keep receiving and dropping data and keeping alive every reference it may hold.
WidgetCreator widgetCreator = new WidgetCreator();
WidgetCreator.WidgetCreatorObserver wo = new WidgetCreator.WidgetCreatorObserver() {
#Override
public void onWidgetCreated(Widget widget) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(widget);
}
}
#Override
public void onWidgetError(Throwable e) {
if (!subscriber.isUnsubscribed()) {
subscriber.onError(e);
}
}
}
widgetCreator.setWidgetCreatorObserver(wo);
wo.add(Subscriptions.create(() -> widgetCreator.removeWidgetCreatorObserver(wo)));

Update ListView via AsyncTask or IntentService

I am trying to Update my Custom ListView which is fed by two String Arrays:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getStringArray(ARG_PARAM1);
mParam2 = getArguments().getStringArray(ARG_PARAM2);
}
setupListView();
}
private void setupListView() {
listItemList = new ArrayList();
if (mParam1 != null && mParam2 != null && mParam1.length == mParam2.length) {
for (int i = 0; i < mParam1.length; i++) {
listItemList.add(new MyListItem(mParam1[i], (mParam2[i]).substring(0, 75) + "..."));
}
} else {
listItemList.add(new MyListItem("Loading...", "Swipe Down for Update"));
}
mAdapter = new MyListAdapter(getActivity(), listItemList);
}
mParam1 and mParam2 are Values which are fetched by an XML parser (IntentService) class in the MainActivity which i can show if needed.
Now, if i am to fast, and the mPara1 and mPara2 is empty there won´t be any ListView shown. Now i want to solve this by some AsyncTask or IntentService whatever is useful. I tried AsyncTask, which didn´t work at all. I tried notifyDataSetChanged() which didn´t work too...
Now, how could i solve this....
Using AsyncTask i have the problem that i don´t know how to passt the two Arrays to publishProgress() correctly
THis is how my AsyncTask looks like:
class UpdateListView extends AsyncTask<Void, String, Void> {
private MyListAdapter adapter;
private ArrayList listItemList;
#Override
protected void onPreExecute() {
adapter = (MyListAdapter) mListView.getAdapter();
}
#Override
protected Void doInBackground(Void... params) {
for (String item1 : mParam1) {
publishProgress(item1);
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
adapter.add(new MyListItem(values[0], values[1]));
}
#Override
protected void onPostExecute(Void result) {
Log.d("onPostExecute", "Added successfully");
}
}
Okay solved it...My Fragments are running in same Activity where the Data is loaded in, so i just created getter and setter in MainActivity and access them in the needed Fragment via
String[] titles =(MainActivity) getActivity()).getTitlesArray();
String[] text=(MainActivity) getActivity()).getTextArray();
Whatever i do trying setting Bundle with
bundle.putStringArray(TITLES,titles);
doesn´t work. Should work using parceable/serializable class but didn´t try...

How to remove "no data" labels from empty nodes in GWT?

I have a widget that inherits from CellTree. If the node not have the child elements, this node can be opened and shows "no data" label.
I'd like to see nodes without child's displayed as empty.
That's how I fill the tree. My DictionaryTreeDataProvider class (relevant part):
public class DictionaryTreeDataProvider extends ListDataProvider<MValue> {
private final DictionariesServiceAsync service = GWT.create(DictionariesService.class);
...
#Override
public void onRangeChanged(HasData<MValue> result) {
service.queryDictionaryValues(range, query, new AsyncCallback<SubsetResult<MValue>>() {
#Override
public void onFailure(Throwable t) {
}
#Override
public void onSuccess(SubsetResult<MValue> result) {
getList().clear();
for (MValue value : result.items) {
getList().add(value);
}
}
});
}
}
On the server side I make EJB call which fills SubsetResult.
I found that this problem fixed in version of GWT-2.5.0-rc2 (see https://groups.google.com/forum/#!topic/google-web-toolkit/d-rFUmyHTT4).
Now everything is OK, thanks to #moutellou.
I did as he suggested:
...
#Override
public void onSuccess(SubsetResult<MValue> result) {
if (result.length == 0) {
updateRowCount(-1, true);
return;
} else {
for (MValue value : result.items) {
// some checks here
getList().add(value);
}
}
}
...
Some alternative solution. Can be defined interface that extends the interface CellTree.Resources.
In this interface must specify the path to the CSS, which override the desired style.
Interface CellTree.Resources:
public class CellTree extends AbstractCellTree implements HasAnimation,
Focusable {
...
/**
* A ClientBundle that provides images for this widget.
*/
public interface Resources extends ClientBundle {
/**
* An image indicating a closed branch.
*/
#ImageOptions(flipRtl = true)
#Source("cellTreeClosedArrow.png")
ImageResource cellTreeClosedItem();
/**
* An image indicating that a node is loading.
*/
#ImageOptions(flipRtl = true)
ImageResource cellTreeLoading();
/**
* An image indicating an open branch.
*/
#ImageOptions(flipRtl = true)
#Source("cellTreeOpenArrow.png")
ImageResource cellTreeOpenItem();
/**
* The background used for selected items.
*/
#ImageOptions(repeatStyle = RepeatStyle.Horizontal, flipRtl = true)
ImageResource cellTreeSelectedBackground();
/**
* The styles used in this widget.
*/
#Source(Style.DEFAULT_CSS)
Style cellTreeStyle();
}
...
}
Interface CustomCellTreeResources, based on CellTree.Resources:
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.user.cellview.client.CellTree;
public interface CustomCellTreeResources extends CellTree.Resources {
static final String STYLE_PATH = "components/common/client/static/custom-cell-tree.css";
#Override
#ClientBundle.Source({CellTree.Style.DEFAULT_CSS, STYLE_PATH})
CellTree.Style cellTreeStyle();
}
Overriding rule:
.cellTreeEmptyMessage {
display: none;
}
Create an instance:
private final static CellTree.Resources customCellTreeResources =
GWT.create(CustomCellTreeResources.class);
And next need to explicitly pass customCellTreeResources to the CellTree class constructor.
Message is not displayed more.
Mandatory: before filing the list, ie, before clicking on a node, the list should be cleaned( getList().clear();):
#Override
public void onRangeChanged(HasData<MValue> result) {
service.queryDictionaryValues(range, query,
new AsyncCallback<SubsetResult<MValue>>() {
#Override
public void onFailure(Throwable t) {}
#Override
public void onSuccess(SubsetResult<MValue> result) {
getList().clear();
for (MValue value : result.items) {
getList().add(value);
}
}
});
}
This is how I removed the no data label in my DataProvider
//Fetch children
int size = children.size();
if (size == 0) {
updateRowCount(-1, true); //Method called on AsyncDataProvider
return;
}
In the TreeViewModel, make sure that the isLeaf method returns true if the argument value has no children. Example:
#Override
public boolean isLeaf(Object value) {
if (value instanceof DepartmentDto) {
DepartmentDto department = (DepartmentDto) value;
return department.getEmployees().isEmpty();
} else if (value instanceof EmployeeDto) {
return true;
} else {
return false;
}
}
In this case, a department should declare itself as a leaf only if it has no employees, an employee will declare itself as a leaf, and default to false.
Note that value many also be an internal GWT node. In this example, it might not necessarily be just DepartmentDto and EmployeeDto.