001/* BeanContextSupport.java --
002   Copyright (C) 2003, 2005, 2006  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.beans.beancontext;
040
041import java.beans.Beans;
042import java.beans.DesignMode;
043import java.beans.PropertyChangeEvent;
044import java.beans.PropertyChangeListener;
045import java.beans.PropertyVetoException;
046import java.beans.VetoableChangeListener;
047import java.beans.Visibility;
048import java.io.IOException;
049import java.io.InputStream;
050import java.io.ObjectInputStream;
051import java.io.ObjectOutputStream;
052import java.io.Serializable;
053import java.net.URL;
054import java.util.ArrayList;
055import java.util.Collection;
056import java.util.HashMap;
057import java.util.Iterator;
058import java.util.List;
059import 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 */
070public 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   * Deserializes 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 ois the input 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}