1 /*
2  * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.awt.Component;
25 import java.awt.Container;
26 import java.awt.EventQueue;
27 import java.awt.FlowLayout;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.concurrent.TimeUnit;
31 
32 import javax.swing.JButton;
33 import javax.swing.JCheckBox;
34 import javax.swing.JCheckBoxMenuItem;
35 import javax.swing.JComboBox;
36 import javax.swing.JComponent;
37 import javax.swing.JDesktopPane;
38 import javax.swing.JEditorPane;
39 import javax.swing.JFormattedTextField;
40 import javax.swing.JFrame;
41 import javax.swing.JInternalFrame;
42 import javax.swing.JLabel;
43 import javax.swing.JList;
44 import javax.swing.JMenu;
45 import javax.swing.JMenuBar;
46 import javax.swing.JMenuItem;
47 import javax.swing.JPanel;
48 import javax.swing.JPasswordField;
49 import javax.swing.JProgressBar;
50 import javax.swing.JRadioButton;
51 import javax.swing.JRadioButtonMenuItem;
52 import javax.swing.JScrollBar;
53 import javax.swing.JScrollPane;
54 import javax.swing.JSeparator;
55 import javax.swing.JSlider;
56 import javax.swing.JSpinner;
57 import javax.swing.JSplitPane;
58 import javax.swing.JTabbedPane;
59 import javax.swing.JTable;
60 import javax.swing.JTextArea;
61 import javax.swing.JTextField;
62 import javax.swing.JTextPane;
63 import javax.swing.JToggleButton;
64 import javax.swing.JToolBar;
65 import javax.swing.JToolTip;
66 import javax.swing.JTree;
67 import javax.swing.JViewport;
68 import javax.swing.SwingUtilities;
69 import javax.swing.UIManager.LookAndFeelInfo;
70 
71 import jdk.test.lib.process.OutputAnalyzer;
72 import jdk.test.lib.process.ProcessTools;
73 
74 import static javax.swing.UIManager.getInstalledLookAndFeels;
75 
76 /**
77  * @test
78  * @key headful
79  * @bug 8134947 8253977 8240709
80  * @library /test/lib
81  * @run main/timeout=450/othervm UnninstallUIMemoryLeaks
82  */
83 public final class UnninstallUIMemoryLeaks {
84 
85     private static JFrame frame;
86 
main(String[] args)87     public static void main(String[] args) throws Exception {
88         if (args.length == 0) {
89             long end = System.nanoTime() + TimeUnit.SECONDS.toNanos(400);
90             // run one task per look and feel
91             List<Process> tasks = new ArrayList<>();
92             for (LookAndFeelInfo laf : getInstalledLookAndFeels()) {
93                 String name = laf.getName();
94                 tasks.add(runProcess(laf));
95             }
96             for (Process p : tasks) {
97                 if (!p.waitFor(end - System.nanoTime(), TimeUnit.NANOSECONDS)) {
98                     p.destroyForcibly();
99                 }
100             }
101             for (Process task : tasks) {
102                 new OutputAnalyzer(task).shouldHaveExitValue(0)
103                                         .stderrShouldBeEmpty();
104             }
105             return;
106         }
107 
108         try {
109             createGUI();
110             long end = System.nanoTime() + TimeUnit.SECONDS.toNanos(350);
111             SwingUtilities.invokeAndWait(() -> {
112                 while (end > System.nanoTime()) {
113                     SwingUtilities.updateComponentTreeUI(frame);
114                 }
115                 checkListenersCount(frame);
116             });
117         } finally {
118             if (frame != null) {EventQueue.invokeAndWait(frame::dispose);}
119         }
120     }
121 
createGUI()122     private static void createGUI() throws Exception {
123         EventQueue.invokeAndWait(() -> {
124             frame = new JFrame();
125             //TODO we sometimes generate unnecessary repaint events
126             frame.setIgnoreRepaint(true);
127             frame.setLayout(new FlowLayout());
128 
129             frame.add(new JButton("JButton"));
130             frame.add(new JCheckBox("JCheckBox"));
131             frame.add(new JComboBox<>());
132             frame.add(new JEditorPane());
133             frame.add(new JFormattedTextField("JFormattedTextField"));
134             frame.add(new JLabel("label"));
135             frame.add(new JPanel());
136             frame.add(new JPasswordField("JPasswordField"));
137             frame.add(new JProgressBar());
138             frame.add(new JRadioButton("JRadioButton"));
139             frame.add(new JScrollBar());
140             frame.add(new JScrollPane());
141             frame.add(new JSeparator());
142             frame.add(new JSlider());
143             frame.add(new JSpinner());
144             frame.add(new JSplitPane());
145             frame.add(new JTabbedPane());
146             frame.add(new JTable());
147             frame.add(new JTextArea("JTextArea"));
148             frame.add(new JTextField("JTextField"));
149             frame.add(new JTextPane());
150             frame.add(new JToggleButton());
151             frame.add(new JToolBar());
152             frame.add(new JToolTip());
153             frame.add(new JTree());
154             frame.add(new JViewport());
155 
156             final JMenuBar bar = new JMenuBar();
157             final JMenu menu1 = new JMenu("menu1");
158             final JMenu menu2 = new JMenu("menu2");
159             menu1.add(new JMenuItem("menuitem"));
160             menu2.add(new JCheckBoxMenuItem("JCheckBoxMenuItem"));
161             menu2.add(new JRadioButtonMenuItem("JRadioButtonMenuItem"));
162             bar.add(menu1);
163             bar.add(menu2);
164             frame.setJMenuBar(bar);
165 
166             final String[] data = {"one", "two", "three", "four"};
167             final JList<String> list = new JList<>(data);
168             frame.add(list);
169 
170             final JDesktopPane pane = new JDesktopPane();
171             final JInternalFrame internalFrame = new JInternalFrame();
172             internalFrame.setBounds(10, 10, 130, 130);
173             internalFrame.setVisible(true);
174             pane.add(internalFrame);
175             pane.setSize(150, 150);
176 
177             frame.add(pane);
178             frame.pack();
179             frame.setSize(600, 600);
180             frame.setLocationRelativeTo(null);
181             // Commented to prevent a reference from RepaintManager
182             // frame.setVisible(true);
183         });
184     }
185 
checkListenersCount(Component comp)186     private static void checkListenersCount(Component comp) {
187         test(comp.getComponentListeners());
188         test(comp.getFocusListeners());
189         test(comp.getHierarchyListeners());
190         test(comp.getHierarchyBoundsListeners());
191         test(comp.getKeyListeners());
192         test(comp.getMouseListeners());
193         test(comp.getMouseMotionListeners());
194         test(comp.getMouseWheelListeners());
195         test(comp.getInputMethodListeners());
196         test(comp.getPropertyChangeListeners());
197         if (comp instanceof JComponent) {
198             test(((JComponent) comp).getAncestorListeners());
199             test(((JComponent) comp).getVetoableChangeListeners());
200         }
201         if (comp instanceof JMenuItem) {
202             test(((JMenuItem) comp).getMenuKeyListeners());
203             test(((JMenuItem) comp).getMenuDragMouseListeners());
204         }
205         if (comp instanceof JMenu) {
206             test(((JMenu) comp).getMenuListeners());
207         }
208         if(comp instanceof Container) {
209             for(Component child: ((Container)comp).getComponents()){
210                 checkListenersCount(child);
211             }
212         }
213     }
214 
215     /**
216      * Checks the count of specific listeners, assumes that the proper
217      * implementation does not use more than 20 listeners.
218      */
test(Object[] listeners)219     private static void test(Object[] listeners) {
220         int length = listeners.length;
221         if (length > 20) {
222             throw new RuntimeException("The count of listeners is: " + length);
223         }
224     }
225 
runProcess(LookAndFeelInfo laf)226     private static Process runProcess(LookAndFeelInfo laf) throws Exception {
227         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
228                 "-Dswing.defaultlaf=" + laf.getClassName(), "-mx9m",
229                 "-XX:+HeapDumpOnOutOfMemoryError",
230                 UnninstallUIMemoryLeaks.class.getSimpleName(), "mark");
231         return ProcessTools.startProcess(laf.getName(), pb);
232     }
233 }
234