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 }