001 /*
002 * Copyright 2002-2004 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.richclient.util;
017
018 import java.awt.AWTEvent;
019 import java.awt.BorderLayout;
020 import java.awt.Component;
021 import java.awt.Dimension;
022 import java.awt.EventQueue;
023 import java.awt.LayoutManager;
024 import java.awt.Rectangle;
025 import java.awt.Toolkit;
026 import java.lang.ref.ReferenceQueue;
027 import java.lang.ref.PhantomReference;
028 import java.lang.reflect.InvocationTargetException;
029 import java.util.concurrent.FutureTask;
030 import java.util.concurrent.Callable;
031
032 import javax.swing.JComponent;
033 import javax.swing.JLabel;
034 import javax.swing.JPanel;
035 import javax.swing.JScrollPane;
036 import javax.swing.JTextField;
037 import javax.swing.Scrollable;
038 import javax.swing.JRootPane;
039 import javax.swing.JLayeredPane;
040 import javax.swing.SwingUtilities;
041
042 import org.springframework.richclient.test.SpringRichTestCase;
043
044 /**
045 * @author andy
046 * @since May 12, 2006 8:32:27 AM
047 */
048 public class OverlayHelperTests extends SpringRichTestCase {
049
050 /**
051 * OverlayHelper installs the overlay as the View of a JScrollPane viewport, if the component is in a JScrollPane,
052 * so that the overlay is shown in the proper location when scrolled. However, to accomplish this, it will remove
053 * the component that was in the viewport, add it to a JLayeredPane, and then add that JLayeredPane to the viewport
054 * instead. This introduced a bug if the viewport's view happened to implement the Scrollable interface, since
055 * JScrollPane does <i>not</i> implement the Scrollable interface. See issue RCP-344.
056 *
057 * @throws Exception
058 */
059 public void testRegressionScrollableProxy() throws Exception {
060 performScrollableTest();
061 performNonScrollableTest();
062 }
063
064
065 /**
066 * See <a href="http://opensource.atlassian.com/projects/spring/browse/RCP-492">RCP-492<a/>
067 * for details on this test and related bug.
068 *
069 * @throws Exception
070 */
071 public void testRegressionOverlayHelperLeak() throws Exception {
072 // Basically, this test will simulate adding a component to a
073 // JRootPane, then attach an overlay to it. The overlay will be
074 // installed into the layered pane of the JRootPane at the
075 // PALETTE_LAYER. This test will then remove the component from the
076 // JRootPane, ensure the overlay is removed from the layered pane,
077 // and then further ensure that nothing else holds a strong reference
078 // to the component (by waiting for the component to be garbage
079 // collected).
080 JComponent component = createTestComponent();
081 JComponent overlay = createTestOverlay();
082 final ReferenceQueue rq = new ReferenceQueue();
083 final PhantomReference componentRef = new PhantomReference(component, rq);
084
085 final JRootPane rootPane = new JRootPane() {
086 public boolean isVisible() {
087 return true;
088 }
089 public boolean isShowing() {
090 return true;
091 }
092 protected JLayeredPane createLayeredPane() {
093 return new JLayeredPane() {
094 public boolean isVisible() {
095 return true;
096 }
097 public boolean isShowing() {
098 return true;
099 }
100 };
101 }
102 };
103
104 final int lpcount = rootPane.getLayeredPane().getComponentCountInLayer(JLayeredPane.PALETTE_LAYER.intValue());
105
106 OverlayHelper.attachOverlay(overlay, component, 0, 0, 0);
107
108 assertEquals(lpcount, rootPane.getLayeredPane().getComponentCountInLayer(JLayeredPane.PALETTE_LAYER.intValue()));
109 rootPane.getContentPane().add(component);
110 // updateOverlay is "invokedLater", so wait for it...
111 waitUntilEventQueueIsEmpty();
112 assertEquals(lpcount + 1, rootPane.getLayeredPane().getComponentCountInLayer(JLayeredPane.PALETTE_LAYER.intValue()));
113
114 rootPane.getContentPane().remove(component);
115
116 // the remove operation make be invoked later, so wait for it...
117 waitUntilEventQueueIsEmpty();
118
119 // Clear out our strong references so it gets garbage collected.
120 component = null;
121 overlay = null;
122
123 // Make sure the overlay was removed from the layered pane.
124 assertEquals("It appears the overlay was not removed from the layered pane when its component was removed from the content pane", lpcount, rootPane.getLayeredPane().getComponentCountInLayer(JLayeredPane.PALETTE_LAYER.intValue()));
125
126 // Make sure no other references are held... wait up to 15 seconds for
127 // the object to be garbage collected.
128 // Note: we may want to remove this section from the test as it
129 // presumes the VM will garbage collect the reference fairly quickly
130 // once a System.gc() is invoked.
131 // There is no guarantee this will happen, so the assumption may be
132 // faulty. If it ends up being a problem, or starts yielding
133 // inconsistent test results, then feel free to yank it.
134 PhantomReference pr;
135 final long end = System.currentTimeMillis() + 15000;
136 do {
137 System.gc();
138 } while((pr = (PhantomReference)rq.remove(100)) == null && System.currentTimeMillis() < end);
139 if(pr != null) {
140 pr.clear();
141 }
142 assertSame("Either something else is still holding a strong reference to the component, or the VM is not garbage collecting it. See comments in OverlayHelperTests.testRegressionOverlayHelperLeak() for more detail", pr, componentRef);
143 }
144
145 /**
146 * Ensures that OverlayHelper supports the Scrollable interface and properly proxies Scrollable methods.
147 *
148 * @throws Exception
149 */
150 private void performScrollableTest() throws Exception {
151 final ScrollablePanel view = new ScrollablePanel(new BorderLayout());
152 view.setScrollableUnitIncrement(5);
153 view.setScrollableBlockIncrement(30);
154 view.setScrollableTracksViewportWidth(true);
155
156 final JComponent overlay = createTestOverlay();
157 final JComponent someField = createTestComponent();
158
159 OverlayHelper.attachOverlay(overlay, someField, 0, 0, 0);
160
161 view.add(someField);
162
163 final JScrollPane scrollPane = new JScrollPane(view);
164
165 waitUntilEventQueueIsEmpty();
166
167 final Component viewportView = scrollPane.getViewport().getView();
168
169 // If OverlayHelper changes the way it handles scrollable overlays,
170 // then the test will need to be revisited - this makes sure it
171 // won't get ignored. :)
172 // assertFalse(viewportView == view);
173
174 assertTrue(viewportView instanceof Scrollable);
175 assertTrue(((Scrollable) viewportView).getScrollableTracksViewportWidth());
176 assertFalse(((Scrollable) viewportView).getScrollableTracksViewportHeight());
177 assertEquals(5, ((Scrollable) viewportView).getScrollableUnitIncrement(null, 0, 0));
178 assertEquals(30, ((Scrollable) viewportView).getScrollableBlockIncrement(null, 0, 0));
179 assertEquals(view.getPreferredScrollableViewportSize(), ((Scrollable) viewportView)
180 .getPreferredScrollableViewportSize());
181 }
182
183 private void waitUntilEventQueueIsEmpty() throws InterruptedException, InvocationTargetException {
184 // we have to sleep here until the asynchronously attachement of JLayeredPane and the overlay is finished
185 EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
186 AWTEvent peekEvent;
187 while((peekEvent = eventQueue.peekEvent()) != null) {
188 System.out.println("got event in queue: " + peekEvent);
189 Thread.currentThread().sleep(0);
190 }
191 // Added 10/10/07 AlD: Sometimes the above loop fails to work!? This
192 // should further ensure the event loop is empty.
193 SwingUtilities.invokeAndWait(new Runnable() {
194 public void run() { }
195 });
196 }
197
198 /**
199 * Ensures that OverlayHelper will NOT implement the Scrollable interface if the view component does not implement
200 * the Scrollable interface.
201 *
202 * @throws Exception
203 */
204 private void performNonScrollableTest() throws Exception {
205 final JPanel view = new JPanel(new BorderLayout());
206 final JComponent overlay = createTestOverlay();
207 final JComponent someField = createTestComponent();
208
209 OverlayHelper.attachOverlay(overlay, someField, 0, 0, 0);
210
211 view.add(someField);
212
213 final JScrollPane scrollPane = new JScrollPane(view);
214
215 waitUntilEventQueueIsEmpty();
216
217 final Component viewportView = scrollPane.getViewport().getView();
218 // assertFalse(viewportView == view);
219 assertFalse(viewportView instanceof Scrollable);
220 }
221
222 public void testSwapScrollableForNonScrollable() throws Exception {
223 JComponent view = new ScrollablePanel(new BorderLayout());
224
225 final JComponent overlay = createTestOverlay();
226 final JComponent someField = createTestComponent();
227
228 OverlayHelper.attachOverlay(overlay, someField, 0, 0, 0);
229
230 view.add(someField);
231
232 final JScrollPane scrollPane = new JScrollPane(view);
233
234 waitUntilEventQueueIsEmpty();
235
236 Component viewportView = scrollPane.getViewport().getView();
237 // assertFalse(viewportView == view);
238 assertTrue(viewportView instanceof Scrollable);
239
240 view.remove(someField);
241 view = new JPanel(new BorderLayout());
242 view.add(someField);
243 scrollPane.setViewportView(view);
244
245 waitUntilEventQueueIsEmpty();
246
247 viewportView = scrollPane.getViewport().getView();
248 // assertFalse(viewportView == view);
249 assertFalse(viewportView instanceof Scrollable);
250
251 view.remove(someField);
252 view = new ScrollablePanel(new BorderLayout());
253 view.add(someField);
254 scrollPane.setViewportView(view);
255
256 waitUntilEventQueueIsEmpty();
257
258 viewportView = scrollPane.getViewport().getView();
259 // assertFalse(viewportView == view);
260 assertTrue(viewportView instanceof Scrollable);
261 }
262
263 private JComponent createTestComponent() {
264 return new JTextField("Hello, world!") {
265 // This is to force the OverlayHelper to install the overlay,
266 // even though we don't have a UI visible.
267 public boolean isVisible() {
268 return true;
269 }
270
271 public boolean isShowing() {
272 return true;
273 }
274 };
275 }
276
277 private JComponent createTestOverlay() {
278 final JComponent overlay = new JLabel("x") {
279 // This is to force the OverlayHelper to install the overlay,
280 // even though we don't have a UI visible.
281 public boolean isVisible() {
282 return true;
283 }
284
285 public boolean isShowing() {
286 return true;
287 }
288 };
289 overlay.setOpaque(false);
290 return overlay;
291 }
292
293 public static class ScrollablePanel extends JPanel implements Scrollable {
294 private int scrollableUnitIncrement = 10;
295
296 private int scrollableBlockIncrement = 40;
297
298 private boolean scrollableTracksViewportWidth = false;
299
300 private boolean scrollableTracksViewportHeight = false;
301
302 public ScrollablePanel(LayoutManager layout, boolean isDoubleBuffered) {
303 super(layout, isDoubleBuffered);
304 }
305
306 public ScrollablePanel(LayoutManager layout) {
307 super(layout);
308 }
309
310 public ScrollablePanel(boolean isDoubleBuffered) {
311 super(isDoubleBuffered);
312 }
313
314 public ScrollablePanel() {
315 }
316
317 public void setScrollableUnitIncrement(final int scrollableUnitIncrement) {
318 this.scrollableUnitIncrement = scrollableUnitIncrement;
319 }
320
321 public void setScrollableBlockIncrement(final int scrollableBlockIncrement) {
322 this.scrollableBlockIncrement = scrollableBlockIncrement;
323 }
324
325 public void setScrollableTracksViewportWidth(final boolean scrollableTracksViewportWidth) {
326 this.scrollableTracksViewportWidth = scrollableTracksViewportWidth;
327 }
328
329 public void setScrollableTracksViewportHeight(final boolean scrollableTracksViewportHeight) {
330 this.scrollableTracksViewportHeight = scrollableTracksViewportHeight;
331 }
332
333 public Dimension getPreferredScrollableViewportSize() {
334 return getPreferredSize();
335 }
336
337 public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
338 return this.scrollableUnitIncrement;
339 }
340
341 public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
342 return this.scrollableBlockIncrement;
343 }
344
345 public boolean getScrollableTracksViewportWidth() {
346 return this.scrollableTracksViewportWidth;
347 }
348
349 public boolean getScrollableTracksViewportHeight() {
350 return this.scrollableTracksViewportHeight;
351 }
352 }
353 }