PDA

View Full Version : [FIXED] URGENT BUG : overriding hashCode() in BaseModelData



seb2nim
10 Sep 2008, 3:35 AM
I'm getting an headache trying to understand why i cannot add a child item to a parent item in a tree (by letting treeStore monitorChanges or manually) after modifying some property on this parent node.

The reason is findWrapper() method in TreeStore cannot find wrapper for my parent model object.

protected TreeModel findWrapper(M item) {
return modelMap.get(item);
}The underlying reason is that : (from BaseModelData)

@Override
public int hashCode() {
if (map != null) {
return map.hashCode();
}
return super.hashCode();
}=> hashCode result varies in time for a given object as it uses the map's hashCode (RpcMap delegation java.util.Map) :

from javadoc :


Returns the hash code value for this map. The hash code of a map is defined to be the sum of the hash codes of each entry in the map's <tt>entrySet()</tt> view. This ensures that <tt>m1.equals(m2)</tt> implies that <tt>m1.hashCode()==m2.hashCode()</tt> for any two maps <tt>m1</tt> and <tt>m2</tt>, as required by the general contract of {@link Object#hashCode}.Workaround : override hashCode in your Model object

darrellmeyer
11 Sep 2008, 9:29 AM
Can you provide some simple test code that illustrates the issue you are having?

seb2nim
11 Sep 2008, 1:38 PM
Hello Darell.
Here is the sample :
I didnt test it as a single module (via onModuleLoad) but in place of the TreePage in explorer demo. I tested with 1.0.1 (Gwt 1.5.1) and it works. Seems your hashCode modif is between 1.0.1 and 1.0.4 no ?

To test :
Add or Remove child via buttons -> It works.
Then click 'Add property to root' => it alters the parent node model.
Then, continue to try to add children => broken.By the way, i posted another thread asking how i can acheive tree update as i alter model in a way that implies sorting resfresh... If you could take an eye on it that would be generous.

For this issue, i'm really surprissed on the choice of Sun for implementing hashCode for HashMap such a way.

For your implementation, i'm not agree with the fact you override equals() in BaseModel based on values in the map neitheir. If you take a look on Map equals method, you 'll see its a comparison on keys...



/*
* Ext GWT - Ext for GWT
* Copyright(c) 2007, 2008, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.samples.explorer.client.pages;

import com.extjs.gxt.ui.client.binder.TreeBinder;
import com.extjs.gxt.ui.client.data.BaseTreeModel;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.data.TreeModel;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.store.TreeStore;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.VerticalPanel;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.button.ButtonBar;
import com.extjs.gxt.ui.client.widget.layout.FlowLayout;
import com.extjs.gxt.ui.client.widget.tree.Tree;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.RootPanel;

public class TreePage extends LayoutContainer implements EntryPoint {

public void onModuleLoad() {
RootPanel.get().add(this);
}

private int itemCount = 0;

private MyTreeElement rootItem;

@Override
protected void onRender(Element parent, int pos) {
super.onRender(parent, pos);

// Tree
final Tree tree = new Tree();
tree.setItemIconStyle("icon-music");

// TreeStore wich monitor changes
TreeStore store = new TreeStore();
store.setMonitorChanges(true);

// A rootItem
rootItem = createTreeElement();

// Some children
for (int i = 1; i <= 5; i++) {
rootItem.add(createTreeElement());
}

// Added to the store
store.add(rootItem, true);

// Now Treebinder
TreeBinder binder = new TreeBinder<ModelData>(tree, store);
binder.setDisplayProperty("name");
binder.init();

// Test buttonbart
ButtonBar buttonBar = new ButtonBar();

buttonBar.add(new Button("Add child", new SelectionListener<ComponentEvent>() {
public void componentSelected(ComponentEvent ce) {
rootItem.add(createTreeElement());
}
}));

buttonBar.add(new Button("Remove last child", new SelectionListener<ComponentEvent>() {
public void componentSelected(ComponentEvent ce) {
if (rootItem.getChildCount() != 0) {
rootItem.remove(rootItem.getChildCount() - 1);
}
}
}));

buttonBar.add(new Button("Add property to root", new SelectionListener<ComponentEvent>() {
public void componentSelected(ComponentEvent ce) {
// Now modify one ModelData property.
// For this test, i append an arbitrary property. this will
// cause underlying HashMap to have a different hashCode and so,
// TreeStore could not find wrapper for this model
rootItem.set("prop_" + itemCount, "myData");

// In fact, hashCode() implementation for HashMap is sum of
// VALUES hashCode()s. So simply modifying value of model
// property (another String, etc) will produce same issue

}
}));

VerticalPanel vp = new VerticalPanel();
vp.setSpacing(10);

vp.add(buttonBar);
vp.add(tree);

setLayout(new FlowLayout());
add(vp);
}

private MyTreeElement createTreeElement() {
return new MyTreeElement("Element_" + (itemCount++));
}

private class MyTreeElement extends BaseTreeModel {

public MyTreeElement(String name) {
set("name", name);
}

public void add(TreeModel child) {
super.add(child);
}
}

}

darrellmeyer
15 Sep 2008, 6:49 AM
equals and hashCode are no longer overridden in BaseModelData. Change is in SVN.