/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.nodes; import java.lang.ref.*; import java.beans.beancontext.*; import java.beans.IntrospectionException; import java.util.*; import org.openide.ErrorManager; /** Class that represents bean children of a JavaBeans context. * It listens on the bean context changes and creates nodes for * child beans. By default {@link BeanNode}s are created for all * child beans, but this behaviour can be changed by * providing a different factory to the constructor. * * @author Jaroslav Tulach, Jesse Glick */ public class BeanChildren extends Children.Keys { /** default factory for creation of children */ private static final Factory DEFAULT_FACTORY = new BeanFactory (); /** bean context to work on */ private BeanContext bean; /** factory for creation of subnodes */ private Factory factory; /** context listener */ private ContextL contextL; /** Map from nodes some BeanChildren have created, to the beans * they were intended to represent. If a node is deleted, we remove * the bean from its context. The nodes are weakly held, and each * value is a 2-element array of weak references to the bean context * and child, resp. * See #7925. */ private static final java.util.Map nodes2Beans = new WeakHashMap(); // Map<Node,Reference<Object>[2]> /** Create {@link BeanNode} children based on a Bean context. * @param bean the context */ public BeanChildren(BeanContext bean) { this (bean, DEFAULT_FACTORY); } /** Create children based on a Bean context. * @param bean the context * @param factory a factory to use for creation of child nodes */ public BeanChildren (BeanContext bean, Factory factory) { this.bean = bean; this.factory = factory; } /** Updates the keys from the bean context. */ final void updateKeys () { setKeys (bean.toArray ()); } /** Creates a node representant for given bean. Uses factory * to get the node. * @param subbean the bean from bean context * @return node created by the factory */ protected Node[] createNodes (Object subbean) { try { if (subbean instanceof BeanContextSupport) { BeanContextSupport bcs = (BeanContextSupport)subbean; if (bean.contains (bcs.getBeanContextPeer()) && bcs != bcs.getBeanContextPeer() ) { // sometimes a BeanContextSupport occures in the list of // beans children even there is its peer. we think that // it is desirable to hide the context if the peer is // also present return new Node[0]; } } Node n = factory.createNode(subbean); // #7925: deleting from BeanChildren has no effect synchronized (nodes2Beans) { nodes2Beans.put(n, new Reference[] {new WeakReference(bean), new WeakReference(subbean)}); } n.addNodeListener(contextL); return new Node[] {n}; } catch (IntrospectionException ex) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex); return new Node[0]; } } /* Initializes children and attaches listener to bean context. */ protected void addNotify () { // attaches a listener to the bean contextL = new ContextL (this); bean.addBeanContextMembershipListener (contextL); updateKeys (); } /** Removes the listener and does some necessary clean up. */ protected void removeNotify () { if (contextL != null) bean.removeBeanContextMembershipListener (contextL); contextL = null; setKeys (java.util.Collections.EMPTY_SET); } /** Controls which nodes * are created for a child bean. * @see BeanChildren#BeanChildren(BeanContext, BeanChildren.Factory) */ public static interface Factory { /** Create a node for a child bean. * @param bean the bean * @return the node for the bean * @exception IntrospectionException if the node cannot be created */ public Node createNode (Object bean) throws IntrospectionException; } /** Default factory. Creates BeanNode for each bean */ private static class BeanFactory extends Object implements Factory { BeanFactory() {} /** @return bean node */ public Node createNode (Object bean) throws IntrospectionException { return new BeanNode (bean); } } /** Context listener. */ private static final class ContextL extends NodeAdapter implements BeanContextMembershipListener { ContextL() {} /** weak reference to the BeanChildren object */ private WeakReference ref; /** Constructor */ ContextL (BeanChildren bc) { ref = new WeakReference (bc); } /** Listener method that is called when a bean is added to * the bean context. * @param bcme event describing the action */ public void childrenAdded (BeanContextMembershipEvent bcme) { BeanChildren bc = (BeanChildren)ref.get (); if (bc != null) { bc.updateKeys(); } } /** Listener method that is called when a bean is removed to * the bean context. * @param bcme event describing the action */ public void childrenRemoved (BeanContextMembershipEvent bcme) { BeanChildren bc = (BeanChildren)ref.get (); if (bc != null) { bc.updateKeys (); } } public void nodeDestroyed(NodeEvent ev) { Node n = ev.getNode(); Reference[] refs; synchronized (nodes2Beans) { refs = (Reference[])nodes2Beans.get(n); } if (refs != null) { BeanContext bean = (BeanContext)refs[0].get(); if (bean != null) { Object subbean = refs[1].get(); if (subbean != null) { // This should in turn cause childrenRemoved to be called... // and the node not to be recreated in the next keys update. try { bean.remove(subbean); } catch (RuntimeException re) { // BeanContext does not document what might be thrown // from this method, but in fact BeanContextSupport // can throw IllegalStateException if either child or // parent refuses the deletion. So better deal with it. ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, re); } } } } } } }