KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdesktop > swing > data > DataLoader


1 /*
2  * $Id: DataLoader.java,v 1.4 2004/11/29 16:09:59 kleopatra Exp $
3  *
4  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5  * Santa Clara, California 95054, U.S.A. All rights reserved.
6  */

7
8 package org.jdesktop.swing.data;
9
10 import org.jdesktop.swing.data.ConversionException;
11
12 import org.jdesktop.swing.event.MessageSourceSupport;
13 import org.jdesktop.swing.event.MessageEvent;
14 import org.jdesktop.swing.event.MessageListener;
15 import org.jdesktop.swing.event.MessageSource;
16 import org.jdesktop.swing.event.ProgressEvent;
17 import org.jdesktop.swing.event.ProgressListener;
18 import org.jdesktop.swing.event.ProgressSource;
19
20 import java.io.InputStream JavaDoc;
21 import java.io.IOException JavaDoc;
22
23 import java.util.ArrayList JavaDoc;
24
25 import javax.swing.SwingUtilities JavaDoc;
26
27
28 /**
29  * Base class for implementing objects which asynchronously load data from an
30  * input stream into a model, ensuring that the potentially lengthy operation
31  * does not block the user-interface.
32  * <p>
33  * Swing requires that all operations which directly affect the user-interface
34  * component hierarchy execute on a single thread, the event dispatch thread (
35  * <a HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">
36  * Swing's single-threaded GUI rule</a>). Because of this, the task of streaming
37  * data (potentially over the network) should not be performed on the event
38  * dispatch thread because it would cause the user-interface to freeze and
39  * become unresponsive. And yet, while the reading of the data should be
40  * off-loaded to a separate thread, it is desirable to incrementally load
41  * portions of that data into the model as it's read so that the user can see
42  * more immediate results than if he/she had to wait for the entire data
43  * stream to be read before seeing any data at all.</p>
44  * <p>
45  * This class implements all the required thread and object synchronization
46  * to support this asynchronous, incremental load operation. It does this by
47  * splitting the operation into three distinct steps:
48  * <ol>
49  * <li>read-meta-data: obtain any available meta-data from the stream which
50  * may provide structural and type information about the data.
51  * Meta-data is typically encoded in the beginning of the stream
52  * and is format-dependent. This should be called only from
53  * the event dispatch thread since it will modify properties on
54  * the model and possibly generate events which affect user-interface
55  * components.</li>
56  * <li>read-data: reading the data from the input-stream and loading it into
57  * a Java data structure which is not connected to the data model.
58  * This step is typically the most time consuming, hence it is
59  * performed in its own &quot;reader&quot; thread which is created by the
60  * <code>startLoading</code> method.</li>
61  * <li>load-data: taking the contents of the disconnected data structure and adding it to
62  * the data model. This step must be performed on the event
63  * dispatch thread because it has the potential to generate
64  * events which affect the user-interface components.</li>
65  * </ol>
66  * A concrete subclass should implement the 3 methods corresponding to these
67  * steps:
68  * <pre><code>
69  * public void loadMetaData(Object model, InputStream is)
70  * public void readData(Object model, InputStream is)
71  * public void loadData(Object model)
72  * </code></pre>
73  * <p>
74  * An application using a DataLoader instance may only invoke
75  * <code>initializeMetaData</code> and <code>startLoading</code> directly.
76  * <code>startLoading</code> will cause <code>readData</code> and
77  * <code>loadData</code> to invoked during the load operation.</p>
78  *
79  * @author Amy Fowler
80  * @version 1.0
81  */

82 public abstract class DataLoader implements ProgressSource, MessageSource {
83     
84     public static final String JavaDoc READER_PRIORITY_KEY = "swingx.readpriority";
85     private LoadNotifier loadNotifier;
86     private MessageSourceSupport mss;
87
88     protected DataLoader() {
89         mss = new MessageSourceSupport(this);
90     }
91
92     /**
93      * Adds the specified progress listener to this data loader.
94      * @param l progress listener to be notified as data is loaded or errors occur
95      */

96     public void addProgressListener(ProgressListener l) {
97         mss.addProgressListener(l);
98     }
99
100     /**
101      * Removes the specified progress listener from this data loader.
102      * @param l progress listener to be notified as data is loaded or errors occur
103      */

104     public void removeProgressListener(ProgressListener l) {
105         mss.removeProgressListener(l);
106     }
107
108     /**
109      *
110      * @return array containing all progress listeners registered on this data loader
111      */

112     public ProgressListener[] getProgressListeners() {
113         return mss.getProgressListeners();
114     }
115
116     /**
117      * Adds the specified message listener to this data loader.
118      * @param l message listener to be notified as data is loaded or errors occur
119      */

120     public void addMessageListener(MessageListener l) {
121         mss.addMessageListener(l);
122     }
123
124     /**
125      * Removes the specified message listener from this data loader.
126      * @param l message listener to be notified as data is loaded or errors occur
127      */

128     public void removeMessageListener(MessageListener l) {
129         mss.removeMessageListener(l);
130     }
131
132     /**
133      *
134      * @return array containing all message listeners registered on this data loader
135      */

136     public MessageListener[] getMessageListeners() {
137         return mss.getMessageListeners();
138     }
139
140     /**
141      * Initializes the model with any meta-data available in the input stream.
142      * The amount of meta-data available from the input stream is format-dependent.
143      * This method is synchronous and may be invoked from the event dispatch thread.
144      * @param model the data model being loaded from the input stream
145      * @param is the input stream containing the meta-data
146      * @throws IOException
147      */

148     public abstract void loadMetaData(Object JavaDoc model, InputStream JavaDoc is)
149         throws IOException JavaDoc;
150
151     /**
152      * Starts the asynchronous load operation by spinning up a separate thread
153      * that will read the data from the input stream. This method will
154      * return immediately. Prior to calling this method, a MessageListener
155      * should be registered to be notified as data is loaded or errors occur.
156      * If the data model being loaded requires meta-data to describe its
157      * structure (such as the number of columns in a tabular data row), then
158      * that meta-data must be initialized before <code>startLoading</code>
159      * is called and must not be modified while the load operation executes,
160      * otherwise synchronization errors will occur.
161      * @see #addProgressListener
162      * @param model the data model being loaded from the input stream
163      * @param is the input stream containing the data
164      */

165     public void startLoading(final Object JavaDoc model, final InputStream JavaDoc is) {
166         loadNotifier = new LoadNotifier(this, model);
167         Runnable JavaDoc task = new Runnable JavaDoc() {
168             public void run() {
169                 try {
170                     readData(is);
171                 }
172                 catch (Exception JavaDoc e) {
173                     final Throwable JavaDoc error = e;
174                     SwingUtilities.invokeLater(new Runnable JavaDoc() {
175                         public void run() {
176                             fireException(error);
177                         }
178                     });
179                 }
180             }
181         };
182         Thread JavaDoc readerThread = new Thread JavaDoc(task);
183         readerThread.setPriority(getReaderThreadPriority());
184         fireProgressStarted(1,1); // indeterminate
185
readerThread.start();
186     }
187
188     protected int getReaderThreadPriority() {
189         String JavaDoc priority = System.getProperty(READER_PRIORITY_KEY);
190         if (priority != null) {
191             try {
192                 int prio = Integer.parseInt(priority);
193                 // PENDING: need to check upper bound?
194
return Math.max(Thread.MIN_PRIORITY, prio);
195             } catch (Exception JavaDoc ex) {
196                 // found some foul expression
197
}
198             
199         }
200         return Thread.MIN_PRIORITY;
201     }
202
203     /**
204      * Invoked by the <code>startLoading</code> method. This method will be
205      * called on a separate &quot;reader&quot; thread. Subclasses must implement
206      * this method
207      * to read the data from the stream and place it in a data structure which
208      * is <b>disconnected</b> from the data model. When increments of data
209      * are ready to be loaded into the model, this method should invoke
210      * <code>scheduleLoad</code>, which will cause <code>loadData</code>
211      * to be called on the event dispatch thread, where the model may be
212      * safely updated. Progress events must not be fired from this method.
213      * @see #scheduleLoad
214      * @param is the input stream containing the data
215      * @throws IOException if errors occur while reading data from the input stream
216      * @throws ConversionException if errors occur while converting data values from
217      * string to object
218      */

219     protected abstract void readData(InputStream JavaDoc is) throws IOException JavaDoc, ConversionException;
220
221     /**
222      * Invoked internally once the <code>readData</code> method calls
223      * <code>scheduleLoad</code> to schedule the loading of an increment of
224      * data to the model. This method is called on the event dispatch
225      * thread, therefore it is safe to mutate the model from this method.
226      * Subclasses must implement this method to load the current contents of the
227      * disconnected data structure into the data model. Note that because
228      * there is an unpredictable delay between the time <code>scheduleLoad</code>
229      * is called from the &quot;reader&quot; thread and <code>loadData</code>
230      * executes on the event dispatch thread, there may be more data available
231      * for loading than was available when <code>scheduleLoad</code> was
232      * invoked. All available data should be loaded from this method.
233      * <p>
234      * This method should fire an appropriate progress event to notify progress
235      * listeners when:
236      * <ul>
237      * <li>incremental load occurs(for determinate load operations)</li>
238      * <li>load completes</li>
239      * <li>exception occurs</li>
240      * </ul>
241      * </p>
242      * @see #fireProgressStarted
243      * @see #fireProgressEnded
244      * @see #fireException
245      *
246      * @param model the data model being loaded from the input stream
247      */

248     protected abstract void loadData(Object JavaDoc model);
249
250     /**
251      * Invoked by the <code>readData</code> method from the &quot;reader&quot;
252      * thread to schedule a subsequent call to <code>loadData</code> on the
253      * event dispatch thread. If <code>readData</code> invokes
254      * <code>scheduleLoad</code> multiple times before <code>loadData</code>
255      * has the opportunity to execute on the event dispatch thread, those
256      * requests will be collapsed, resulting in only a single call to
257      * <code>loadData</code>.
258      * @see #readData
259      * @see #loadData
260      */

261     protected void scheduleLoad() {
262         synchronized (loadNotifier) {
263             if (!loadNotifier.isPending()) {
264                 loadNotifier.setPending(true);
265                 SwingUtilities.invokeLater(loadNotifier);
266             }
267         }
268     }
269
270     /**
271      * Fires event indicating that the load operation has started.
272      * For a determinite progress operation, the minimum value should be less than
273      * the maximum value. For inderminate operations, set minimum equal to maximum.
274      *
275      * @param minimum the minimum value of the progress operation
276      * @param maximum the maximum value of the progress operation
277      */

278     protected void fireProgressStarted(int minimum, int maximum) {
279         mss.fireProgressStarted(minimum, maximum);
280     }
281
282     /**
283      * Fires event indicating that an increment of progress has occured.
284      * @param progress total value of the progress operation. This
285      * value should be between the minimum and maximum values
286      */

287     protected void fireProgressIncremented(int progress) {
288         mss.fireProgressIncremented(progress);
289     }
290
291     /**
292      * Fires event indicating that the load operation has completed
293      */

294     protected void fireProgressEnded() {
295         mss.fireProgressEnded();
296     }
297
298     protected void fireException(Throwable JavaDoc t) {
299         mss.fireException(t);
300     }
301
302     protected void fireMessage(String JavaDoc message) {
303     mss.fireMessage(message);
304     }
305
306     private class LoadNotifier
307         implements Runnable JavaDoc {
308         private DataLoader loader;
309         private Object JavaDoc model;
310         private boolean pending = false;
311
312         LoadNotifier(DataLoader loader, Object JavaDoc model) {
313             this.loader = loader;
314             this.model = model;
315         }
316
317         public synchronized void setPending(boolean pending) {
318             this.pending = pending;
319         }
320
321         public synchronized boolean isPending() {
322             return pending;
323         }
324
325         public void run() {
326             synchronized (this) {
327                 loader.loadData(model);
328                 setPending(false);
329             }
330         }
331     }
332
333 }
334
Popular Tags