Bug 2741 - [JFace] TableViewer: MapElement problem (1GITISX)
Summary: [JFace] TableViewer: MapElement problem (1GITISX)
Status: RESOLVED FIXED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 2.0   Edit
Hardware: All Windows NT
: P1 normal (vote)
Target Milestone: ---   Edit
Assignee: Nick Edgar CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2001-10-10 22:42 EDT by Martin Aeschlimann CLA
Modified: 2002-01-07 10:58 EST (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Martin Aeschlimann CLA 2001-10-10 22:42:38 EDT
MA (21.08.2001 19:00:55)
	I was setting the input of a tableviewer two times with the same input.
	The first time, mapElement was called for each element, the second time for none of them.
	
	'setInput()' always unmaps all elements. So after the second setInput the map is empty.

	I think this is the problematic code: (StructuredViewer)

	protected void associate(Object element, Item item) {
		Object data = item.getData();
		if (data == element)
			return;											<< returns without mapElement
		if (data != null)
			disassociate(item);
		item.setData(element);
		mapElement(element, item);
	}


NOTES:

MA (28.08.2001 20:32:18)
	Other experiment:
		1. Open the package viewer, create java project proj, package 'pack' and CU 'A.java'
		2. set breakpoints on mapElement and unmapElement
		3. create a cu pack.B.java
 		4. Even A.java stays the same, is is mappes and unmapped several times.
		5. set breakpoints on assiciate and disassociate
		6. A.java is disassociated and assiciated again (several times)
Comment 1 DJ Houghton CLA 2001-10-29 19:13:23 EST
PRODUCT VERSION:
	129

Comment 2 Martin Aeschlimann CLA 2001-12-19 05:45:53 EST
another problem is in disassociate
    protected void disassociate(Item item) {
	Object element = item.getData();
	Assert.isNotNull(element);
	item.setData(null);
	if (elementMap != null && findItem(element) == item)
		unmapElement(element); 
    }
It would be better if disassociate does not check 'elementMap' to be non-null.
Comment 3 Nick Edgar CLA 2001-12-19 11:47:46 EST
Note that this check was recently changed to elementMap.get(element) != null 
rather than calling findItem, to avoid the extra work it does for checking the 
input element.

Would it be better if it called usingElementMap() instead?  We would still need 
to access the map directly to get the item.  Note that this is done in other 
places as well, e.g. findItem.

Why do you want this change?
Comment 4 Martin Aeschlimann CLA 2001-12-19 12:34:28 EST
I would just overwrite mapElement & unmapElement, but don't have the enable the 
HashLookup.
Comment 5 Nick Edgar CLA 2001-12-19 12:37:37 EST
To make this change, I would have to add a new method unmapElement(Object 
element, Item item) so it can do the check currently in disassociate.
It could call unmapElement(Object) for backwards compatibility.
Comment 6 Nick Edgar CLA 2001-12-21 14:59:46 EST
Regarding the original bug re unmapAllElements(), it would be nice to avoid 
having to do extra work in associate to cover for the case where there is an 
item->element mapping but not an element->item mapping.

The simplest approach would be to simply null out the data fields of the items 
when the map is cleared, in unmapAllElements().
Unfortunately, this won't work since disassociate() asserts that the data is 
not null (and I'd like to leave this assert in).  It also makes unmapAllElements
() inconsistent with unmapElement(), which does not do this.

Another approach is to remove all items in TableViewer.inputChanged, so that 
there is no inconsistency.  However, this is expensive.  It is much more costly 
to delete and re-add table items than just updating their labels.
This would be a big hit for large tables (e.g. the task list).

The best approach would be to remove the call to unmapElements() from setInput
() and only do it in the inputChanged method of subclasses where it makes sense 
(it does for tree viewers, but not for TableViewer).
Unfortunately, we can't do this since this would be a breaking API change.

So for now, I will change associate to always do a mapElement.
The cost is an extra table lookup (when the element map is enabled), which is 
much cheaper than item creation/deletion.

I will also make the change to unmapElement proposed in the previous comment, 
which will let you simply override mapElement(Object, Item) and unmapElement
(Object, Item) (override this one rather than unmapElement(Object)).
Note that you'll likely get more mapElement calls than unmapElement calls due 
to the change to associate.

Could you try out the following changes to StructuredViewer?  Although the 
tests pass, and Eclipse runs, I am reluctant to release them just before going 
away for the holidays.
You may require some further changes too.  Please let me know.

/**
 * Removes the given association from the internal element to widget map.
 * Does nothing if mapping is disabled, or if the given element does not map
 * to the given item.
 * <p>
 * This method is internal to the framework; subclassers should not call
 * this method.
 * </p>
 *
 * @param element the element
 * @since 2.0
 */
protected void unmapElement(Object element, Widget item) {
	// double-check that the element actually maps to the given item before 
unmapping it
	if (elementMap != null && elementMap.get(element) == item) {
		// call unmapElement for backwards compatibility
		unmapElement(element);
	}
}


protected void disassociate(Item item) {
	Object element = item.getData();
	Assert.isNotNull(element);
	item.setData(null);
	unmapElement(element, item);
}

protected void associate(Object element, Item item) {
	Object data = item.getData();
	if (data != element) {
		if (data != null)
			disassociate(item);
		item.setData(element);
	}
	// Always map the element, even if data == element,
	// since unmapAllElements() can leave the map inconsistent
	// See bug 2741 for details.
	mapElement(element, item);
}
Comment 7 Nick Edgar CLA 2001-12-21 15:08:53 EST
Also need to change AbstractTreeViewer.doUpdateItem to call unmapElement(data, 
item) instead of unmapElement(data).
Comment 8 Nick Edgar CLA 2001-12-21 15:37:29 EST
As for the extra work being done as per your note MA (28.08.2001 20:32:18), 
this is due to 2 things:
1. When multiple items are affected on a package fragment, 
JavaElementContentProvider does a full refresh on the fragment.  This occurs in 
this case because both a .java and a .class are being added with autobuild on.  
Note that this is expensive, and may cause performance problems for large 
packages.
2. Refresh in the tree viewer is suboptimal and can lead to added items, or 
items whose corresponding element has changed, having their labels set twice.  
This is a known problem which we plan to improve.
Comment 9 Martin Aeschlimann CLA 2002-01-03 08:16:34 EST
I tested your implementation and it seems to work!

The behaviour mentioned (unnecessary refreshes on the package viever) is bug 
4138.
Comment 10 Nick Edgar CLA 2002-01-07 10:58:52 EST
Changes released.
Closing PR.