001    /* BeanContextSupport.java --
002       Copyright (C) 2003, 2005, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.beans.beancontext;
040    
041    import java.beans.Beans;
042    import java.beans.DesignMode;
043    import java.beans.PropertyChangeEvent;
044    import java.beans.PropertyChangeListener;
045    import java.beans.PropertyVetoException;
046    import java.beans.VetoableChangeListener;
047    import java.beans.Visibility;
048    import java.io.IOException;
049    import java.io.InputStream;
050    import java.io.ObjectInputStream;
051    import java.io.ObjectOutputStream;
052    import java.io.Serializable;
053    import java.net.URL;
054    import java.util.ArrayList;
055    import java.util.Collection;
056    import java.util.HashMap;
057    import java.util.Iterator;
058    import java.util.List;
059    import java.util.Locale;
060    
061    /**
062     * This is a helper class for implementing a bean context.  It is
063     * intended to be used either by subclassing or by calling methods
064     * of this implementation from another.
065     *
066     * @author Michael Koch
067     * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
068     * @since 1.2
069     */
070    public class BeanContextSupport extends BeanContextChildSupport
071      implements BeanContext, Serializable, PropertyChangeListener,
072      VetoableChangeListener
073    {
074      private static final long serialVersionUID = -4879613978649577204L;
075    
076      /**
077       * Deserializes a stored bean context.  Hook methods are provided to allow
078       * subclasses to perform their own deserialization after the default
079       * deserialization but prior to the deserialization of the children.  Note that
080       * {@link #readChildren(ObjectInputStream)} is only called if there
081       * is no distinct peer.  If there is, the peer is expected to call
082       * the method instead.
083       *
084       * @param s the stream to deserialize.
085       * @throws ClassNotFoundException if the class of an object being deserialized
086       *                                could not be found.
087       * @throws IOException if an I/O error occurs.
088       */
089      private void readObject (ObjectInputStream s)
090        throws ClassNotFoundException, IOException
091      {
092        s.defaultReadObject();
093        bcsPreDeserializationHook(s);
094        BeanContext peer = getBeanContextPeer();
095        if (peer == null || peer == this)
096          readChildren(s);
097      }
098    
099      /**
100       * Serializes a bean context.  Hook methods are provided to allow
101       * subclasses to perform their own serialization after the default
102       * serialization but prior to serialization of the children.  Note that
103       * {@link #writeChildren(ObjectOutputStream)} is only called if there
104       * is no distinct peer.  If there is, the peer is expected to call
105       * the method instead.
106       *
107       * @param s the stream to serialize.
108       * @throws ClassNotFoundException if the class of an object being deserialized
109       *                                could not be found.
110       * @throws IOException if an I/O error occurs.
111       */
112      private void writeObject (ObjectOutputStream s)
113        throws ClassNotFoundException, IOException
114      {
115        serializing = true;
116        s.defaultWriteObject();
117        bcsPreSerializationHook(s);
118        BeanContext peer = getBeanContextPeer();
119        if (peer == null || peer == this)
120          writeChildren(s);
121        serializing = false;
122      }
123    
124      protected class BCSChild implements Serializable
125      {
126        private static final long serialVersionUID = -5815286101609939109L;
127    
128        private Object targetChild;
129        private Object peer;
130    
131        BCSChild(Object targetChild, Object peer)
132        {
133          this.targetChild = targetChild;
134          this.peer = peer;
135        }
136    
137        private Object getTargetChild()
138        {
139          return targetChild;
140        }
141    
142      }
143    
144      protected static final class BCSIterator implements Iterator
145      {
146        private Iterator child;
147    
148        BCSIterator(Iterator child)
149        {
150          this.child = child;
151        }
152    
153        public boolean hasNext ()
154        {
155          return child.hasNext();
156        }
157    
158        public Object next ()
159        {
160          return child.next();
161        }
162    
163        public void remove ()
164        {
165          // This must be a noop remove operation.
166        }
167      }
168    
169      protected transient ArrayList bcmListeners;
170    
171      protected transient HashMap children;
172    
173      protected transient boolean designTime;
174    
175      protected transient Locale locale;
176    
177      protected transient boolean okToUseGui;
178    
179      private transient boolean serializing;
180    
181      /**
182       * Construct a BeanContextSupport instance.
183       */
184      public BeanContextSupport ()
185      {
186        this (null, null, false, true);
187      }
188    
189      /**
190       * Construct a BeanContextSupport instance.
191       * 
192       * @param peer  the bean context peer (<code>null</code> permitted).
193       */
194      public BeanContextSupport(BeanContext peer)
195      {
196        this (peer, null, false, true);
197      }
198    
199      /**
200       * Construct a BeanContextSupport instance.
201       * 
202       * @param peer  the bean context peer (<code>null</code> permitted).
203       * @param locale  the locale (<code>null</code> permitted, equivalent to 
204       *     the default locale).
205       */
206      public BeanContextSupport (BeanContext peer, Locale locale)
207      {
208        this (peer, locale, false, true);
209      }
210    
211      /**
212       * Construct a BeanContextSupport instance.
213       * 
214       * @param peer  the bean context peer (<code>null</code> permitted).
215       * @param locale  the locale (<code>null</code> permitted, equivalent to 
216       *     the default locale).
217       * @param dtime  a flag indicating whether or not the bean context is in
218       *     design time mode.
219       */
220      public BeanContextSupport (BeanContext peer, Locale locale, boolean dtime)
221      {
222        this (peer, locale, dtime, true);
223      }
224    
225      /**
226       * Construct a BeanContextSupport instance.
227       * 
228       * @param peer  the bean context peer (<code>null</code> permitted).
229       * @param locale  the locale (<code>null</code> permitted, equivalent to 
230       *     the default locale).
231       * @param dtime  a flag indicating whether or not the bean context is in
232       *     design time mode.
233       * @param visible  initial value of the <code>okToUseGui</code> flag.
234       */
235      public BeanContextSupport (BeanContext peer, Locale locale, boolean dtime,
236                                 boolean visible)
237      {
238        super(peer);
239    
240        this.locale = locale == null ? Locale.getDefault() : locale;
241        designTime = dtime;
242        okToUseGui = visible;
243    
244        initialize ();
245      }
246    
247      /**
248       * <p>
249       * Add a child to the bean context.  A child can be a simple
250       * <code>Object</code>, a <code>BeanContextChild</code>
251       * or another <code>BeanContext</code>.  
252       * </p>
253       * <p>
254       * The children of a <code>BeanContext</code> form a set.  As
255       * a result, this method returns <code>false</code> if the given
256       * object is already a child of this context.
257       * </p>
258       * <p>
259       * If the child is a <code>BeanContextChild</code>, or a proxy
260       * for such a child, the <code>setBeanContext()</code> method
261       * is invoked on the child.  If this operation is vetoed by the
262       * child, via throwing a <code>PropertyVetoException</code>,
263       * then the current completion state of the <code>add()</code>
264       * operation is rolled back and a <code>IllegalStateException</code>
265       * is thrown.  If the <code>BeanContextChild</code> is successfully
266       * added, then the context registers with its
267       * <code>PropertyChangeListener</code> and
268       * <code>VetoableChangeListener</code> for "beanContext" events.
269       * </p>
270       * <p>
271       * If the child implements <code>java.beans.Visibility</code>,
272       * then its ability to use a GUI is set based on that of
273       * this context.
274       * </p>
275       * <p> 
276       * A <code>BeanContextMembershipEvent</code> is fired when the
277       * child is successfully added to the bean context.
278       * </p>
279       * <p>
280       * This method is synchronized over the global hierarchy lock.
281       * </p>
282       *
283       * @param targetChild the child to add.
284       * @return false if the child has already been added.
285       * @throws IllegalArgumentException if the child is null.
286       * @throws IllegalStateException if the child vetos the setting
287       *                               of its context.
288       */
289      public boolean add(Object targetChild)
290      {
291        synchronized (globalHierarchyLock)
292          {
293            if (targetChild == null)
294              throw new IllegalArgumentException();
295    
296            BCSChild child;
297            synchronized (children)
298              {
299                if (children.containsKey(targetChild)
300                    || ! validatePendingAdd(targetChild))
301                  return false;
302                child = createBCSChild(targetChild, beanContextChildPeer);
303                children.put(targetChild, child);
304              }
305            synchronized (targetChild)
306              {
307                BeanContextChild bcChild = null;
308                if (targetChild instanceof BeanContextChild)
309                  bcChild = (BeanContextChild) targetChild;
310                if (targetChild instanceof BeanContextProxy)
311                  bcChild = ((BeanContextProxy) targetChild).getBeanContextProxy();
312                if (bcChild != null)
313                  try
314                    {
315                      bcChild.setBeanContext(this);
316                      bcChild.addVetoableChangeListener("beanContext", this);
317                      bcChild.addPropertyChangeListener("beanContext", this);
318                    }
319                  catch (PropertyVetoException e)
320                    {
321                      synchronized (children)
322                        {
323                          children.remove(targetChild);
324                        }
325                      throw new IllegalStateException("The child refused to " +
326                                                      "associate itself with " +
327                                                      "this context.", e);
328                    }
329                if (targetChild instanceof Visibility)
330                  {
331                    Visibility visibleChild = (Visibility) targetChild;
332                    if (okToUseGui)
333                      visibleChild.okToUseGui();
334                    else
335                      visibleChild.dontUseGui();
336                  }
337                childJustAddedHook(targetChild, child);
338              }
339            fireChildrenAdded(new BeanContextMembershipEvent(this,
340                                                             new Object[]{ targetChild }));
341            return true;
342          }
343      }
344    
345      public boolean addAll (Collection c)
346      {
347        // Intentionally throws an exception.
348        throw new UnsupportedOperationException();
349      }
350    
351      public void addBeanContextMembershipListener
352        (BeanContextMembershipListener listener)
353      {
354        synchronized (bcmListeners)
355          {
356            if (! bcmListeners.contains(listener))
357              bcmListeners.add(listener);
358          }
359      }
360    
361      /**
362       * Returns true if this bean needs a GUI
363       * but is being prevented from using one.
364       *
365       * @return true if <code>needsGui()</code>
366       *              is true but the bean has been
367       *              told not to use it.
368       */
369      public boolean avoidingGui()
370      {
371        return needsGui() && (!okToUseGui);
372      }
373    
374      protected Iterator bcsChildren ()
375      {
376        synchronized (children)
377          {
378            return new BCSIterator(children.values().iterator());
379          }
380      }
381    
382      /**
383       * Subclasses may use this method to perform their own deserialization
384       * after the default deserialization process has taken place, but
385       * prior to the deserialization of the children.  It should not
386       * be used to replace the implementation of <code>readObject</code>
387       * in the subclass.
388       *
389       * @param ois the input stream.
390       * @throws ClassNotFoundException if the class of an object being deserialized
391       *                                could not be found.
392       * @throws IOException if an I/O error occurs.
393       */
394      protected void bcsPreDeserializationHook (ObjectInputStream ois)
395        throws ClassNotFoundException, IOException
396      {
397        /* Purposefully left empty */
398      }
399    
400      /**
401       * Subclasses may use this method to perform their own serialization
402       * after the default serialization process has taken place, but
403       * prior to the serialization of the children.  It should not
404       * be used to replace the implementation of <code>writeObject</code>
405       * in the subclass.
406       *
407       * @param oos the output stream.
408       * @throws IOException if an I/O error occurs.
409       */
410      protected void bcsPreSerializationHook (ObjectOutputStream oos)
411        throws IOException
412      {
413        /* Purposefully left empty */
414      }
415    
416      /**
417       * Called when a child is deserialized.
418       * 
419       * @param child the deserialized child.
420       * @param bcsc the deserialized context wrapper for the child.
421       */
422      protected void childDeserializedHook (Object child, BeanContextSupport.BCSChild bcsc)
423      {
424        // Do nothing in the base class.
425      }
426    
427      protected void childJustAddedHook (Object child, BeanContextSupport.BCSChild bcsc)
428      {
429        // Do nothing in the base class.
430      }
431    
432      protected void childJustRemovedHook (Object child, BeanContextSupport.BCSChild bcsc)
433      {
434        // Do nothing in the base class.
435      }
436    
437      protected static final boolean classEquals (Class first, Class second)
438      {
439        // Lame function!
440        return (first == second || first.getName().equals(second.getName()));
441      }
442    
443      public void clear ()
444      {
445        // This is the right thing to do.
446        // The JDK docs are really bad here.
447        throw new UnsupportedOperationException();
448      }
449    
450      public boolean contains (Object o)
451      {
452        synchronized (children)
453          {
454            return children.containsKey(o);
455          }
456      }
457    
458      public boolean containsAll (Collection c)
459      {
460        synchronized (children)
461          {
462            Iterator it = c.iterator();
463            while (it.hasNext())
464              if (! children.containsKey(it.next()))
465                return false;
466          }
467        return true;
468      }
469    
470      public boolean containsKey (Object o)
471      {
472        synchronized (children)
473          {
474            return children.containsKey(o);
475          }
476      }
477    
478      protected final Object[] copyChildren ()
479      {
480        synchronized (children)
481          {
482            return children.keySet().toArray();
483          }
484      }
485    
486      protected BeanContextSupport.BCSChild createBCSChild (Object targetChild, Object peer)
487      {
488        return new BCSChild(targetChild, peer);
489      }
490    
491      /**
492       * Deserializes objects (written by {@link #serialize(ObjectOutputStream, 
493       * Collection)}) and adds them to the specified collection.
494       * 
495       * @param ois  the input stream (<code>null</code> not permitted).
496       * @param coll  the collection to add the objects to (<code>null</code> not
497       *     permitted).
498       *     
499       * @throws ClassNotFoundException
500       * @throws IOException
501       * 
502       * @see #serialize(ObjectOutputStream, Collection)
503       */
504      protected final void deserialize (ObjectInputStream ois, Collection coll)
505        throws ClassNotFoundException, IOException
506      {
507        int itemCount = ois.readInt();
508        for (int i = 0; i < itemCount; i++)
509          coll.add(ois.readObject());
510      }
511    
512      /**
513       * Informs this bean that is should not make
514       * use of the GUI.
515       */
516      public void dontUseGui()
517      {
518        okToUseGui = false;
519      }
520    
521      protected final void fireChildrenAdded (BeanContextMembershipEvent bcme)
522      {
523        synchronized (bcmListeners)
524          {
525            Iterator it = bcmListeners.iterator();
526            while (it.hasNext())
527              {
528                BeanContextMembershipListener l
529                  = (BeanContextMembershipListener) it.next();
530                l.childrenAdded(bcme);
531              }
532          }
533      }
534    
535      protected final void fireChildrenRemoved (BeanContextMembershipEvent bcme)
536      {
537        synchronized (bcmListeners)
538          {
539            Iterator it = bcmListeners.iterator();
540            while (it.hasNext())
541              {
542                BeanContextMembershipListener l
543                = (BeanContextMembershipListener) it.next();
544                l.childrenRemoved(bcme);
545              }
546          }
547      }
548    
549      /**
550       * Returns the bean context peer.
551       * 
552       * @return The bean context peer.
553       * 
554       * @see BeanContextChildSupport#beanContextChildPeer
555       */
556      public BeanContext getBeanContextPeer()
557      {
558        return (BeanContext) beanContextChildPeer;
559      }
560    
561      /**
562       * Returns the {@link BeanContextChild} implementation for the given child.
563       * 
564       * @param child  the child (<code>null</code> permitted).
565       * 
566       * @return The bean context child.
567       * 
568       * @throws IllegalArgumentException if <code>child</code> implements both
569       *     the {@link BeanContextChild} and {@link BeanContextProxy} interfaces.
570       */
571      protected static final BeanContextChild getChildBeanContextChild(Object child)
572      {
573        if (child == null)
574          return null;
575        if (child instanceof BeanContextChild && child instanceof BeanContextProxy)
576          throw new IllegalArgumentException("Child cannot implement " 
577              + "BeanContextChild and BeanContextProxy simultaneously.");
578        if (child instanceof BeanContextChild)
579          return (BeanContextChild) child;
580        if (child instanceof BeanContextProxy)
581          return ((BeanContextProxy) child).getBeanContextProxy();
582        return null;
583      }
584    
585      /**
586       * Returns <code>child</code> as an instance of 
587       * {@link BeanContextMembershipListener}, or <code>null</code> if 
588       * <code>child</code> does not implement that interface.
589       * 
590       * @param child  the child (<code>null</code> permitted).
591       * 
592       * @return The child cast to {@link BeanContextMembershipListener}.
593       */
594      protected static final BeanContextMembershipListener 
595          getChildBeanContextMembershipListener(Object child)
596      {
597        if (child instanceof BeanContextMembershipListener) 
598          return (BeanContextMembershipListener) child;
599        else 
600          return null;
601      }
602    
603      /**
604       * Returns <code>child</code> as an instance of 
605       * {@link PropertyChangeListener}, or <code>null</code> if <code>child</code>
606       * does not implement that interface.
607       * 
608       * @param child  the child (<code>null</code> permitted).
609       * 
610       * @return The child cast to {@link PropertyChangeListener}.
611       */
612      protected static final PropertyChangeListener getChildPropertyChangeListener(
613          Object child)
614      {
615        if (child instanceof PropertyChangeListener) 
616          return (PropertyChangeListener) child;
617        else 
618          return null;
619      }
620    
621      /**
622       * Returns <code>child</code> as an instance of {@link Serializable}, or 
623       * <code>null</code> if <code>child</code> does not implement that 
624       * interface.
625       * 
626       * @param child  the child (<code>null</code> permitted).
627       * 
628       * @return The child cast to {@link Serializable}.
629       */
630      protected static final Serializable getChildSerializable(Object child)
631      {
632        if (child instanceof Serializable) 
633          return (Serializable) child;
634        else 
635          return null;
636      }
637    
638      /**
639       * Returns <code>child</code> as an instance of 
640       * {@link VetoableChangeListener}, or <code>null</code> if <code>child</code>
641       * does not implement that interface.
642       * 
643       * @param child  the child (<code>null</code> permitted).
644       * 
645       * @return The child cast to {@link VetoableChangeListener}.
646       */
647      protected static final VetoableChangeListener getChildVetoableChangeListener(
648          Object child)
649      {
650        if (child instanceof VetoableChangeListener) 
651          return (VetoableChangeListener) child;
652        else 
653          return null;
654      }
655    
656      /**
657       * Returns <code>child</code> as an instance of {@link Visibility}, or
658       * <code>null</code> if <code>child</code> does not implement that interface.
659       * 
660       * @param child  the child (<code>null</code> permitted).
661       * 
662       * @return The child cast to {@link Visibility}.
663       */
664      protected static final Visibility getChildVisibility(Object child)
665      {
666        if (child instanceof Visibility) 
667          return (Visibility) child;
668        else 
669          return null;
670      }
671    
672      public Locale getLocale ()
673      {
674        return locale;
675      }
676    
677      public URL getResource (String name, BeanContextChild bcc)
678      {
679        if (! contains(bcc))
680          throw new IllegalArgumentException("argument not a child");
681        ClassLoader loader = bcc.getClass().getClassLoader();
682        return (loader == null ? ClassLoader.getSystemResource(name)
683                : loader.getResource(name));
684      }
685    
686      public InputStream getResourceAsStream (String name, BeanContextChild bcc)
687      {
688        if (! contains(bcc))
689          throw new IllegalArgumentException("argument not a child");
690        ClassLoader loader = bcc.getClass().getClassLoader();
691        return (loader == null ? ClassLoader.getSystemResourceAsStream(name)
692                : loader.getResourceAsStream(name));
693      }
694    
695      protected void initialize ()
696      {
697        bcmListeners = new ArrayList();
698        children = new HashMap();
699      }
700    
701      /**
702       * This is a convenience method for instantiating a bean inside this
703       * context.  It delegates to the appropriate method in
704       * <code>java.beans.Beans</code> using the context's classloader.
705       *
706       * @param beanName the name of the class of bean to instantiate.
707       * @throws IOException if an I/O error occurs in loading the class.
708       * @throws ClassNotFoundException if the class, <code>beanName</code>,
709       *                                can not be found.
710       */
711      public Object instantiateChild (String beanName)
712        throws IOException, ClassNotFoundException
713      {
714        return Beans.instantiate(getClass().getClassLoader(), beanName, this);
715      }
716    
717      /**
718       * Returns <code>true</code> if the <code>BeanContext</code> is in 
719       * design time mode, and <code>false</code> if it is in runtime mode.
720       * 
721       * @return A boolean.
722       * 
723       * @see #setDesignTime(boolean)
724       */
725      public boolean isDesignTime()
726      {
727        return designTime;
728      }
729    
730      /**
731       * Returns true if this bean context has no children.
732       *
733       * @return true if there are no children.
734       */
735      public boolean isEmpty ()
736      {
737        synchronized (children)
738          {
739            return children.isEmpty();
740          }
741      }
742    
743      /**
744       * Returns true if the bean context is in the process
745       * of being serialized.
746       *
747       * @return true if the context is being serialized.
748       */
749      public boolean isSerializing()
750      {
751        return serializing;
752      }
753    
754      public Iterator iterator ()
755      {
756        synchronized (children)
757          {
758            return children.keySet().iterator();
759          }
760      }
761    
762      /**
763       * Returns false as this bean does not a
764       * GUI for its operation.
765       *
766       * @return false
767       */
768      public boolean needsGui()
769      {
770        return false;
771      }
772    
773      /**
774       * Informs this bean that it is okay to make use of
775       * the GUI.
776       */
777      public void okToUseGui ()
778      {
779        okToUseGui = true;
780      }
781    
782      /**
783       * Subclasses may use this method to catch property changes
784       * arising from the children of this context.  At present,
785       * we just listen for the beans being assigned to a different
786       * context and remove them from here if such an event occurs.
787       *
788       * @param pce the property change event.
789       */
790      public void propertyChange (PropertyChangeEvent pce)
791      {
792        if (pce.getNewValue() != this)
793          remove(pce.getSource(), false);
794      }
795    
796      /**
797       * Deerializes the children using the
798       * {@link #deserialize(ObjectInputStream, Collection} method
799       * and then calls {@link childDeserializedHook(Object, BCSChild)}
800       * for each child deserialized.
801       *
802       * @param oos the output stream.
803       * @throws IOException if an I/O error occurs.
804       */
805      public final void readChildren (ObjectInputStream ois)
806        throws IOException, ClassNotFoundException
807      {
808        List temp = new ArrayList();
809        deserialize(ois, temp);
810        Iterator i = temp.iterator();
811        synchronized (globalHierarchyLock)
812          {
813            synchronized (children)
814              {
815                while (i.hasNext())
816                  {
817                    BCSChild bcs = (BCSChild) i.next();
818                    childDeserializedHook(bcs.getTargetChild(), bcs);
819                    children.put(bcs.getTargetChild(), bcs);
820                  }
821              }
822          }
823      }
824    
825      /**
826       * Remove the specified child from the context.  This is
827       * the same as calling <code>remove(Object,boolean)</code>
828       * with a request for the <code>setBeanContext()</code> method
829       * of the child to be called (i.e. the second argument is true).
830       *
831       * @param targetChild the child to remove.
832       */
833      public boolean remove (Object targetChild)
834      {
835        return remove(targetChild, true);
836      }
837    
838      /**
839       * <p>
840       * Removes a child from the bean context.  A child can be a simple
841       * <code>Object</code>, a <code>BeanContextChild</code>
842       * or another <code>BeanContext</code>.  If the given child is not
843       * a child of this context, this method returns <code>false</code>.
844       * </p>
845       * <p>
846       * If the child is a <code>BeanContextChild</code>, or a proxy
847       * for such a child, the <code>setBeanContext()</code> method
848       * is invoked on the child (if specified).  If this operation is vetoed
849       * by the child, via throwing a <code>PropertyVetoException</code>,
850       * then the current completion state of the <code>remove()</code>
851       * operation is rolled back and a <code>IllegalStateException</code>
852       * is thrown.  If the <code>BeanContextChild</code> is successfully
853       * removed, then the context deregisters with its
854       * <code>PropertyChangeListener</code> and
855       * <code>VetoableChangeListener</code> for "beanContext" events.
856       * </p>
857       * <p> 
858       * A <code>BeanContextMembershipEvent</code> is fired when the
859       * child is successfully removed from the bean context.
860       * </p>
861       * <p>
862       * This method is synchronized over the global hierarchy lock.
863       * </p>
864       *
865       * @param targetChild the child to remove.
866       * @param callChildSetBC true if the <code>setBeanContext()</code>
867       *                       method of the child should be called.
868       * @return false if the child doesn't exist.
869       * @throws IllegalArgumentException if the child is null.
870       * @throws IllegalStateException if the child vetos the setting
871       *                               of its context.
872       */
873      protected boolean remove (Object targetChild, boolean callChildSetBC)
874      {
875        synchronized (globalHierarchyLock)
876          {
877            if (targetChild == null)
878              throw new IllegalArgumentException();
879    
880            BCSChild child;
881            synchronized (children)
882              {
883                if (!children.containsKey(targetChild)
884                    || !validatePendingRemove(targetChild))
885                  return false;
886                child = (BCSChild) children.remove(targetChild);
887              }
888            synchronized (targetChild)
889              {
890                BeanContextChild bcChild = null;
891                if (targetChild instanceof BeanContextChild)
892                  bcChild = (BeanContextChild) targetChild;
893                if (targetChild instanceof BeanContextProxy)
894                  bcChild = ((BeanContextProxy) targetChild).getBeanContextProxy();
895                if (bcChild != null)
896                  try
897                    {
898                      if (callChildSetBC)
899                        bcChild.setBeanContext(null);
900                      bcChild.removeVetoableChangeListener("beanContext", this);
901                      bcChild.removePropertyChangeListener("beanContext", this);
902                    }
903                  catch (PropertyVetoException e)
904                    {
905                      synchronized (children)
906                        {
907                          children.put(targetChild, child);
908                        }
909                      throw new IllegalStateException("The child refused to " +
910                                                      "disassociate itself with " +
911                                                      "this context.", e);
912                    }
913                childJustRemovedHook(targetChild, child);
914              }
915            fireChildrenRemoved(new BeanContextMembershipEvent(this,
916                                                             new Object[]{ targetChild }));
917            return true;
918          }
919      }
920    
921      public boolean removeAll (Collection c)
922      {
923        // Intentionally throws an exception.
924        throw new UnsupportedOperationException();
925      }
926    
927      public void removeBeanContextMembershipListener (BeanContextMembershipListener bcml)
928      {
929        synchronized (bcmListeners)
930          {
931            bcmListeners.remove(bcml);
932          }
933      }
934    
935      public boolean retainAll (Collection c)
936      {
937        // Intentionally throws an exception.
938        throw new UnsupportedOperationException();
939      }
940    
941      /**
942       * Writes the items in the collection to the specified output stream.  Items
943       * in the collection that are not instances of {@link Serializable} 
944       * (this includes <code>null</code>) are simply ignored.
945       * 
946       * @param oos  the output stream (<code>null</code> not permitted).
947       * @param coll  the collection (<code>null</code> not permitted).
948       * 
949       * @throws IOException
950       * 
951       * @see #deserialize(ObjectInputStream, Collection)
952       */
953      protected final void serialize(ObjectOutputStream oos, Collection coll)
954        throws IOException
955      {
956        Object[] items = coll.toArray();
957        int itemCount = 0;
958        for (int i = 0; i < items.length; i++)
959          {
960            if (items[i] instanceof Serializable)
961              itemCount++;
962          }
963        oos.writeInt(itemCount);
964        for (int i = 0; i < items.length; i++)
965          {
966            if (items[i] instanceof Serializable)
967              oos.writeObject(items[i]);
968          }
969      }
970    
971      /**
972       * Sets the flag that indicates whether or not the 
973       * <code>BeanContext</code> is in design mode.  If the flag changes
974       * value, a {@link PropertyChangeEvent} (with the property name 'designMode')
975       * is sent to registered listeners.  Note that the property name used here
976       * does NOT match the specification in the {@link DesignMode} interface, we
977       * match the reference implementation instead - see bug parade entry 4295174.
978       * 
979       * @param dtime  the new value for the flag.
980       * 
981       * @see #isDesignTime()
982       */
983      public void setDesignTime(boolean dtime)
984      {
985        boolean save = designTime;
986        designTime = dtime;
987        // note that we use the same property name as Sun's implementation,
988        // even though this is a known bug: see bug parade entry 4295174
989        firePropertyChange("designMode", Boolean.valueOf(save),
990                           Boolean.valueOf(dtime));
991      }
992    
993      public void setLocale (Locale newLocale)
994        throws PropertyVetoException
995      {
996        if (newLocale == null || locale == newLocale)
997          return;
998        fireVetoableChange("locale", locale, newLocale);
999        Locale oldLocale = locale;
1000        locale = newLocale;
1001        firePropertyChange("locale", oldLocale, newLocale);
1002      }
1003    
1004      public int size ()
1005      {
1006        synchronized (children)
1007          {
1008            return children.size();
1009          }
1010      }
1011    
1012      /**
1013       * Returns an array containing the children of this <code>BeanContext</code>.
1014       * 
1015       * @return An array containing the children.
1016       */
1017      public Object[] toArray()
1018      {
1019        synchronized (children)
1020          {
1021            return children.keySet().toArray();
1022          }
1023      }
1024    
1025      /**
1026       * Populates, then returns, the supplied array with the children of this 
1027       * <code>BeanContext</code>.  If the array is too short to hold the 
1028       * children, a new array is allocated and returned.  If the array is too 
1029       * long, it is padded with <code>null</code> items at the end.
1030       * 
1031       * @param array  an array to populate (<code>null</code> not permitted).
1032       */
1033      public Object[] toArray(Object[] array)
1034      {
1035        synchronized (children)
1036          {
1037            return children.keySet().toArray(array);
1038          }
1039      }
1040    
1041      protected boolean validatePendingAdd (Object targetChild)
1042      {
1043        return true;
1044      }
1045    
1046      protected boolean validatePendingRemove (Object targetChild)
1047      {
1048        return true;
1049      }
1050    
1051      /**
1052       * Subclasses may use this method to veto changes arising
1053       * from the children of this context.
1054       *
1055       * @param pce the vetoable property change event fired.
1056       */
1057      public void vetoableChange (PropertyChangeEvent pce)
1058        throws PropertyVetoException
1059      {
1060        /* Purposefully left empty */
1061      }
1062    
1063      /**
1064       * Serializes the children using the
1065       * {@link #serialize(ObjectOutputStream, Collection} method.
1066       *
1067       * @param oos the output stream.
1068       * @throws IOException if an I/O error occurs.
1069       */
1070      public final void writeChildren (ObjectOutputStream oos)
1071        throws IOException
1072      {
1073        synchronized (children)
1074          {
1075            serialize(oos, children.values());
1076          }
1077      }
1078    
1079    }