Wednesday, March 25, 2009

Problem Solved

In my last post I talked about my initial fix for Bug 209289 which I thought was a cheap fix since it didn't get to the real root of the problem which was solving why the refresh() method needed to run through every time in order for the delete button to be disabled. Why didn't the state of the button persist even if the refresh() method wasn't called? Clearly there was another piece of code modifying the button's state.

I tried debugging for awhile, running multiple stack traces to try to find the point where the button's state was being changed, with no luck. Along the way though, I came to understand the code more and more.

It hit me that AbstractExtensionsSection.java was a subclass of AbstractSection.java, so I decided to try finding something in that file. Unfortunately I found nothing, but I realized that the name "AbstractExtensionsSection" implies that there is probably a class that subclasses it. I ran a search and I came back upon W11ExtensionsSection.java, the file I originally referenced in my January 29th post and subsequently dismissed on my February 24th post because it only called the setEnabled() method on addButton and removeButton. Originally I figured that this couldn't have been where the problem was, that it had to be wherever the button was created and it's state was set. However now I had a better understanding of the code.

Looking at the method that the code was inside:


public void setInput(IWorkbenchPart part, ISelection selection)
{
super.setInput(part, selection);
isReadOnly = true;
ExtensibleElement extensibleElement = getExtensibleElement(input);
if (extensibleElement != null)
{
Element element = extensibleElement.getElement();
if (element instanceof ElementImpl)
{
isReadOnly = false;
modelAdapter = WSDLModelAdapter.lookupOrCreateModelAdapter(element.getOwnerDocument());
modelAdapter.getModelReconcileAdapter().addListener(internalNodeAdapter);
}
}
addButton.setEnabled(!isReadOnly);
removeButton.setEnabled(!isReadOnly);
}

I realized that enabling the two buttons here didn't make any sense. The logic for this is handled in the refresh() method in AbstractExtensionsSection, and if the state changes refresh() will run through again and adjust the state accordingly. So why does this method need to handle the buttons' state? It doesn't.

So, I removed these two lines from the method and ran the application. To my delight, the changes worked, and the results I saw using my cheap fix a couple of weeks ago were the same changes I was seeing now. This time, I did it without having to change refresh() execute every time the extensions section was returned to, which is a waste of resources. Instead my change simply leaves the handling of the buttons' state to the refresh() method, which I feel alot more comfortable with.

So now I need to take this solution to Bugzilla. Hopefully I'll be able to get my patch into the tree. Also, my next step is to find another bug to work on, as I have no intention of sitting around for the final few weeks of the semester.

Wednesday, March 11, 2009

0.2 Release

Well, I believe I'm ready to release 0.2. If you read my last blog, I was currently evaluating what changes I need to make between two files (AddExtensionsComponentDialog.java, AbstractExtensionsSection.java). I figured out that AbstractExtensionsSection.java was the file I was looking for.

I ran a find for "removeButton.setEnabled" (the method which enables and disables buttons) within this file and I found two lines, one of which was located within a method called refresh(). The name alone suggested that this might be relevant. It would make sense for the section to be refreshed on a consistent basis. Here is the code:


public void refresh()
{
setListenerEnabled(false);
if (input != null)
{
if ( prevInput == input)
return;
else
prevInput = input;

Tree tree = extensionTreeViewer.getTree();
extensionDetailsViewer.setInput(null);
tree.removeAll();

addButton.setEnabled(!isReadOnly);

extensionTreeViewer.setInput(input);
if (tree.getSelectionCount() == 0 && tree.getItemCount() > 0)
{
TreeItem treeItem = tree.getItem(0);
extensionTreeViewer.setSelection(new StructuredSelection(treeItem.getData()));
}

removeButton.setEnabled(tree.getSelectionCount() > 0 && !isReadOnly);

// Bugzilla 197315. Make this bulletproof for maintenance release.
Control detailsViewerControl = extensionDetailsViewer.getControl();

if (detailsViewerControl != null && !detailsViewerControl.isDisposed())
{
detailsViewerControl.setEnabled(!isReadOnly);
}
}
setListenerEnabled(true);
}


The instance of Tree represents the list of extensions, which is highlighted in the photo below:



And as I mentioned before, removeButton refers to the Delete button which I am trying to fix. At first I believed that perhaps refresh() simply wasn't getting called when switching tabs e.g. clicking on Extensions, then clicking Documentation, then clicking Extensions again.

So I inserted a breakpoint at the beginning of the method and ran a stack trace. Turns out refresh() actually DOES get called whenever the tabs are switched. So why is the button not being re-disabled? This isn't even the real issue, which I'll explain later.

I then realized that even though refresh() was being called after every switch, the method was being exited early. If we look at this code:


if ( prevInput == input)
return;
else
prevInput = input;


Notice here that if "prevInput" is not equal to "input" then the method should return out. I realized that prevInput refers to the previous status of the Extensions frame. It was being compared to the current status of the frame, and because switching tabs wasn't changing the actual contents of the frame, the method was exiting early. To change this, I changed the if statement to look like this:


if ( prevInput != input)
prevInput = input;


When I ran the Eclipse Application, the button behaved properly!



Note that I switched tabs before switching back to the Extensions tab in this picture. When I clicked on an object in the WSDL editor the buttons kept their proper state. It would appear that the problem is solved.

However, to me the real issue is: Why does refresh() need to be called every time to persist the state of the buttons? Why don't the buttons remember their state? I believe that the answer isn't that the buttons are forgetting their state, I believe it's because something else is changing their state. This would suggest that the problem actually doesn't occur in refresh(). The change I made now is a bit of a cheap fix, what I would like to do is eventually find the root of this problem and fix it, which is why I don't plan on submitting a patch yet.

Clearly the developers wanted to make sure that the code executed in refresh() wasn't done every time a tab was switched. Thus, the problem with my solution is that by cutting the old if statement out, I ensure that the code inside refresh() is executed on every tab switch, which is a waste of resources. So for 0.3 my current objective is to find the underlying problem as to why the Delete button's state change isn't being persisted, and that means finding out where else the button is being changed.