[an error occurred while processing this directive]

Page One
Page Two
The Databank
What Is Swing?
Special Report
IDE Roundup
Swing and the Web
Swing Text
Tech Topics
Friends
Tips and Tricks
The PLAF Papers
Call 911
The Archive
JFC Home
Download Swing
Swing API Docs
Download JDK
JDK Docs
Java Tutorial
 
The Swing Connection Swing Text

CONCURRENCY IN SWING TEXT

By Timothy Prinzing

Concurrency in GUIs has often been a subject of controversy. JFCTM/Swing, while not entirely thread-safe, does offer some support for concurrency in key areas. This support simplifies the programming of simple tasks, while leaving the door open for the creation of more complex applications.

Text tends to come in large quantities that often can't be satisfactorily dealt with using brute force while keeping the GUI responsive. Making use of concurrency for text-oriented interfaces is natural because most the work needed to make things current have negligible visibility. That is why the JFC/Swing text package offers some support for concurrency.

This article examines the support for concurrency provided by the Swing text package, and then shows how to perform text layout asynchronously to the event dispatching thread in an interactive display.

This article contains five major sections:

The examples in the article require a recent version of JFC/Swing, as a number of concurrency related bugs have been fixed in newer versions. To run the examples, you should use JFC/Swing 1.1.1 beta 1 or later, or JDK1.2.2. Links to the source files are provided in the sections that describe them. To download a set of a set of prebuilt class files stored as a zipped.jar file, follow this link. (To use the .jar file, you'll have to unzip it using PKZip, WinZip, or some other unzipping utility.)


Asynchronous Model Changes

The primary form of concurrency support in the text package is designed to support building and maintenance (also called mutations) of the document model on a separate thread from the event dispatching thread. A concurrent application will typically interact with the model and have no direct interest in the GUI. This allows the control and view functionality built into the Swing text package to maintain the simplicity of operating on the event dispatching thread. What makes it particularly useful is that parsing from an archive is generally a very time-consuming process that actually amounts to making a series of edits to the model.

One of our goals in designing the Swing text package was to support developers who want to create a customized view but have no interest in dealing with concurrency. To accomplish that goal, the TextUI implementation serializes access to the view. At any given time, a thread may either be actively mutating the model or performing some operation that doesn't mutate the document model, such as rendering or performing layout-related operations. Because the model is the source of the mutations (and the center of the MVC relationship), it is a convenient place to perform locking. Therefore, the model provides a locking mechanism that supports either one writer or multiple readers. The text package makes use of the locking mechanism to serialize access to the view(s) of the model.

The model change notification updates the view with a write lock. The purpose of notifying with the write lock held is to guarantee that threads with read-only interest in the model are not using the view while the view is being updated with the changes that have been made to the model. For example, the event dispatching thread renders the view with a read-lock and is guaranteed the model is not changing while the event dispatching thread is performing its operations. Serializing access to the UI in this way makes writing the view implementations substantially easier than trying to support concurrency through atomic operations on each of the objects that make up the UI. It also reduces the cost for developers who choose not to make use of concurrency; that is, the approach is not fine-grained and represents minimal overhead over an unthreaded approach.

The preceding diagram illustrates the serialization of access to the UI between the event dispatching thread and another thread that is actively mutating the model. The javax.swing.plaf.basic.BasicTextUI and javax.swing.text.AbstractDocument classes implement this relationship. The rendering and layout occur in an implementation of the javax.swing.text.View class, which can be written with minimal concern for thread safety. The considerations for View implementers are as follows:

  • There will likely be multiple MVC relationships, so it is unlikely that a View will be able to use any static variables unless they are thread-safe.
  • For methods where the flow of control is downward -- that is, methods called by the TextUI implementation on a View or called by a parent View to a child View -- there should only be one thread at a time inside a View instance that is traversing downward in the tree. These methods can be implemented with complete disregard to locking unless the class specifically wants to add additional concurrency, such as the AsyncBoxView presented here.
  • For methods where the flow of control is upward -- such as methods that a child View calls on its parent, and [methods that] the root View calls on the associated TextUI -- the call should be capable of occurring asynchronously.

With the exception of the JEditorPane.setPage method, there are no asynchronous mutations unless the developer specifically arranges for them by creating a thread to mutate the document model. This feature of the text package leaves it up to the developer to determine whether concurrency is used. Loading a file is treated as a series of mutations to the model. If this operation is carried out in response to a user action -- for example, in response to the pressing of a button in the GUI -- then that thread (the event dispatching thread) will be used to load and the operation will be performed synchronously. Alternatively, the developer might create a thread and perform the loading on that thread as a series of asynchronous mutations to the model.

A simple class to load a document might look like this:


    class AsyncLoader extends Thread {

	AsyncLoader(Document doc, Reader in) {
	    super("document-loader");
	    setPriority(Thread.MIN_PRIORITY);
	    this.in = in;
	    this.doc = doc;
	}

                      public void run() {
	    try {
		char[] buff = new char[4096];
		int nch;
		while ((nch = in.read(buff, 0, buff.length)) != -1) {
		    doc.insertString(doc.getLength(), new String(buff, 0, nch), null);
		}
	    } catch (Throwable e) {
		e.printStackTrace();
		System.exit(1);
	    }
	}

	private Reader in;
	private Document doc;
    }

The preceding example functions with plain text, but for more interesting kinds of formats such as HTML a fairly large amount of time is spent parsing and determining how to modify the model. To leave the Document free of a write-lock for as much time as possible, the HTMLDocument builds up a set of tokens that describe the tree edit that is desired while the input stream is parsed, so that the actual amount of time that is spent with a write-lock acquired is small. After the changes have been made, the document remains locked, while notifying the listeners. This is because the Java Beans event model requires all listeners to be notified of the new state prior to publishing the effects of further mutations. The class that provides most of this functionality is DefaultStyledDocument.ElementBuffer, which is accessed via the DefaultStyledDocument.insert method. The ElementBuffer functionality is described in another article

The preceding diagram shows an example of how two threads might interact. It's what one potentially gets when using the HTMLEditorKit with JEditorPane.setPage. The green portions of the timing bars indicate the model is in an unlocked state. Red portions indicate a lock is held. Yellow indicates the thread is stopped waiting to acquire the lock. The red portions of the timing for the model thread indicate a write lock is held. The red portions of the timing bar for the view thread indicate a read lock is held. The smaller the red and yellow areas on the event dispatching thread the better the GUI response.

The JEditorPane.setPage method spawns a thread to load the document if the document is of type AbstractDocument and has a value greater than or equal to zero specified for the asynchronousLoadPriority property. If the document advertises a thread priority to load at a thread will be created and run to load the document.

Each EditorKit implementation can control whether or not URLs of its type are loaded asynchronously by using the setting of the asynchronousLoadPriority property. There is an additional property (called tokenThreshold) on HTMLDocument that controls how many tokens (that describe the desired edit) are buffered before attempting to mutate the document model. The larger the setting, the wider the red/yellow bars (lock and wait times) will get. The smaller the setting, the more frequent are the updates to the view(s), resulting in a higher level of repeated layout and rendering operations triggered by the updates.

Asynchronous Layout

For interactive views, layout and rendering can be an iterative process. The thread-safe methods Component.repaint and JComponent.revalidate are for scheduling future paints and layout. With this capability we can accomplish a number of things:

  1. The responsiveness of the UI can be substantially improved because the event dispatching thread doesn't have to wait for layout to complete. Performing layout is inherently computationally expensive and much of it may not be visible so performing calculations not needed to display can be done as a low priority task.
  2. Updates from the model will be substantially more efficient because the updates don't have to wait for layout to complete, and the updates from mutating the model will likely mean changes to the layout anyway.
  3. If the layout is performed asynchronously, it is much easier to provide incremental updates to the layout. For some kinds of layout that involve positioning many objects, this can result in substantial timesaving.

Views are produced by a ViewFactory implementation, which gives the developer substantial control over how the view represents the model. Replacing the ViewFactory is a good strategy for increasing the level of concurrency for those cases that will benefit from it. A text field has so little work involved in layout that there is no benefit to computing the layout asynchronously. An HTML view does so much work that trying to do it on the event dispatching thread will cause the user interface to freeze. Printing could use a ViewFactory implementation that is different from the factory used by the GUI. It can share most of the building blocks used for interactive views, but strategically replace certain parts. For example, a printing ViewFactory might use a View that does page breaking to represent an element, where the View implementation used for the GUI might be something like the AsyncBoxView example that can defer work to keep the GUI responsive.

The javax.swing.text.View implementation provided in this article is an example of performing layout asynchronous to the event dispatching thread. A future version of swing (version 1.3) will have this functionality built in.

For print views, layout and rendering can all happen on another thread that is completely unrelated to the event dispatching thread. The locking strategy would depend upon whether or not mutations would be allowed while the model is being printed. If a read lock is acquired for the entire duration of the layout and render process, the user would be prevented from modifying the model but they could view it. A better implementation would watch for mutations while preparing the print job and do fixes to the print view if it hadn’t yet been printed. We don't currently have any direct support of printing. A future article will provide some examples and a future version of Swing will have direct support for printing.

 


Asynchronous Layout Example

One View implementation that performs layout asynchronously is AsyncBoxView. Many text views use the box layout pattern, so an implementation that performs layout asynchronously is a useful one. The javax.swing.text.BoxView class provides a general-purpose box layout that can be easily customized. However, when a large box needs to be laid out, performance could be improved by avoiding the time-consuming task of considering all the children when doing layout. That is the direction that was taken in the design of the AsyncBoxView class. The class was designed with these goals in mind:

  • Keep the GUI thread active by doing as little work as possible on it.
  • Minimize the number of threads used for doing layout.
  • Update visible areas before updating those that are not visible.
  • Service model/view translations as quickly as possible and synchronously.
  • Minimize the time required to react to changes that are broadcast from the model (these changes may not be visible).

Design Overview

The most time-consuming part of laying out a large box is calculating preferred sizes for the children, and then waiting for the actual layout that the children perform. If these two operations are performed synchronously, it may even turn out that a lot of this work is useless. For instance, the view may have had its size changed again, or an update from the model may have changed the overall size requirements that result in a resize. Further, access to the view hierarchy is serialized with respect to mutations. This means a read lock should not be held any longer than necessary, so that updates from the model can be squeezed in. Since changes are natural, it is important to deal with them in the least time-consuming way possible. To accomplish this, AsyncBoxView does the following things:
  • The management of the size of the child is separated from the management of where the child view is located. This separates the time-consuming functionality of a layout from the display-oriented functionality of placement so that the time consuming part can be performed on a separate thread. The size of each child is also managed separately -- that is the children are not aligned with respect to each other.
  • The call to a method on each child is granularity of the read lock on the document for layout operations. This is substantially simpler than alternative kinds of controls of the read-lock lifetime; in general, it gives the mutating thread pretty good access to the model. Choosing a good strategy from the ViewFactory implementation will ensure that this is true.
  • The locations of the children are calculated only if they're actually needed. The overall length along the major axis is maintained separately, so that the individual locations are not needed to communicate with the parent view. This makes it easier to calculate the size incrementally and defers some work that might be invalidated prior to becoming useful.
  • The event dispatching thread is used to perform the layout if the child is actually visible. This makes the display responsive, no matter what the state of the layout queue might be. The order in which tasks sit in the queue becomes inconsequential when the only work being done is not visible.
  • There can be multiple layout queues, but by default there will be one queue to service all of the instances of AsyncBoxView. The default queue is serviced by a single dedicated thread running at a low priority.
  • Layout work that has been queued may be invalidated at some time prior to to the time that it is actually executed. This might be because the size has changed again, or because the child has become visible. Trying to clean the queue can become expensive, so the expense is avoided by making all the layout tasks able to discover if their services are no longer needed, or by immediately adapting to a new set of constraints. This mechanism allows the queue to automatically empty quickly for completed work, and is possible because order in the queue is inconsequential.
  • While not specifically addressing a goal, it would be useful if the example could be customized to the floating-point based coordinates used in Java 2D.

    The example itself needs to function with JDK 1.1 as well, however. To make it possible to create an entirely floating-point based solution, the AsyncBoxView class uses floating point everywhere possible, so an entirely floating-point solution is possible with only minor changes from a subclass -- that is, the only integers used are the incoming allocation and outgoing child allocation.

In order to calculate the layout incrementally, the AsyncBoxView advertises its preferences as being flexible along the minor axis -- that is, the axis orthogonal to the axis being tiled -- and rigid along the major axis along which the children are being tiled. The size along the major axis is the sum of the children's preferred span, which can be determined incrementally. This incremental calculation gives the view the ability to publish its size to the parent relatively cheaply, and as frequently as it wants. The actual location of the children is not calculated unless they become visible or are needed for caculating a model-to-view coordinate translation. The following diagram shows the preferences for a vertical box (i.e. with a major axis of View.Y_AXIS).
Because many views can make use of a border area around the children, the AsyncBoxView supports a set of floating point insets around the area given to the children. This inset area might be used to draw borders, create margins, and so on. The following diagram shows the insets:

More Details about the AsyncBoxView Class

The following paragraphs provide a few more important details about the AsyncBoxView class, its nested classes, and the layout queue.

The classes

The AsyncBoxView class is primarily a facade that delegates to other objects to accomplish its tasks. The actual layout is performed by nested classes, and the layout thread is managed as a separate class to enable multiple views to use the class's services at the same time.
ChildState
This nested class is responsible for representing the layout state of a child view. The layout thread (or the event dispatching thread, depending upon whether the child view is needed to be current) may update it. This is the expensive part the layout, and represents the child size portion of the layout. This is implemented to run as a task (i.e. it does its work as an implementation of the Runnable interface) so that is can be placed on the LayoutQueue without allocating additional memory. There is one of these for each child referenced.
ChildLocator
This nested class is responsible for representing the location of the children. This is used to determine child view allocations so that AsyncBoxView can paint the children and perform model/view translation via the children. There is exactly one of these per instance of AsyncBoxView.
LayoutQueue
This is a very simple layout queue that takes objects implementing the Runnable interface and executes them sequentially. By default, there is one LayoutQueue that is shared by all instances of AsyncBoxView.
The view implementation is composed of the following classes, packaged in a package called pre13:
    • AsyncBoxView.java
    • LayoutQueue.java
    • GapVector.java
    • The GapVector is used only because Vector is too inefficient and using the collection classes would tie the example to the 1.2. It seemed more useful for the example to work under both 1.1 and 1.2. The view can easily be rewritten to not use it.

How It Works

The AsyncBoxView extends the basic support provided by the document lock by adding another thread to do layout. The layout thread always grabs a read lock on the document to ensure that the document will not be changing while layout operations are being performed. The lock is held for the length of time needed to make one method call on a child of the AsyncBoxView. This limited lock aquisition gives a mutating thread a chance to change the document and notify the views, but protects against any unwanted interaction with a changing model. Unfortunately, aquiring a read lock does nothing to protect the layout thread from unwanted interaction with the event dispatching thread, which can acquire a read lock at the same time. The methods that propagate downward to the children all expect that only one thread will be active in a given View instance at a time unless the view was specifically written to support concurrency (as AsyncBoxView was). Each child view has an associated instance of the ChildState class that represents what is known about the child. Each ChildState record is implemented as a Runnable to enable it to be placed upon the LayoutQueue which will execute it on a layout thread. It can also be run directly by the event dispatching thread. The ChildState object is used to synchronize access to the child (a view subtree). This synchronization keeps the layout thread and event dispatching thread from both traveling down the same subtree at the same time. An example of a layout thread and event dispatching thread operating in different parts of the tree is illustrated by the diagram below. The root of the tree is an instance of AsyncBoxView and the event dispatching thread is actively painting a child that is currently visible while another child is being processed by a layout thread.
The code in ChildState to update the child state is shown below. In this example, potentially expensive method calls are highlighted in red. The preferences along the minor axis (due to being flexible) are determined by calling the getMinimumSpan, getPreferredSpan, and getMaximumSpan methods. These values only need to be fetched once unless the child changes what it wants to publish along this axis (which is common). If the child changes its preference, it calls preferenceChanged, which propagates upward through the tree. Along the major axis, the child is given exactly what it prefers. The View can publish this upward through the tree when it wants; this is an interesting customization area for an asynchronous View. What is published is the required span to tile the preferred span of the children. This is a rigid value (i.e. the mininum value and maximum value are equal to the preferred value), so a properly functioning parent will give the desired allocation to fulfill the tile operation. Finally, the child's size is set, triggering the layout in the child (which may or may not be synchronous).

	void updateChild() {
	    boolean minorUpdated = false;
	    synchronized(this) {
		if (! minorValid) {
		    int minorAxis = getMinorAxis();
		    min = child.getMinimumSpan(minorAxis);
		    pref = child.getPreferredSpan(minorAxis);
		    max = child.getMaximumSpan(minorAxis);
		    minorValid = true;
		    minorUpdated = true;
		}
	    }
	    if (minorUpdated) {
		minorRequirementChange(this);
	    }

	    boolean majorUpdated = false;
	    float delta = 0.0f;
	    synchronized(this) {
		if (! majorValid) {
		    float old = span;
		    span = child.getPreferredSpan(axis);
		    delta = span - old;
		    majorValid = true;
		    majorUpdated = true;
		}
	    }
	    if (majorUpdated) {
		majorRequirementChange(this, delta);
		locator.childChanged(this);
	    }

	    synchronized(this) {
		if (! childSizeValid) {
		    float w;
		    float h;
		    if (axis == X_AXIS) {
			w = span;
			h = getMinorSpan();
		    } else {
			w = getMinorSpan();
			h = span;
		    }
		    childSizeValid = true;
		    child.setSize(w, h);
		}
	    }
	    
	}
The methods that travel upward through the parent should be written in a thread-safe way. Most of these methods simply fetch resources from the hosting component, and aren't a concern to view implementers, as the default implementation is generally called. An exception to this is the preferenceChanged method, which typically changes some state in a composite view because it needs to undergo layout again. AsyncBoxView is a composite view with children, so it needs to consider the ramifications of a call on preferenceChanged. The AsyncBoxView is different from a synchronous view in the implementation will not immediately propagate the call upward if it's preferences were changed because it has the ability to propagate the change on the layout thread. If a child changes its requirements (by calling preferenceChanged on the AsyncBoxView), its associated ChildState record is marked and the ChildState is placed on the layout queue, along with a Runnable to publish the new layout to the parent of the AsyncBoxView. The code to accomplish this [operation] is:
    public synchronized void preferenceChanged
                                (View child, boolean width, boolean height) {
        if (child == null) {
            getParent().preferenceChanged(this, width, height);
        } else {
            if (changing != null) {
                View cv = changing.getChildView();
                if (cv == child) {
                    // size was being changed on the child, no need to
                    // queue work for it.
                    changing.preferenceChanged(width, height);
                    return;
                 }
            }
            int index = getViewIndexAtPosition(child.getStartOffset(), 
                                               Position.Bias.Forward);
            ChildState cs = getChildState(index);
            cs.preferenceChanged(width, height);
            LayoutQueue q = getLayoutQueue();
            q.addTask(cs);
            q.addTask(flushTask);
        }
    }
  
When child views are added from the AsyncBoxView, AsyncBoxView creates a ChildState instance to represent the View and adds it to the layout queue for processing. After all the new state records have been added, a Runnable to publish the resulting changes to the parent of the AsyncBoxView is also placed on the queue. This happens as a result of adding the AsyncBoxView to a View hierarchy with the setParent method, or as a result of notification from the model via the insertUpdate, removeUpdate, and changedUpdate methods. The notification methods will, of course, be called with a write lock held on the document. The newly created representatives of the child state will start being processed to keep the layout of AsyncBoxView current.
    public void replace(int offset, int length, View[] views) {
        synchronized(stats) {
            LayoutQueue q = getLayoutQueue();
            ChildState[] s = new ChildState[views.length];
            for (int i = 0; i < s.length; i++) {
                s[i] = createChildState(views[i]);
                q.addTask(s[i]);
            }
            stats.replace(offset, length, s);
            q.addTask(flushTask);
        }
    }
  
After a change in the view hierarchy, typically a repaint will have been triggered for some region of the display. If a part of the AsyncBoxView is visible, it will get a paint request that requires that the children intersecting the visible region be current prior to traversing through them. The event dispatching thread will request the locations from the ChildLocator, which determines how far down the major axis the first child is that intersects the visible region. The ChildLocator will ensure that each intersected child's state is made current (by running it on the event dispatching thread) and then will propagate the paint method (while synchronized on the ChildState of the child being painted).
    public void paint(Graphics g, Shape alloc) {
        synchronized (locator) {
            locator.setAllocation(alloc);
            locator.paintChildren(g);
        }
    }
  
A modelToView or viewToModel method call work like paint, except the propagation is to a single child rather than multiple children.
    public Shape modelToView(int pos, Shape a, Position.Bias b) 
                                    throws BadLocationException {
        int index = getViewIndexAtPosition(pos, b);
        Shape ca = locator.getChildAllocation(index, a);

        // forward to the child view, and make sure we don't
        // interact with the layout thread by synchronizing
        // on the child state.
        ChildState cs = getChildState(index);
        synchronized (cs) {
            View cv = cs.getChildView();
            Shape v = cv.modelToView(pos, ca, b);
            return v;
        }
    }

    public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
        int pos;    // return position
        int index;  // child index to forward to
        Shape ca;   // child allocation
	
        // locate the child view and it's allocation so that
        // we can forward to it.  Make sure the layout thread
        // doesn't change anything by trying to flush changes
        // to the parent while the GUI thread is trying to
        // find the child and it's allocation.
        synchronized (locator) {
            index = locator.getViewIndexAtPoint(x, y, a);
            ca = locator.getChildAllocation(index, a);
        }

        // forward to the child view, and make sure we don't
        // interact with the layout thread by synchronizing
        // on the child state.
        ChildState cs = getChildState(index);
        synchronized (cs) {
            View v = cs.getChildView();
            pos = v.viewToModel(x, y, ca, biasReturn);
        }
        return pos;
    }
  
If there is a change in the span along the minor axis (via a call from the parent to the setSize method), all the ChildState records are invalidated and placed upon the layout queue, and a task to publish the results to the parent is also queued. Any work previously on the layout queue for this view will immediately begin performing layout of the children according to the new span setting along the minor axis. When the redundant task is encountered again, it will simply notice there is no work to do.
    public void setSize(float width, float height) {
	float targetSpan;
	if (axis == X_AXIS) {
	    targetSpan = height - getTopInset() - getBottomInset();
	} else {
	    targetSpan = width - getLeftInset() - getRightInset();
	}
	if (targetSpan != minorSpan) {
	    minorSpan = targetSpan;

	    // mark all of the ChildState instances as needing to
	    // resize the child, and queue up work to fix them.
	    int n = getViewCount();
	    LayoutQueue q = getLayoutQueue();
	    for (int i = 0; i < n; i++) {
		ChildState cs = getChildState(i);
		cs.childSizeValid = false;
		q.addTask(cs);
	    }
	    q.addTask(flushTask);
	}
    }
  

Areas for Improvement

The example makes no attempt to be clever about when it chooses to publish its changes upward to the parent view. In many cases this doesn't matter very much, but if multiple AsyncBoxView instances are nested (such as in nested html tables), the delay of propagating the layout changes upward becomes visible. Intermediate results can be published easily, and an improved implementation would take advantage of that.
There are a number of performance enhancements that could be made for calculating the requirements along the minor axis. These calculations are performed on the layout thread, via a brute-force visitation of all the children.
The ChildLocator can often locate the correct child for viewToModel() calculations via a binary search. This would fall out rather naturally if the 1.2 collections were used.


An Example usage with StyledEditorKit

A relatively simple usage of the asynchronous box is to alter the StyledEditorKit ViewFactory implementation to produce an AsyncBoxView for the vertical arrangement of the paragraphs, rather than using BoxView. For larger documents this change makes a substantial difference in the responsiveness of the UI. The classes are packaged in the examples.async package. The class examples.async.StyledEditorKit provides a replacement (extension) for javax.swing.text.StyledEditorKit that can be used as an alternative in JTextPane. The class examples.async.Example1 illustrates the use of the replacement EditorKit implementation.

The element structure used by DefaultStyledDocument is discussed in another article. From that model we can easily build a view that is a tree of objects extending the View class. By default, this is a set of objects based upon BoxView, ParagraphView, LabelView, ComponentView, and IconView. These are all implementations of the View class.

The AsyncStyledEditorKit class implements the ViewFactory interface. The implementation of the ViewFactory.create method creates an AsyncBoxView if the element is the root element of the DefaultStyledDocument; otherwise the default factory of the superclass is used to produce the views. The following figure illustrates the spatial relationship of the views in the view hierarchy.


        public View create(Element elem) {
            String kind = elem.getName();
            if ((kind != null) && 
              (kind.equals(AbstractDocument.SectionElementName))) {
                return new AsyncBoxView(elem, View.Y_AXIS);
            }
            ViewFactory f = super.getViewFactory();
            return f.create(elem);
        }

The StyleExample.java file makes up the example. The example can be executed as an application by executing a command something like:

java StyleExample filename

Where filename is the name of a file you would like to load.


An Example of Usage with HTMLEditorKit

The factory for the asynchronous HTMLEditorKit works much like the asynchronous StyledEditorKit, except that it can't produce AsyncBoxView directly because more functionality is needed to translate the CSS attributes recognized. The nested BlockView class operates like the javax.swing.text.html.BlockView class, except that it extends AsyncBoxView instead of BoxView. This is used to represent the body element, which typically has a large number of children. Many pages are also composed entirely of tables, so using the AsyncBoxView derivative for table cells will cause their contents to be handled asynchronously. One could be more clever about when asynchronous views are used, but the approach shown is simple.

The HtmlExamle.java file makes up the example. The example can be executed as an application by executing a command something like:

java HtmlExample url

Where url is the name of an URL you would like to load.

[an error occurred while processing this directive]