8240633: Memory leaks in the implementations of FileChooserUI

Reviewed-by: pbansal, psadhukhan
This commit is contained in:
Sergey Bylokhov 2020-03-12 10:00:23 +01:00
parent 4f1fabd8d0
commit b4863f9dd8
3 changed files with 233 additions and 24 deletions
src/java.desktop/macosx/classes/com/apple/laf
test/jdk/javax/swing/JFileChooser

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,20 +25,55 @@
package com.apple.laf;
import java.awt.*;
import java.awt.event.*;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import javax.accessibility.*;
import javax.swing.*;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleState;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ComboBoxEditor;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JRootPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import com.apple.laf.ClientPropertyApplicator.Property;
import apple.laf.JRSUIConstants.Size;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.ListUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import apple.laf.JRSUIConstants.Size;
import com.apple.laf.AquaUtilControlSize.Sizeable;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.ClientPropertyApplicator.Property;
// Inspired by MetalComboBoxUI, which also has a combined text-and-arrow button for noneditables
public class AquaComboBoxUI extends BasicComboBoxUI implements Sizeable {
@ -86,6 +121,11 @@ public class AquaComboBoxUI extends BasicComboBoxUI implements Sizeable {
protected void uninstallComponents() {
getApplicator().removeFrom(comboBox);
// AquaButtonUI install some listeners to all parents, which means that
// we need to uninstall UI here to remove those listeners, because after
// we remove them from ComboBox we lost the latest reference to them,
// and our standard uninstallUI machinery will not call them.
arrowButton.getUI().uninstallUI(arrowButton);
super.uninstallComponents();
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,22 +25,90 @@
package com.apple.laf;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.*;
import java.beans.*;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.net.URI;
import java.text.DateFormat;
import java.util.*;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.Vector;
import javax.swing.*;
import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.plaf.*;
import javax.swing.table.*;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.FileChooserUI;
import javax.swing.plaf.UIResource;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import sun.swing.SwingUtilities2;
@ -168,7 +236,6 @@ public class AquaFileChooserUI extends FileChooserUI {
if (propertyChangeListener != null) {
fc.addPropertyChangeListener(propertyChangeListener);
}
if (model != null) fc.addPropertyChangeListener(model);
ancestorListener = new AncestorListener(){
public void ancestorAdded(final AncestorEvent e) {
@ -196,6 +263,7 @@ public class AquaFileChooserUI extends FileChooserUI {
fc.removePropertyChangeListener(propertyChangeListener);
}
fFileList.removeMouseListener(doubleClickListener);
fc.removePropertyChangeListener(filterComboBoxModel);
fc.removePropertyChangeListener(model);
fc.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
fc.removeAncestorListener(ancestorListener);
@ -1741,7 +1809,17 @@ public class AquaFileChooserUI extends FileChooserUI {
return fButtonActions[which];
}
public void uninstallComponents(final JFileChooser fc) { //$ Metal (on which this is based) doesn't uninstall its components.
public void uninstallComponents(final JFileChooser fc) {
// AquaButtonUI install some listeners to all parents, which means that
// we need to uninstall UI here to remove those listeners, because after
// we remove them from FileChooser we lost the latest reference to them,
// and our standard uninstallUI machinery will not call them.
fApproveButton.getUI().uninstallUI(fApproveButton);
fOpenButton.getUI().uninstallUI(fOpenButton);
fNewFolderButton.getUI().uninstallUI(fNewFolderButton);
fCancelButton.getUI().uninstallUI(fCancelButton);
directoryComboBox.getUI().uninstallUI(directoryComboBox);
filterComboBox.getUI().uninstallUI(filterComboBox);
}
// Consistent with the AppKit NSSavePanel, clicks on a file (not a directory) should populate the text field

@ -0,0 +1,91 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.awt.EventQueue;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;
/**
* @test
* @bug 8240633
* @key headful
* @summary FileChooserUI delegates should clean listeners in JFileChooser
*/
public final class FileChooserListenerLeak {
public static void main(final String[] args) throws Exception {
EventQueue.invokeAndWait(()->{
JFileChooser chooser = new JFileChooser();
checkListenersCount(chooser);
LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
for (int i = 0; i < 100; ++i) {
for (LookAndFeelInfo installedLookAndFeel : infos) {
setLookAndFeel(installedLookAndFeel);
SwingUtilities.updateComponentTreeUI(chooser);
}
}
checkListenersCount(chooser);
});
}
private static void checkListenersCount(JFileChooser chooser) {
test(chooser.getComponentListeners());
test(chooser.getFocusListeners());
test(chooser.getHierarchyListeners());
test(chooser.getHierarchyBoundsListeners());
test(chooser.getKeyListeners());
test(chooser.getMouseListeners());
test(chooser.getMouseMotionListeners());
test(chooser.getMouseWheelListeners());
test(chooser.getInputMethodListeners());
test(chooser.getPropertyChangeListeners());
test(chooser.getAncestorListeners());
test(chooser.getVetoableChangeListeners());
}
/**
* Checks the count of specific listeners, assumes that the proper
* implementation does not use more than 10 listeners.
*/
private static void test(Object[] listeners) {
int length = listeners.length;
if (length > 10) {
throw new RuntimeException("The count of listeners is: " + length);
}
}
private static void setLookAndFeel(UIManager.LookAndFeelInfo laf) {
try {
UIManager.setLookAndFeel(laf.getClassName());
} catch (UnsupportedLookAndFeelException ignored){
System.out.println("Unsupported LookAndFeel: " + laf.getClassName());
} catch (ClassNotFoundException | InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}