JDK-6452106 : FlowView.layout causes ArrayIndexOutOfBoundsException
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 6
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2006-07-24
  • Updated: 2011-01-19
  • Resolved: 2006-08-23
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6
6 b97Fixed
Description
FULL PRODUCT VERSION :
java version "1.6.0-beta2"
Java(TM) SE Runtime Environment (build 1.6.0-beta2-b86)
Java HotSpot(TM) Client VM (build 1.6.0-beta2-b86, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
This code in FlowView.FlowStrategy.layout is problematic as it's written in JDK 1.6:

                rowIndex = fv.getViewIndexAtPosition(damageStart);
                if (rowIndex > 0) {
                    rowIndex--;
                }
                p0 = fv.getView(rowIndex).getStartOffset();

Since fv.getViewIndexAtPosition can return -1, the later fv.getView(rowIndex) can (and often does) throw ArrayIndexOutOfBoundsException.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
This is easily generated on my machine using a JEditorPane in HTML mode with editable = true and an embedded image.  Just select some nearby text and it happens.  I haven't done the work to extract that code into a simple runnable, but since the fix would be so easy hopefully this won't be necessary.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
In JDK 1.5 there are no exceptions.
ACTUAL -
An exception is thrown, then nothing in the box is rendered until something triggers a clean redraw.

REPRODUCIBILITY :
This bug can be reproduced often.

CUSTOMER SUBMITTED WORKAROUND :
Since everything in the stack trace is Sun/Swing code, I could find no easy work-around.  The only practical workaround is to not use JDK 1.6.

Release Regression From : 5.0
The above release value was the last known release where this 
bug was not reproducible. Since then there has been a regression.

Release Regression From : 5.0
The above release value was the last known release where this 
bug was not reproducible. Since then there has been a regression.

Comments
SUGGESTED FIX http://javaweb.sfbay/jcg/1.6.0-mustang/swing/6452106/
18-08-2006

EVALUATION While this fix was on review i was convinced to change the recovery strategy for performance reasons. Now if there's no view at position damageStart we decrement the position by one until a view is found. My understanding is that there should always be a view at position damageStart-1; however i've introduced a loop to be on safe side. This change can improve performance for large paragraphs.
16-08-2006

EVALUATION The index passed into getViewIndexAtPosition() should by nature be always inside bounds of the FlowView, so it was supposed that there will always be a child view at that index. This is why our code does not protect against -1. Here's why this supposition fails: When a GlyphView is a fragment, i.e. it represents a piece of an element, it keeps an offset and length of that piece. Now if our view is in the end of an element, and we append a character to the end of that element, the view will be out of sync. The element's end offset will grow by 1 but the view's end offset will remain the same. Thus we have a 'gap' between glyph views: a character not covered by any view. If our view is the last view in its parent, there will be a gap between parent views as well. This is why the code in question fails: a GlyphView fragment 'lags' behind its element. If it happens to be at the end of a row, we have a gap between rows. The index we pass into getViewIndexAtPosition() falls into that gap, so no View is found. The purpose of this code is to find a row that we can safely start layout from (i.e. no rows before that row will be changed). Various recovery strategies are possible here. We could restart layout from row 0, or call getViewIndexAtPosition() again passing index decremented by one. I prefer the former option, as it keeps things simple.
27-07-2006