Home > Software engineering >  Injecting an Interface implementation in Swing objects
Injecting an Interface implementation in Swing objects

Time:10-05

I have defined a class InfoAction implements java.swing.Action that needs to retrieve some information from its calling context in order to execute itself. For this I have defined an interface for providing those information:

public interface InfoProvider {
    Info getInfo();
}

The actionPerformed in the InfoAction class looks like

public void actionPerformed(ActionEvent e) {
    Info info=e.getSource().getInfo();
    // ... treat Info ...
}

Then I want to create various Swing objects using these actions, every Swing object providing its own context to the action.

InfoAction infoAction = MyApp.getInstance().getAction(MyApp.RENAME_ACTION); // Retrieving one action
JButton button = new JButton(infoAction); // TODO: inject the InfoProvider interface
JMenuItem menuItem = new JMenuItem(infoAction); // TODO: inject the InfoProvider interface

I'm stuck at how to add to those swing objects the InfoProvider interface.

Naively I tried to write

InfoAction infoAction = MyApp.getInstance().getAction(MyApp.RENAME_ACTION);
JButton button = new JButton(infoAction) implements InfoProvider {
    Info getInfo() {
        return MyGui.this.getInfo();
        }
    };
JMenuItem menuItem = new JMenuItem(infoAction)  implements InfoProvider {
    Info getInfo() {
        return MyGui.this.getInfo();
        }
    };

But this is not a valid code.

I'd like to avoid creating sub-classes of all the Swing pieces such as private class InfoButton extends JButton implements InfoProvider {...}, private class InfoMenuItem extends JMenuItem implements InfoProvider {...}.

So I wonder what are the other options to do this while keeping a clean code ?
Would working with reflection a good option ? Getting rid of the interface and testing if the actionEvent.getSource() has a getInfo() method ?

PS: I'm working with Java8

CodePudding user response:

You don’t need to specialize every component supporting actions (or timer or other sources of action events). Instead, create one specialized Action implementation to mediate between the component/event source and the actual action. E.g.

public class InfoProviderAction implements Action, InfoProvider {
    final PropertyChangeSupport listeners = new PropertyChangeSupport(this);
    final PropertyChangeListener relay = ev -> listeners.firePropertyChange(
                        ev.getPropertyName(), ev.getOldValue(), ev.getNewValue());
    final Action target;
    final InfoProvider actualProvider;

    public InfoProviderAction(Action target, InfoProvider actualProvider) {
        this.target = target;
        this.actualProvider = actualProvider;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        target.actionPerformed(new ActionEvent(
            this, e.getID(), e.getActionCommand(), e.getWhen(), e.getModifiers()));
    }

    @Override
    public Info getInfo() {
        return actualProvider.getInfo();
    }

    @Override
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        boolean hadListeners = listeners.hasListeners(null);
        listeners.addPropertyChangeListener(listener);
        if(!hadListeners) target.addPropertyChangeListener(relay);
    }

    @Override
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        boolean hadListeners = listeners.hasListeners(null);
        listeners.removePropertyChangeListener(listener);
        if(hadListeners && !listeners.hasListeners(null))
            target.removePropertyChangeListener(relay);
    }

    @Override
    public boolean isEnabled() {
        return target.isEnabled();
    }

    @Override
    public void setEnabled(boolean b) {
        target.setEnabled(b);
    }

    @Override
    public Object getValue(String key) {
        return target.getValue(key);
    }

    @Override
    public void putValue(String key, Object value) {
        target.putValue(key, value);
    }
}

Then, your MyGui class can use it like

InfoAction infoAction = MyApp.getInstance().getAction(MyApp.RENAME_ACTION);
infoAction = new InfoProviderAction(infoAction, this::getInfo);// inject the InfoProvider
JButton button = new JButton(infoAction);
JMenuItem menuItem = new JMenuItem(infoAction);

The component will see the InfoProviderAction as its action, which will exhibit the same properties and behavior as the wrapped action. Whereas the actual action will see the InfoProviderAction as the even source, implementing the InfoProvider interface as expected.

Problems could arise if the action expects the source to be a component. But actions should not assume this, as it would cause problems with Timer or global key bindings as event sources.

CodePudding user response:

There is something you can use with Swing:

When you build your object, you can configure it:

class ComponentFactory {
  public static <T extends JComponent> T configure(T component) {
    // the MyGui.this won't compile here, but it is only to explain
    // what you can do with your current code.
    component.putClientProperty(InfoProvider.class, MyGui.this);
  }
}

And build object as in:

ComponentFactory.configure(new JButton(infoAction));

Or create a static method for it in ComponentFactory:

ComponentFactory.createButton(infoAction);

In your action, you can then read the property:

public void actionPerformed(ActionEvent e) {
  Object src = e.getSource();
  if (src instanceof JComponent) {
    JComponent c = (JComponent) src;
    
    Info info = (Info) c.getClientProperty(InfoProvider.class);
    ...
  }

}

Notes:

Since it is Swing, there is a some chance it requires to be Serializable - which I don't think is a problem in 2022.

And that the doc for putClientProperty says:

The clientProperty dictionary is not intended to support large scale extensions to JComponent nor should be it considered an alternative to subclassing when designing a new component.

It should be fine for one property, otherwise you would have to use subclassing and implements InfoProvider for each such component.

  • Related