Sunday September 13, 2009
More on @ConvertAsJavaBean
Improved version of previous blog entry on the new @ConvertAsJavaBean annotation.
- Create a bean with PropertyChangeSupport, annotated with @ConvertAsJavaBean:
@ConvertAsJavaBean() public class CountryBean { protected String name; protected String food; private PropertyChangeSupport propertyChangeSupport; public CountryBean() { propertyChangeSupport = new PropertyChangeSupport(this); } public String getFood() { return food; } public void setFood(String newValue) { String oldValue = this.food; this.food = newValue; propertyChangeSupport.firePropertyChange("food", oldValue, newValue); } public String getName() { return name; } public void setName(String newValue) { String oldValue = this.name; this.name = newValue; propertyChangeSupport.firePropertyChange("name", oldValue, newValue); } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } } - Create a root Node with an action that creates a new ".settings" file in a "Countries" folder whenever a new bean is created. It also opens the Properties sheet so that the values can be customized immediately. Changes made in the Properties sheet will automatically be persisted to the ".settings" file because of the PropertyChangeSupport on the bean.
class RootNode extends AbstractNode { public RootNode(Children kids) { super(kids); setDisplayName("Root"); } @Override public Action[] getActions(boolean bln) { Action[] actions = new Action[]{ new CreateNewCountryBeanAction() }; return actions; } public class CreateNewCountryBeanAction extends AbstractAction { public CreateNewCountryBeanAction() { putValue(Action.NAME, "Create new country"); } @Override public void actionPerformed(ActionEvent arg0) { CountryBean bean = new CountryBean(); bean.setName("state"); bean.setFood("meal"); try { NodeOperation.getDefault().showProperties(new BeanNode(bean)); InstanceDataObject ido = InstanceDataObject.create( DataFolder.findFolder(FileUtil.getConfigFile("Countries")), bean.getName(), bean, null, true ); } catch (Exception ex) { Exceptions.printStackTrace(ex); } } } } - Then set that root Node as the root context of an ExplorerManager, together with this ChildFactory, which builds child nodes based on CountryBeans found in the "Countries" folder:
public class CountryChildFactory extends ChildFactory<CountryBean> implements LookupListener { Result<CountryBean> res; public CountryChildFactory() { res = Lookups.forPath("Countries").lookupResult(CountryBean.class); res.addLookupListener(this); refresh(false); } @Override protected boolean createKeys(List<CountryBean> list) { list.addAll(res.allInstances()); return true; } @Override protected Node createNodeForKey(final CountryBean countryBean) { BeanNode countryNode = null; try { countryNode = new BeanNode(countryBean); } catch (IntrospectionException ex) { Exceptions.printStackTrace(ex); } countryNode.setDisplayName(countryBean.getName()); return countryNode; } @Override public void resultChanged(LookupEvent le) { refresh(false); } }
That's it. Thanks Jaroslav Tulach for this code.
Sep 13 2009, 01:42:33 AM PDT Permalink
I don't believe you need to (or should) call refresh in the ChildFactory constructor; if and when the node is expanded, you will be asked for keys.
FindBugs would remind you to return null (or, better, rethrow an AssertionError) when catching IntrospectionException; otherwise you would just immediately get an NPE.
I believe BeanNode will automatically pick up its name from a getName method.
putValue(Action.NAME, ...) in an AbstractAction constructor can just be replaced with a call to the super constructor taking String.
Posted by Jesse Glick on September 14, 2009 at 08:22 AM PDT #


