I have a Swing action class which works as follows:
package org.trypticon.hex.gui.datatransfer;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.FlavorListener;
import java.awt.event.ActionEvent;
import javax.annotation.Nonnull;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.TransferHandler;
import org.trypticon.hex.gui.Resources;
import org.trypticon.hex.gui.util.FinalizeGuardian;
import org.trypticon.hex.gui.util.FocusedComponentAction;
public class PasteAction extends FocusedComponentAction {
private final FlavorListener listener = (event) -> {
// this method in the superclass calls back `shouldBeEnabled`
updateEnabled();
};
@SuppressWarnings({"UnusedDeclaration"})
private final Object finalizeGuardian = new FinalizeGuardian(() -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.removeFlavorListener(listener);
});
public PasteAction() {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.addFlavorListener(listener);
}
@Override
protected boolean shouldBeEnabled(@Nonnull JComponent focusOwner) {
TransferHandler transferHandler = focusOwner.getTransferHandler();
if (transferHandler == null) {
return false;
}
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
DataFlavor[] flavorsInClipboard = clipboard.getAvailableDataFlavors();
return transferHandler.canImport(focusOwner, flavorsInClipboard);
}
@Override
protected void doAction(@Nonnull JComponent focusOwner) throws Exception {
Action action = TransferHandler.getPasteAction();
action.actionPerformed(new ActionEvent(
focusOwner, ActionEvent.ACTION_PERFORMED, (String) action.getValue(Action.NAME)));
}
}
The FinalizeGuardian
referred to here is currently implemented using finalize()
:
package org.trypticon.hex.gui.util;
public final class FinalizeGuardian {
private final Runnable cleanupLogic;
public FinalizeGuardian(Runnable cleanupLogic) {
this.cleanupLogic = cleanupLogic;
}
@Override
protected final void finalize() throws Throwable {
try {
cleanupLogic.run();
} finally {
super.finalize();
}
}
}
So, for obvious reasons, I'd like to switch to using Cleaner
for this.
The first try was something like this:
package org.trypticon.hex.gui.util;
import java.lang.ref.Cleaner;
public final class FinalizeGuardian {
private static final Cleaner cleaner = Cleaner.create();
public FinalizeGuardian(Runnable cleanupLogic) {
cleaner.register(this, cleanupLogic);
}
}
The problem is that now the object never becomes phantom reachable, because:
Cleaner
itself holds a strong reference tocleanupLogic
cleanupLogic
holds a reference tolistener
in order to remove the listenerlistener
holds a reference to the action class in order to callupdateEnabled
on it- the action class holds a reference to the
FinalizeGuardian
so that it doesn't get collected prematurely
Because the FinalizeGuardian
itself never becomes phantom reachable, the cleaner will never be called.
So what I'd like to know is, is there a way to restructure this to follow the rules required to make Cleaner
work correctly that doesn't involve breaking encapsulation by moving the listener to outside my action class?