ariba.ui.aribaweb.core
Class AWResponseBuffer
java.lang.Object
ariba.ui.aribaweb.util.AWBaseObject
ariba.ui.aribaweb.core.AWResponseBuffer
- All Implemented Interfaces:
- AWObject
public final class AWResponseBuffer
- extends AWBaseObject
High-level notes on the algorithm:
A responseBuffer defines a region of the responseContents (ie the encodedStrings appended to the response during
renderResponse). ResponseBuffers can be nested. There are two types: scoped and regular. Let's first
describe how a regular buffer works then talk about the scoped.
RegularBuffers: The entire page is in a regular buffer. Within that page there can be regions (div/spans)
that can change when cycling the page. If we make each of these into a sub-buffer, we'll have nested regular
buffers. To detect the minimum amount of text that needs to be written to the response, we need to find the
smallest buffers that changed. If a buffer is determined to be changed, all its content must be re-written.
We talk in terms of the "top-level" content of a buffer. This is all the encodedStrins in the buffer but not the
contents of the subbuffers, which are treated independently. However, we do concern ourselves with the names of
the subbuffers because if these change, we will have had a deletion or insertion and the only way to handle that
in general is to rewrite the entire buffer.
We employ checksums to substitute for actual string comparisons. The checksum for a regularBuffer is computed
by including all the encodedStrings at the top-level of a given regular buffer, plus the names of the subbuffers.
If any top-level string of subbuffer name changed, we detect a change and must write the entire buffer.
If we do not detect a change in the top-level content of the buffer (ie the checksums are the same), we still need
to recurse down to the next level to see if any of the contents of the subbuffers changed and needs to be written
out. We simply iterate through the children of the current buffer and repeat the aforementioned process.
However, there is a second type of buffer: scoped buffers. An example of a scoped buffer is a
.
In a table, we can detect and react to insertions, deletions, and modifications of rows, but this requires a
different algorithm than regular buffers.
First, to know if we need to write the entire scoped buffer, we compare its top-level content to its predecessor.
In this case, we only care about the encodedStrings at the top level and not the names of the children buffers.
Changes in child buffers names are an indication of insertions or deletions and we have a way to deal with that.
In any case, if we detect a change in the top level content of a scoped buffer, we simply write out the entire
buffer.
However, if we detect a change in a child buffer (either insertion, deletion, or modification), we must still
write out the top-level contents of the scoped buffer as this is generally required to be legitimate html (that is,
a
cannot exist in the absence of a ). So to write the children of a scoped buffer, we iterate through
the encodedStrings at the top level which are intermingled with the subbuffers. We write all encodedStrings,
but must determine what to do with each subbuffer. If the subbuffer is a simple modification at the top level, its
checksum will differ from its predecessor and we simply write the entire child buffer.
If the child buffer doesn't exist in the previous response, then we have an insertion and this means we must
write out the entire buffer and generate some javascript to tell the client side code that this entry is an
insertion. For insertions, we must indicate which row it comes after so the client side code can insert the
row in the proper place.
Once we've iterated through all children in the scoped buffer and written all the top-level content of the
scoped buffer, we must iterate through its predecessor to determine if any rows were present in the predecessor
but not in the current scoped buffer -- this would be a deletion. All deletions can be written after the scoped
buffer has been written.
Finally, we must write out any sub-subbuffers nested within a child of a scoped buffer which wasn't written in
the previous passes. That is, if a given child had no changes and didn't require writing within the scoped buffer,
we need to propagate the writeTo function to each of the nested buffers within those children.
Notes on the implementation of AWResponseBuffer:
To minimize garbage generation, we chose to use a single "contents" buffer and have the AWResponseBuffers point
into this PagedVector. So the BaseResponse allocates a PagedVector which is shared by all responseBuffers and
the responseBuffers simply maintin indexes to their start and ending positions. Each time a new subbuffer
is required by the AWRefreshRegion component, a new one is created and initialized with the index of the globalContents
buffer. The baseResponse makes this buffer the current target buffer and all "append" is done to this buffer.
Of course, the target buffer puts the appended content in the shared globalCotents buffer, but it keep track
of what's going on by updating its checksum as content is appended.
When a buffer is appended it updates its _children list by adding the buffer to the end of the list. Each
responseBuffer has a _children pointer which points to the head of the children, and a _next pointer which
points to the next child in the current list. We also maintain a _tail which allows for rapid append to the
end of the list without requiring iteration to the end to simply append a new child.
If a buffer is a scoped buffer, appending a child does something a bit different than a regular buffer.
In a regular buffer, we don't really care to know where the begin/end of a given child buffer is because we
are either going to write the entire buffer or merely its children. With the proper use of checksums,
we can determine which to do quite easily and quickly (se discussion above).
However, with a scoped buffer, we are required to write the top-level content of the scoped buffer and optionally
write some of the children. This means we must iterate through the top-level encodedStrings writing them out
but, at the appropriate point, conditionally write out a child buffer. Hence, we must put the buffers themselves
in the globalContents to keep track of when to compare/write them. Once we've determined that some child
requires writing, we must render the wrapper of the scoped buffer (eg the tags) and all
the interstitial content between the rows of the table (ie the children). As we go, we encounter child buffers
and optionally write them out (by comparing their checksums to their predecesor).
So, when we append a child buffer to a scoped buffer, we add this buffer to the content so it can be invovled
in the rendering of the wrapper. However, we do not include its name in the scoped buffer's checksum as with
regular buffers because we will deal with each child buffer in situ as either a modification or insertion.
To make the determination of whether or not a child buffer was an insertion or deletion, we keep a HashMap
of all scoped child buffers in both the current and previous responseBuffers. Thus, we can easily compare
the current children with the previous and determine if it existed before or not. Of course, we can do the same
from the other direction to determine if there were deletions, and this is done after the scoped buffer is written
in its entirety.
Finally, we must make a pass throught he children of the scoped buffer and, for those children which were
not written out in the previous pass, give them an opportunity to write out any of their subbuffers which may have
changed. Again, this is done outside the rendering of the scoped buffer to result in legitimate html.
Other notes: This whole operation is an interactive dance with the BaseResponse. BaseResponse maintains a stack
of buffers and the current buffer so it knows where to direct the next append operation. As it pops buffers
off its stack, it sends a close() message to that buffer so that buffer can cleanup (ie release its CRC32) and,
most importantly, take note of the size of the globalContents buffer so it knows where its end is.
Pooling: To avoid too much garbage generation, we use a recycle pool for the CRC32 objects and for
the AWPagedVectorIterator. In both cases, the number of objects required at any one time is a function of the
depth of the stack of responseBuffers, so the pool neededn't be too large. However, they come and go quite
frequently, so its make sense to pool these. Also, they clean up nicely, so its easy to pool them.
I do not pool AWResponseBuffers since they are quite numerous and have a fairly long life.
Also, once a BaseResponse has been written to the client, we jettison the globalContent to free up that memory.
Fields inherited from class ariba.ui.aribaweb.util.AWBaseObject |
AribaHashtableClass, AribaVectorClass, ClassClass, EmptyHashtable, EmptyMap, EmptyVector, False, IntegerClass, JavaHashtableClass, JavaMapClass, JavaVectorClass, LogHandling, NullObject, ObjectClass, StringClass, True, UndefinedObject, UndefinedString, UninitializedRealNumber |
Methods inherited from class java.lang.Object |
equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
updateParentChecksum
public void updateParentChecksum(AWResponseBuffer parent)
setIgnoreWhitespaceDiffs
public void setIgnoreWhitespaceDiffs(boolean yn)
AribaWeb User Interface Development Framework
Copyright © 2000-2014 Ariba, Inc. All Rights Reserved.