ariba.ui.aribaweb.core
Class AWResponseBuffer

java.lang.Object
  extended by ariba.ui.aribaweb.util.AWBaseObject
      extended by 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.


Nested Class Summary
static class AWResponseBuffer.Type
           
 
Field Summary
 
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
 
Method Summary
 void setIgnoreWhitespaceDiffs(boolean yn)
           
 void updateParentChecksum(AWResponseBuffer parent)
           
 
Methods inherited from class ariba.ui.aribaweb.util.AWBaseObject
debugString, ensureFieldValuesClear, getFieldValue, init, isKindOfClass, localizedJavaString, logString, logWarning, setFieldValue
 
Methods inherited from class java.lang.Object
equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Method Detail

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.