1   /*
2    * Copyright (c) 1998, 2005 Gargoyle Software Inc. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    *
7    * 1. Redistributions of source code must retain the above copyright notice,
8    *    this list of conditions and the following disclaimer.
9    * 2. Redistributions in binary form must reproduce the above copyright notice,
10   *    this list of conditions and the following disclaimer in the documentation
11   *    and/or other materials provided with the distribution.
12   * 3. The end-user documentation included with the redistribution, if any, must
13   *    include the following acknowledgment:
14   *
15   *       "This product includes software developed by Gargoyle Software Inc.
16   *        (http://www.GargoyleSoftware.com/)."
17   *
18   *    Alternately, this acknowledgment may appear in the software itself, if
19   *    and wherever such third-party acknowledgments normally appear.
20   * 4. The name "Gargoyle Software" must not be used to endorse or promote
21   *    products derived from this software without prior written permission.
22   *    For written permission, please contact info@GargoyleSoftware.com.
23   * 5. Products derived from this software may not be called "GSBase", nor may
24   *    "GSBase" appear in their name, without prior written permission of
25   *    Gargoyle Software Inc.
26   *
27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30   * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37   */
38  package com.gargoylesoftware.base.gui;
39  
40  import java.util.Arrays;
41  import java.util.ArrayList;
42  import java.util.Collections;
43  import java.util.List;
44  import java.util.Locale;
45  import javax.swing.SwingUtilities;
46  import junit.framework.TestCase;
47  
48  /***
49   *  Tests for AbstractUIController
50   *
51   * @version  $Revision: 1.9 $
52   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
53   */
54  public final class AbstractUIControllerTest extends TestCase {
55  
56      private static final String TASK_COMPLETE = "taskComplete";
57      private static final String TASK_ERROR_THROWN = "taskErrorThrown";
58      private static final String TASK_EXCEPTION_THROWN = "taskExceptionThrown";
59      private static final String TASK_SUCCESSFUL = "taskSuccessful";
60  
61      private Locale originalLocale_;
62  
63  
64      /***
65       *  Create an instance of the test
66       *
67       * @param  name The name of the test.
68       */
69      public AbstractUIControllerTest( final String name ) {
70          super( name );
71      }
72  
73  
74  
75      /***
76       *  The JUnit setup method
77       */
78      public void setUp() {
79          originalLocale_ = Locale.getDefault();
80  
81          // We need the swing event queue initialized for all the tests to run.  Creating a swing
82          // component (doesn't matter which one) will do the initialization for us.
83          new javax.swing.JLabel();
84      }
85  
86  
87      /***
88       *  The teardown method for JUnit
89       */
90      public void tearDown() {
91          Locale.setDefault( originalLocale_ );
92      }
93  
94  
95      /***
96       *  Test the startTask() method
97       *
98       * @throws  Exception When the test fails
99       */
100     public void testStartTask()
101         throws Exception {
102         final String workerString = "worker";
103         final String uiString = "ui";
104 
105         final List list = Collections.synchronizedList( new ArrayList() );
106 
107         /*** A task for testing */
108         class TestStartTask extends WorkerTask {
109             public String failMessage = null;
110 
111             /*** Code that will run on the worker thread */
112             public void runOnWorkerThread() {
113                 if( SwingUtilities.isEventDispatchThread() == true ) {
114                     failMessage = "runOnWorkerThread() was called from the UI thread";
115                 }
116                 list.add( workerString );
117             }
118 
119             /*** Code that will run on the ui thread */
120             public void runOnUIThread() {
121                 list.add( uiString );
122                 if( SwingUtilities.isEventDispatchThread() == false ) {
123                     failMessage = "runOnUIThread() was not called from the UI thread";
124                 }
125             }
126         }
127 
128         final TestStartTask task = new TestStartTask();
129         final LoggingUIController controller = new LoggingUIController();
130 
131         controller.testTask( task );
132 
133         if( task.failMessage != null ) {
134             fail( task.failMessage );
135         }
136 
137         final List expectedStatusMessages = Arrays.asList( new String[]{
138             TASK_SUCCESSFUL, TASK_COMPLETE } );
139         assertEquals( expectedStatusMessages, controller.getList() );
140 
141         assertEquals( "list size", 2, list.size() );
142         assertEquals( "worker element", workerString, list.get( 0 ) );
143         assertEquals( "UI element", uiString, list.get( 1 ) );
144     }
145 
146 
147     /***
148      *  Test successful completion of a task
149      *
150      * @throws  Exception If an error occurs
151      */
152     public void testTaskSuccessful()
153         throws Exception {
154         final LoggingUIController controller = new LoggingUIController();
155         controller.testTask( new ThrowableTask( null, null ) );
156 
157         final List expectedResult = new ArrayList();
158         expectedResult.add( TASK_SUCCESSFUL );
159         expectedResult.add( TASK_COMPLETE );
160         assertListsSame( expectedResult, controller.getList() );
161     }
162 
163 
164     /***
165      *  Test a task that throws an exception from the worker thread
166      *
167      * @throws  Exception If an error occurs
168      */
169     public void testTaskException_WorkerThread()
170         throws Exception {
171         final LoggingUIController controller = new LoggingUIController();
172         final IllegalStateException e = new IllegalStateException();
173         controller.testTask( new ThrowableTask( e, null ) );
174 
175         final List expectedResult = new ArrayList();
176         expectedResult.add( TASK_EXCEPTION_THROWN );
177         expectedResult.add( e );
178         expectedResult.add( TASK_COMPLETE );
179         assertListsSame( expectedResult, controller.getList() );
180     }
181 
182 
183     /***
184      *  Test a task that throws an exception from the ui thread
185      *
186      * @throws  Exception If an error occurs
187      */
188     public void testTaskException_UiThread()
189         throws Exception {
190         final LoggingUIController controller = new LoggingUIController();
191         final IllegalStateException e = new IllegalStateException();
192         controller.testTask( new ThrowableTask( null, e ) );
193 
194         final List expectedResult = new ArrayList();
195         expectedResult.add( TASK_EXCEPTION_THROWN );
196         expectedResult.add( e );
197         expectedResult.add( TASK_COMPLETE );
198         assertListsSame( expectedResult, controller.getList() );
199     }
200 
201 
202     /***
203      *  Test a task that throws an error from the worker thread
204      *
205      * @throws  Exception If an error occurs
206      */
207     public void testTaskError_WorkerThread()
208         throws Exception {
209         final LoggingUIController controller = new LoggingUIController();
210         final Error e = new OutOfMemoryError();
211         controller.testTask( new ThrowableTask( e, null ) );
212 
213         final List expectedResult = new ArrayList();
214         expectedResult.add( TASK_ERROR_THROWN );
215         expectedResult.add( e );
216         expectedResult.add( TASK_COMPLETE );
217         assertListsSame( expectedResult, controller.getList() );
218     }
219 
220 
221     /***
222      *  Test a task that throws an error from the ui thread
223      *
224      * @throws  Exception If an error occurs
225      */
226     public void testTaskError_UiThread()
227         throws Exception {
228         final LoggingUIController controller = new LoggingUIController();
229         final Error e = new OutOfMemoryError();
230         controller.testTask( new ThrowableTask( null, e ) );
231 
232         final List expectedResult = new ArrayList();
233         expectedResult.add( TASK_ERROR_THROWN );
234         expectedResult.add( e );
235         expectedResult.add( TASK_COMPLETE );
236         assertListsSame( expectedResult, controller.getList() );
237     }
238 
239 
240     /***
241      *  Test changing the locale
242      */
243     public void testLocaleChanged() {
244         final List list = new ArrayList();
245 
246         /*** Dummy controller for testing */
247         class MyController extends AbstractUIController {
248             /*** @param locale The new locale */
249             protected final void localeChanged( final Locale locale ) {
250                 assertEquals( "locale == getLocale()", locale, getLocale() );
251                 list.add( locale );
252             }
253 
254             /*** The code to run */
255             protected final void runImpl() {
256             }
257         }
258         Locale.setDefault( Locale.GERMAN );
259         final MyController controller = new MyController();
260 
261         assertEquals( "before run()", 0, list.size() );
262         controller.run();
263 
264         assertEquals( "localeChanged on construction", 1, list.size() );
265         assertEquals( "locale at construction", Locale.GERMAN, list.get( 0 ) );
266 
267         controller.setLocale( Locale.FRENCH );
268         assertEquals( "localeChanged on setLocale", 2, list.size() );
269         assertEquals( "locale at construction", Locale.FRENCH, list.get( 1 ) );
270     }
271 
272 
273     /***
274      *  Test setLocale() with a null locale
275      */
276     public void testSetLocale_Null() {
277         final AbstractUIController controller =
278             new AbstractUIController() {
279                 protected void localeChanged( final Locale locale ) {
280                 }
281 
282 
283                 protected void runImpl() {
284                 }
285             };
286 
287         try {
288             controller.setLocale( null );
289             fail( "Expected exception" );
290         }
291         catch( final NullPointerException e ) {
292             // Expected path
293         }
294     }
295 
296 
297     private void assertListsSame( final List expected, final List actual ) {
298         final int max = Math.min( expected.size(), actual.size() );
299 
300         int i;
301         for( i = 0; i < max; i++ ) {
302             assertEquals( "list[" + i + "]", expected.get( i ), actual.get( i ) );
303         }
304         assertEquals( "list.size", expected.size(), actual.size() );
305     }
306 
307 
308     private class LoggingUIController extends AbstractUIController {
309         private List list_ = Collections.synchronizedList( new ArrayList() );
310 
311         /*** @return An immutable copy of the logged information */
312         public List getList() {
313             return Collections.unmodifiableList( list_ );
314         }
315 
316         /*** @inheritDoc AbstractUIController#taskComplete(WorkerTask) */
317         public void taskComplete( final WorkerTask task ) {
318             list_.add( TASK_COMPLETE );
319             checkIsOnUIThread();
320             synchronized( this ) {
321                 this.notifyAll();
322             }
323         }
324 
325         /*** @inheritDoc AbstractUIController#taskSuccessful(WorkerTask) */
326         public void taskSuccessful( final WorkerTask task ) {
327             list_.add( TASK_SUCCESSFUL );
328             checkIsOnUIThread();
329         }
330 
331         /*** @inheritDoc AbstractUIController#taskErrorThrown(WorkerTask,Throwable) */
332         public void taskErrorThrown( final WorkerTask task, final Throwable t ) {
333             list_.add( TASK_ERROR_THROWN );
334             checkIsOnUIThread();
335             list_.add( t );
336         }
337 
338         /*** @inheritDoc AbstractUIController#taskExceptionThrown(WorkerTask,Exception) */
339         public void taskExceptionThrown( final WorkerTask task, final Exception e ) {
340             list_.add( TASK_EXCEPTION_THROWN );
341             checkIsOnUIThread();
342             list_.add( e );
343         }
344 
345         /***
346          * Perform testing of this task
347          * @param task The task to test
348          * @throws InterruptedException If the thread is interrupted
349          */
350         public synchronized void testTask( final WorkerTask task )
351             throws InterruptedException {
352             startTask( task );
353             this.wait( 5000 );
354         }
355 
356 
357         protected void localeChanged( final Locale locale ) {
358         }
359 
360 
361         protected void runImpl() {
362         }
363 
364 
365         private void checkIsOnUIThread() {
366             if( SwingUtilities.isEventDispatchThread() == false ) {
367                 list_.add( "Not on UI thread!" );
368             }
369         }
370     }
371 
372 
373     private class ThrowableTask extends WorkerTask {
374         private Throwable workerThrowable_;
375         private Throwable uiThrowable_;
376 
377 
378         /***
379          *  Create an instance of {1}
380          *
381          * @param  workerThrowable
382          * @param  uiThrowable
383          */
384         public ThrowableTask( final Throwable workerThrowable, final Throwable uiThrowable ) {
385             workerThrowable_ = workerThrowable;
386             uiThrowable_ = uiThrowable;
387         }
388 
389         /*** @inheritDoc AbstractUIController#runOnWorkerThread() */
390         public void runOnWorkerThread()
391             throws Exception {
392             throwIfNeeded( workerThrowable_ );
393         }
394 
395         /*** @inheritDoc AbstractUIController#runOnUIThread() */
396         public void runOnUIThread()
397             throws Exception {
398             throwIfNeeded( uiThrowable_ );
399         }
400 
401 
402         private void throwIfNeeded( final Throwable t )
403             throws Exception {
404             if( t != null ) {
405                 if( t instanceof Exception ) {
406                     throw ( Exception )t;
407                 }
408 
409                 throw ( Error )t;
410             }
411         }
412     }
413 }
414