/**
   * Remove a bundle listener. The listener is removed from the context bundle's list of listeners.
   * See {@link #getBundle() getBundle()} for a definition of context bundle.
   *
   * <p>If this method is called with a listener which is not registered, then this method does
   * nothing.
   *
   * @param listener The bundle listener to remove.
   * @exception java.lang.IllegalStateException If the bundle context has stopped.
   */
  public void removeBundleListener(BundleListener listener) {
    checkValid();
    if (listener == null) {
      throw new IllegalArgumentException();
    }

    if (Debug.DEBUG_EVENTS) {
      String listenerName =
          listener.getClass().getName()
              + "@"
              + Integer.toHexString(System.identityHashCode(listener)); // $NON-NLS-1$
      Debug.println(
          "removeBundleListener["
              + bundle
              + "]("
              + listenerName
              + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }

    framework.removeBundleListener(listener, this);
  }
  /**
   * Bottom level event dispatcher for the BundleContext.
   *
   * @param originalListener listener object registered under.
   * @param l listener to call (may be filtered).
   * @param action Event class type
   * @param object Event object
   */
  public void dispatchEvent(Object originalListener, Object l, int action, Object object) {
    // save the bundle ref to a local variable
    // to avoid interference from another thread closing this context
    AbstractBundle tmpBundle = bundle;
    Object previousTCCL = setContextFinder();
    try {
      if (isValid()) /* if context still valid */ {
        switch (action) {
          case Framework.BUNDLEEVENT:
          case Framework.BUNDLEEVENTSYNC:
            {
              BundleListener listener = (BundleListener) l;

              if (Debug.DEBUG_EVENTS) {
                String listenerName =
                    listener.getClass().getName()
                        + "@"
                        + Integer.toHexString(System.identityHashCode(listener)); // $NON-NLS-1$
                Debug.println(
                    "dispatchBundleEvent["
                        + tmpBundle
                        + "]("
                        + listenerName
                        + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
              }

              BundleEvent event = (BundleEvent) object;
              switch (event.getType()) {
                case Framework.BATCHEVENT_BEGIN:
                  {
                    if (listener instanceof BatchBundleListener)
                      ((BatchBundleListener) listener).batchBegin();
                    break;
                  }
                case Framework.BATCHEVENT_END:
                  {
                    if (listener instanceof BatchBundleListener)
                      ((BatchBundleListener) listener).batchEnd();
                    break;
                  }
                default:
                  {
                    listener.bundleChanged((BundleEvent) object);
                  }
              }
              break;
            }

          case ServiceRegistry.SERVICEEVENT:
            {
              ServiceEvent event = (ServiceEvent) object;

              ServiceListener listener = (ServiceListener) l;
              if (Debug.DEBUG_EVENTS) {
                String listenerName =
                    listener.getClass().getName()
                        + "@"
                        + Integer.toHexString(System.identityHashCode(listener)); // $NON-NLS-1$
                Debug.println(
                    "dispatchServiceEvent["
                        + tmpBundle
                        + "]("
                        + listenerName
                        + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
              }
              listener.serviceChanged(event);

              break;
            }

          case Framework.FRAMEWORKEVENT:
            {
              FrameworkListener listener = (FrameworkListener) l;

              if (Debug.DEBUG_EVENTS) {
                String listenerName =
                    listener.getClass().getName()
                        + "@"
                        + Integer.toHexString(System.identityHashCode(listener)); // $NON-NLS-1$
                Debug.println(
                    "dispatchFrameworkEvent["
                        + tmpBundle
                        + "]("
                        + listenerName
                        + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
              }

              listener.frameworkEvent((FrameworkEvent) object);
              break;
            }
          default:
            {
              throw new InternalError();
            }
        }
      }
    } catch (Throwable t) {
      if (Debug.DEBUG_GENERAL) {
        Debug.println(
            "Exception in bottom level event dispatcher: " + t.getMessage()); // $NON-NLS-1$
        Debug.printStackTrace(t);
      }
      // allow the adaptor to handle this unexpected error
      framework.adaptor.handleRuntimeError(t);
      publisherror:
      {
        if (action == Framework.FRAMEWORKEVENT) {
          FrameworkEvent event = (FrameworkEvent) object;
          if (event.getType() == FrameworkEvent.ERROR) {
            break publisherror; // avoid infinite loop
          }
        }

        framework.publishFrameworkEvent(FrameworkEvent.ERROR, tmpBundle, t);
      }
    } finally {
      if (previousTCCL != Boolean.FALSE)
        Thread.currentThread().setContextClassLoader((ClassLoader) previousTCCL);
    }
  }