/** @author Symphorien Wanko */
public class PopupMessageHandlerSLick extends TestSuite implements BundleActivator {
  /** Logger for this class */
  private static Logger logger = Logger.getLogger(PopupMessageHandlerSLick.class);

  /** our bundle context */
  protected static BundleContext bundleContext = null;

  /** implements BundleActivator.start() */
  public void start(BundleContext bc) throws Exception {
    logger.info("starting popup message test ");

    bundleContext = bc;

    setName("PopupMessageHandlerSLick");

    Hashtable<String, String> properties = new Hashtable<String, String>();

    properties.put("service.pid", getName());

    // we maybe are running on machine without WM and systray
    // (test server machine), skip tests
    if (ServiceUtils.getService(bc, SystrayService.class) != null) {
      addTest(TestPopupMessageHandler.suite());
    }

    bundleContext.registerService(getClass().getName(), this, properties);
  }

  /** implements BundleActivator.stop() */
  public void stop(BundleContext bc) throws Exception {}
}
/**
 * Bundle adds Android specific notification handlers.
 *
 * @author Pawel Domas
 */
public class NotificationActivator implements BundleActivator {
  /** The logger */
  private final Logger logger = Logger.getLogger(NotificationActivator.class);

  /** OSGI bundle context. */
  protected static BundleContext bundleContext;

  /** Notification service instance. */
  private static NotificationService notificationService;

  /** Vibrate handler instance. */
  private VibrateHandlerImpl vibrateHandler;

  /** {@inheritDoc} */
  public void start(BundleContext bc) throws Exception {
    bundleContext = bc;
    try {
      logger.logEntry();
      logger.info("Android notification handler Service...[  STARTED ]");

      // Get the notification service implementation
      ServiceReference notifReference =
          bundleContext.getServiceReference(NotificationService.class.getName());

      notificationService = (NotificationService) bundleContext.getService(notifReference);

      vibrateHandler = new VibrateHandlerImpl();

      notificationService.addActionHandler(vibrateHandler);

      logger.info("Android notification handler Service...[REGISTERED]");
    } finally {
      logger.logExit();
    }
  }

  /** {@inheritDoc} */
  public void stop(BundleContext bc) throws Exception {
    notificationService.removeActionHandler(vibrateHandler.getActionType());

    logger.info("Android notification handler Service ...[STOPPED]");
  }
}
/**
 * Activator for the Metacafe source bundle.
 *
 * @author Purvesh Sahoo
 */
public class MetacafeActivator implements BundleActivator {
  /** The <tt>Logger</tt> used by the <tt>MetacafeActivator</tt> class. */
  private static final Logger logger = Logger.getLogger(MetacafeActivator.class);

  /** The metacafe service registration. */
  private ServiceRegistration metacafeServReg = null;

  /** The source implementation reference. */
  private static ReplacementService metacafeSource = null;

  /**
   * Starts the Metacafe replacement source bundle
   *
   * @param context the <tt>BundleContext</tt> as provided from the OSGi framework
   * @throws Exception if anything goes wrong
   */
  public void start(BundleContext context) throws Exception {
    Hashtable<String, String> hashtable = new Hashtable<String, String>();
    hashtable.put(
        ReplacementService.SOURCE_NAME, ReplacementServiceMetacafeImpl.METACAFE_CONFIG_LABEL);
    metacafeSource = new ReplacementServiceMetacafeImpl();

    metacafeServReg =
        context.registerService(ReplacementService.class.getName(), metacafeSource, hashtable);

    logger.info("Metacafe source implementation [STARTED].");
  }

  /**
   * Unregisters the Metacafe replacement service.
   *
   * @param context BundleContext
   * @throws Exception if anything goes wrong
   */
  public void stop(BundleContext context) throws Exception {
    metacafeServReg.unregister();
    logger.info("Metacafe source implementation [STOPPED].");
  }
}
/**
 * The class listens for "focus joined room" and "conference created" events and adds the info about
 * all conference components versions to Jicofo's MUC presence.
 *
 * @author Pawel Domas
 */
public class VersionBroadcaster extends EventHandlerActivator {
  /** The logger */
  private static final Logger logger = Logger.getLogger(VersionBroadcaster.class);

  /** <tt>FocusManager</tt> instance used to access <tt>JitsiMeetConference</tt>. */
  private FocusManager focusManager;

  /** <tt>VersionService</tt> which provides Jicofo version. */
  private VersionService versionService;

  /** Jitsi Meet tools used to add packet extension to Jicofo presence. */
  private OperationSetJitsiMeetTools meetTools;

  /** Creates new instance of <tt>VersionBroadcaster</tt>. */
  public VersionBroadcaster() {
    super(new String[] {EventFactory.FOCUS_JOINED_ROOM_TOPIC, EventFactory.CONFERENCE_ROOM_TOPIC});
  }

  /** {@inheritDoc} */
  @Override
  public void start(BundleContext bundleContext) throws Exception {
    focusManager = ServiceUtils.getService(bundleContext, FocusManager.class);

    Assert.notNull(focusManager, "focusManager");

    versionService = ServiceUtils.getService(bundleContext, VersionService.class);

    Assert.notNull(versionService, "versionService");

    meetTools = focusManager.getOperationSet(OperationSetJitsiMeetTools.class);

    Assert.notNull(meetTools, "meetTools");

    super.start(bundleContext);
  }

  /** {@inheritDoc} */
  @Override
  public void stop(BundleContext bundleContext) throws Exception {
    super.stop(bundleContext);

    focusManager = null;
    versionService = null;
    meetTools = null;
  }

  /**
   * Handles {@link EventFactory#FOCUS_JOINED_ROOM_TOPIC} and {@link
   * EventFactory#CONFERENCE_ROOM_TOPIC}.
   *
   * <p>{@inheritDoc}
   */
  @Override
  public void handleEvent(Event event) {
    String topic = event.getTopic();
    if (!topic.equals(EventFactory.FOCUS_JOINED_ROOM_TOPIC)
        && !topic.equals(EventFactory.CONFERENCE_ROOM_TOPIC)) {
      logger.error("Unexpected event topic: " + topic);
      return;
    }

    String roomJid = (String) event.getProperty(EventFactory.ROOM_JID_KEY);

    JitsiMeetConference conference = focusManager.getConference(roomJid);
    if (conference == null) {
      logger.error("Conference is null");
      return;
    }

    ChatRoom chatRoom = conference.getChatRoom();
    if (chatRoom == null) {
      logger.error("Chat room is null");
      return;
    }

    JitsiMeetServices meetServices = focusManager.getJitsiMeetServices();
    ComponentVersionsExtension versionsExtension = new ComponentVersionsExtension();

    // XMPP
    Version xmppServerVersion = meetServices.getXMPPServerVersion();
    if (xmppServerVersion != null) {
      versionsExtension.addComponentVersion(
          ComponentVersionsExtension.COMPONENT_XMPP_SERVER,
          xmppServerVersion.getNameVersionOsString());
    }

    // Conference focus
    org.jitsi.service.version.Version jicofoVersion = versionService.getCurrentVersion();
    versionsExtension.addComponentVersion(
        ComponentVersionsExtension.COMPONENT_FOCUS,
        jicofoVersion.getApplicationName()
            + "("
            + jicofoVersion.toString()
            + ","
            + System.getProperty("os.name")
            + ")");

    // Videobridge
    // It is not be reported for FOCUS_JOINED_ROOM_TOPIC
    String bridgeJid = (String) event.getProperty(EventFactory.BRIDGE_JID_KEY);
    Version jvbVersion = bridgeJid == null ? null : meetServices.getBridgeVersion(bridgeJid);
    if (jvbVersion != null) {
      versionsExtension.addComponentVersion(
          ComponentVersionsExtension.COMPONENT_VIDEOBRIDGE, jvbVersion.getNameVersionOsString());
    }

    meetTools.sendPresenceExtension(chatRoom, versionsExtension);

    if (logger.isDebugEnabled()) logger.debug("Sending versions: " + versionsExtension.toXML());
  }
}
Exemple #5
0
/**
 * Provides capabilities to a specific <code>JComponent</code> to contain <code>PluginComponent
 * </code>s, track when they are added and removed.
 *
 * @author Lyubomir Marinov
 */
public class PluginContainer implements PluginComponentListener {
  /**
   * The <tt>Logger</tt> used by the <tt>PluginContainer</tt> class and its instances for logging
   * output.
   */
  private static final Logger logger = Logger.getLogger(PluginContainer.class);

  /**
   * The <code>JComponent</code> which contains the components of the <code>PluginComponent</code>s
   * managed by this instance.
   */
  private final JComponent container;

  /** The container id of the <code>PluginComponent</code> managed by this instance. */
  private final Container containerId;

  /**
   * The list of <code>PluginComponent</code> instances which have their components added to this
   * <code>PluginContainer</code>.
   */
  private final java.util.List<PluginComponent> pluginComponents =
      new LinkedList<PluginComponent>();

  /**
   * Initializes a new <code>PluginContainer</code> instance which is to provide capabilities to a
   * specific <code>JComponent</code> container with a specific <code>Container</code> id to contain
   * <code>PluginComponent</code> and track when they are added and removed.
   *
   * @param container the <code>JComponent</code> container the new instance is to provide its
   *     capabilities to
   * @param containerId the <code>Container</code> id of the specified <code>container</code>
   */
  public PluginContainer(JComponent container, Container containerId) {
    this.container = container;
    this.containerId = containerId;

    initPluginComponents();
  }

  /**
   * Adds a specific <tt>Component</tt> to a specific <tt>JComponent</tt> container. Allows
   * extenders to apply custom logic to the exact placement of the specified <tt>Component</tt> in
   * the specified container.
   *
   * @param component the <tt>Component</tt> to be added to the specified <tt>JComponent</tt>
   *     container
   * @param container the <tt>JComponent</tt> container to add the specified <tt>Component</tt> to
   * @param preferredIndex the index at which <tt>component</tt> is to be added to
   *     <tt>container</tt> if possible or <tt>-1</tt> if there is no preference with respect to the
   *     index in question
   */
  protected void addComponentToContainer(
      Component component, JComponent container, int preferredIndex) {
    if ((0 <= preferredIndex) && (preferredIndex < getComponentCount(container)))
      container.add(component, preferredIndex);
    else container.add(component);
  }

  /**
   * Adds the component of a specific <tt>PluginComponent</tt> to the associated <tt>Container</tt>.
   *
   * @param c the <tt>PluginComponent</tt> which is to have its component added to the
   *     <tt>Container</tt> associated with this <tt>PluginContainer</tt>
   */
  private synchronized void addPluginComponent(PluginComponent c) {
    /*
     * Try to respect positionIndex of PluginComponent to some extent:
     * PluginComponents with positionIndex equal to 0 go at the beginning,
     * these with positionIndex equal to -1 follow them and then go these
     * with positionIndex greater than 0.
     */
    int cIndex = c.getPositionIndex();
    int index = -1;
    int i = 0;

    for (PluginComponent pluginComponent : pluginComponents) {
      if (pluginComponent.equals(c)) return;

      if (-1 == index) {
        int pluginComponentIndex = pluginComponent.getPositionIndex();

        if ((0 == cIndex) || (-1 == cIndex)) {
          if ((0 != pluginComponentIndex) && (cIndex != pluginComponentIndex)) index = i;
        } else if (cIndex < pluginComponentIndex) index = i;
      }

      i++;
    }

    int pluginComponentCount = pluginComponents.size();

    if (-1 == index) index = pluginComponents.size();

    /*
     * The container may have added Components of its own apart from the
     * ones this PluginContainer has added to it. Since the common case for
     * the additional Components is to have them appear at the beginning,
     * adjust the index so it gets correct in the common case.
     */
    int containerComponentCount = getComponentCount(container);

    addComponentToContainer(
        (Component) c.getComponent(),
        container,
        (containerComponentCount > pluginComponentCount)
            ? (index + (containerComponentCount - pluginComponentCount))
            : index);
    pluginComponents.add(index, c);

    container.revalidate();
    container.repaint();
  }

  /**
   * Runs clean-up for associated resources which need explicit disposal (e.g. listeners keeping
   * this instance alive because they were added to the model which operationally outlives this
   * instance).
   */
  public void dispose() {
    GuiActivator.getUIService().removePluginComponentListener(this);

    /*
     * Explicitly remove the components of the PluginComponent instances
     * because the latter are registered with OSGi and are thus global.
     */
    synchronized (this) {
      for (PluginComponent pluginComponent : pluginComponents)
        container.remove((Component) pluginComponent.getComponent());
      pluginComponents.clear();
    }
  }

  /**
   * Gets the number of <tt>Component</tt>s in a specific <tt>JComponent</tt> container. For
   * example, returns the result of <tt>getMenuComponentCount()</tt> if <tt>container</tt> is an
   * instance of <tt>JMenu</tt>.
   *
   * @param container the <tt>JComponent</tt> container to get the number of <tt>Component</tt>s of
   * @return the number of <tt>Component</tt>s in the specified <tt>container</tt>
   */
  protected int getComponentCount(JComponent container) {
    return (container instanceof JMenu)
        ? ((JMenu) container).getMenuComponentCount()
        : container.getComponentCount();
  }

  /**
   * Gets the <tt>PluginComponent</tt>s of this <tt>PluginContainer</tt>.
   *
   * @return an <tt>Iterable</tt> over the <tt>PluginComponent</tt>s of this
   *     <tt>PluginContainer</tt>
   */
  public Iterable<PluginComponent> getPluginComponents() {
    return pluginComponents;
  }

  /**
   * Adds the <tt>Component</tt>s of the <tt>PluginComponent</tt>s registered in the OSGi
   * <tt>BundleContext</tt> in the associated <tt>Container</tt>.
   */
  private void initPluginComponents() {
    // Look for PluginComponents registered in the OSGi BundleContext.
    ServiceReference[] serRefs = null;

    try {
      serRefs =
          GuiActivator.bundleContext.getServiceReferences(
              PluginComponent.class.getName(),
              "(" + Container.CONTAINER_ID + "=" + containerId.getID() + ")");
    } catch (InvalidSyntaxException exc) {
      logger.error("Could not obtain plugin reference.", exc);
    }

    if (serRefs != null) {
      for (ServiceReference serRef : serRefs) {
        PluginComponent component = (PluginComponent) GuiActivator.bundleContext.getService(serRef);

        addPluginComponent(component);
      }
    }

    GuiActivator.getUIService().addPluginComponentListener(this);
  }

  /**
   * Implements {@link PluginComponentListener#pluginComponentAdded(PluginComponentEvent)}.
   *
   * @param event a <tt>PluginComponentEvent</tt> which specifies the <tt>PluginComponent</tt> which
   *     has been added
   */
  public void pluginComponentAdded(PluginComponentEvent event) {
    PluginComponent c = event.getPluginComponent();

    if (c.getContainer().equals(containerId)) addPluginComponent(c);
  }

  /**
   * Implements {@link PluginComponentListener#pluginComponentRemoved(PluginComponentEvent)}.
   *
   * @param event a <tt>PluginComponentEvent</tt> which specifies the <tt>PluginComponent</tt> which
   *     has been added
   */
  public void pluginComponentRemoved(PluginComponentEvent event) {
    PluginComponent c = event.getPluginComponent();

    if (c.getContainer().equals(containerId)) removePluginComponent(c);
  }

  /**
   * Removes the component of a specific <code>PluginComponent</code> from this <code>
   * PluginContainer</code>.
   *
   * @param c the <code>PluginComponent</code> which is to have its component removed from this
   *     <code>PluginContainer</code>
   */
  private synchronized void removePluginComponent(PluginComponent c) {
    container.remove((Component) c.getComponent());
    pluginComponents.remove(c);
  }
}
/**
 * A SIP implementation of the protocol provider factory interface.
 *
 * @author Emil Ivov
 */
public class ProtocolProviderFactorySipImpl extends ProtocolProviderFactory {
  private static final Logger logger = Logger.getLogger(ProtocolProviderFactorySipImpl.class);

  /** The table that we store our accounts in. */
  private Hashtable registeredAccounts = new Hashtable();

  /** Constructs a new instance of the ProtocolProviderFactorySipImpl. */
  public ProtocolProviderFactorySipImpl() {}

  /**
   * Returns the ServiceReference for the protocol provider corresponding to the specified accountID
   * or null if the accountID is unknown.
   *
   * @param accountID the accountID of the protocol provider we'd like to get
   * @return a ServiceReference object to the protocol provider with the specified account id and
   *     null if the account id is unknwon to the provider factory.
   */
  public ServiceReference getProviderForAccount(AccountID accountID) {
    ServiceRegistration registration = (ServiceRegistration) registeredAccounts.get(accountID);

    return (registration == null) ? null : registration.getReference();
  }

  /**
   * Returns a copy of the list containing all accounts currently registered in this protocol
   * provider.
   *
   * @return a copy of the llist containing all accounts currently installed in the protocol
   *     provider.
   */
  public ArrayList getRegisteredAccounts() {
    return new ArrayList(registeredAccounts.keySet());
  }

  /**
   * Initializes and creates an account corresponding to the specified accountProperties and
   * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter.
   *
   * @param userIDStr the user identifier uniquely representing the newly created account within the
   *     protocol namespace.
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @return the AccountID of the newly created account.
   * @throws IllegalArgumentException if userID does not correspond to an identifier in the context
   *     of the underlying protocol or if accountProperties does not contain a complete set of
   *     account installation properties.
   * @throws IllegalStateException if the account has already been installed.
   * @throws NullPointerException if any of the arguments is null.
   */
  public AccountID installAccount(String userIDStr, Map accountProperties) {
    BundleContext context = SipActivator.getBundleContext();
    if (context == null) throw new NullPointerException("The specified BundleContext was null");

    if (userIDStr == null) throw new NullPointerException("The specified AccountID was null");

    accountProperties.put(USER_ID, userIDStr);

    if (accountProperties == null)
      throw new NullPointerException("The specified property map was null");

    String serverAddress = (String) accountProperties.get(SERVER_ADDRESS);

    if (serverAddress == null) throw new NullPointerException("null is not a valid ServerAddress");

    if (!accountProperties.containsKey(PROTOCOL))
      accountProperties.put(PROTOCOL, ProtocolNames.SIP);

    AccountID accountID = new SipAccountID(userIDStr, accountProperties, serverAddress);

    // make sure we haven't seen this account id before.
    if (registeredAccounts.containsKey(accountID))
      throw new IllegalStateException("An account for id " + userIDStr + " was already installed!");

    // first store the account and only then load it as the load generates
    // an osgi event, the osgi event triggers (trhgough the UI) a call to
    // the register() method and it needs to acces the configuration service
    // and check for a password.
    this.storeAccount(SipActivator.getBundleContext(), accountID);

    try {
      accountID = loadAccount(accountProperties);
    } catch (RuntimeException exc) {
      // it might happen that load-ing the account fails because of a bad
      // initialization. if this is the case, make sure we remove it.
      this.removeStoredAccount(SipActivator.getBundleContext(), accountID);

      throw exc;
    }

    return accountID;
  }

  /**
   * Initializes and creates an account corresponding to the specified accountProperties and
   * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter.
   *
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @return the AccountID of the newly created account
   */
  protected AccountID loadAccount(Map accountProperties) {
    BundleContext context = SipActivator.getBundleContext();
    if (context == null) throw new NullPointerException("The specified BundleContext was null");

    String userIDStr = (String) accountProperties.get(USER_ID);
    if (userIDStr == null)
      throw new NullPointerException("The account properties contained no user id.");

    if (accountProperties == null)
      throw new NullPointerException("The specified property map was null");

    String serverAddress = (String) accountProperties.get(SERVER_ADDRESS);

    if (serverAddress == null)
      throw new NullPointerException(serverAddress + " is not a valid ServerAddress");

    if (!accountProperties.containsKey(PROTOCOL))
      accountProperties.put(PROTOCOL, ProtocolNames.SIP);

    SipAccountID accountID = new SipAccountID(userIDStr, accountProperties, serverAddress);

    // get a reference to the configuration service and register whatever
    // properties we have in it.

    Hashtable properties = new Hashtable();
    properties.put(PROTOCOL, ProtocolNames.SIP);
    properties.put(USER_ID, userIDStr);

    ProtocolProviderServiceSipImpl sipProtocolProvider = new ProtocolProviderServiceSipImpl();

    try {
      sipProtocolProvider.initialize(userIDStr, accountID);

      // We store again the account in order to store all properties added
      // during the protocol provider initialization.
      this.storeAccount(SipActivator.getBundleContext(), accountID);
    } catch (OperationFailedException ex) {
      logger.error("Failed to initialize account", ex);
      throw new IllegalArgumentException("Failed to initialize account" + ex.getMessage());
    }

    ServiceRegistration registration =
        context.registerService(
            ProtocolProviderService.class.getName(), sipProtocolProvider, properties);

    registeredAccounts.put(accountID, registration);
    return accountID;
  }

  /** Loads (and hence installs) all accounts previously stored in the configuration service. */
  public void loadStoredAccounts() {
    super.loadStoredAccounts(SipActivator.getBundleContext());
  }

  /**
   * Removes the specified account from the list of accounts that this provider factory is handling.
   * If the specified accountID is unknown to the ProtocolProviderFactory, the call has no effect
   * and false is returned. This method is persistent in nature and once called the account
   * corresponding to the specified ID will not be loaded during future runs of the project.
   *
   * @param accountID the ID of the account to remove.
   * @return true if an account with the specified ID existed and was removed and false otherwise.
   */
  public boolean uninstallAccount(AccountID accountID) {
    // unregister the protocol provider
    ServiceReference serRef = getProviderForAccount(accountID);

    if (serRef == null) return false;

    ProtocolProviderService protocolProvider =
        (ProtocolProviderService) SipActivator.getBundleContext().getService(serRef);

    try {
      protocolProvider.unregister();
    } catch (OperationFailedException e) {
      logger.error(
          "Failed to unregister protocol provider for account : "
              + accountID
              + " caused by : "
              + e);
    }

    ServiceRegistration registration = (ServiceRegistration) registeredAccounts.remove(accountID);

    if (registration == null) return false;

    // kill the service
    registration.unregister();

    return removeStoredAccount(SipActivator.getBundleContext(), accountID);
  }

  /** Prepares the factory for bundle shutdown. */
  public void stop() {
    logger.trace("Preparing to stop all SIP protocol providers.");
    Enumeration registrations = this.registeredAccounts.elements();

    while (registrations.hasMoreElements()) {
      ServiceRegistration reg = ((ServiceRegistration) registrations.nextElement());

      ProtocolProviderServiceSipImpl provider =
          (ProtocolProviderServiceSipImpl)
              SipActivator.getBundleContext().getService(reg.getReference());

      // do an attempt to kill the provider
      provider.shutdown();

      reg.unregister();
    }

    registeredAccounts.clear();
  }

  /**
   * Saves the password for the specified account after scrambling it a bit so that it is not
   * visible from first sight (Method remains highly insecure).
   *
   * @param accountID the AccountID for the account whose password we're storing.
   * @param passwd the password itself.
   * @throws java.lang.IllegalArgumentException if no account corresponding to <tt>accountID</tt>
   *     has been previously stored.
   */
  public void storePassword(AccountID accountID, String passwd) throws IllegalArgumentException {
    super.storePassword(SipActivator.getBundleContext(), accountID, passwd);
  }

  /**
   * Returns the password last saved for the specified account.
   *
   * @param accountID the AccountID for the account whose password we're looking for..
   * @return a String containing the password for the specified accountID.
   * @throws java.lang.IllegalArgumentException if no account corresponding to <tt>accountID</tt>
   *     has been previously stored.
   */
  public String loadPassword(AccountID accountID) throws IllegalArgumentException {
    return super.loadPassword(SipActivator.getBundleContext(), accountID);
  }
}
/**
 * Implements {@link BundleActivator} for the <tt>msofficecomm</tt> bundle.
 *
 * @author Lyubomir Marinov
 */
public class MsOfficeCommActivator implements BundleActivator {
  /**
   * The <tt>Logger</tt> used by the <tt>MsOfficeCommActivator</tt> class and its instances for
   * logging output.
   */
  private static final Logger logger = Logger.getLogger(MsOfficeCommActivator.class);

  /**
   * Starts the <tt>msofficecomm</tt> bundle in a specific {@link BundleContext}.
   *
   * @param bundleContext the <tt>BundleContext</tt> in which the <tt>msofficecomm</tt> bundle is to
   *     be started
   * @throws Exception if anything goes wrong while starting the <tt>msofficecomm</tt> bundle in the
   *     specified <tt>BundleContext</tt>
   */
  public void start(BundleContext bundleContext) throws Exception {
    // The msofficecomm bundle is available on Windows only.
    if (!OSUtils.IS_WINDOWS) return;

    if (logger.isInfoEnabled()) logger.info("MsOfficeComm plugin ... [STARTED]");

    Messenger.start(bundleContext);

    boolean stopMessenger = true;

    try {
      int hresult = OutOfProcessServer.start();

      if (logger.isInfoEnabled())
        logger.info("MsOfficeComm started OutOfProcessServer HRESULT:" + hresult);

      if (hresult < 0) throw new RuntimeException("HRESULT " + hresult);
      else stopMessenger = false;
    } finally {
      if (stopMessenger) Messenger.stop(bundleContext);
    }
  }

  /**
   * Stops the <tt>msofficecomm</tt> bundle in a specific {@link BundleContext}.
   *
   * @param bundleContext the <tt>BundleContext</tt> in which the <tt>msofficecomm</tt> bundle is to
   *     be stopped
   * @throws Exception if anything goes wrong while stopping the <tt>msofficecomm</tt> bundle in the
   *     specified <tt>BundleContext</tt>
   */
  public void stop(BundleContext bundleContext) throws Exception {
    // The msofficecomm bundle is available on Windows only.
    if (!OSUtils.IS_WINDOWS) return;

    try {
      int hresult = OutOfProcessServer.stop();

      if (hresult < 0) throw new RuntimeException("HRESULT " + hresult);
    } finally {
      Messenger.stop(bundleContext);
    }

    if (logger.isInfoEnabled()) logger.info("MsOfficeComm plugin ... [UNREGISTERED]");
  }
}
/**
 * Represents the Gibberish protocol icon. Implements the <tt>ProtocolIcon</tt> interface in order
 * to provide a gibberish logo image in two different sizes.
 *
 * @author Yana Stamcheva
 */
public class ProtocolIconGibberishImpl implements ProtocolIcon {
  private static Logger logger = Logger.getLogger(ProtocolIconGibberishImpl.class);

  private static ResourceManagementService resourcesService;

  /** A hash table containing the protocol icon in different sizes. */
  private static Hashtable<String, byte[]> iconsTable = new Hashtable<String, byte[]>();

  static {
    iconsTable.put(
        ProtocolIcon.ICON_SIZE_16x16,
        getImageInBytes("service.protocol.gibberish.GIBBERISH_16x16"));

    iconsTable.put(
        ProtocolIcon.ICON_SIZE_32x32,
        getImageInBytes("service.protocol.gibberish.GIBBERISH_32x32"));

    iconsTable.put(
        ProtocolIcon.ICON_SIZE_48x48,
        getImageInBytes("service.protocol.gibberish.GIBBERISH_48x48"));

    iconsTable.put(
        ProtocolIcon.ICON_SIZE_64x64,
        getImageInBytes("service.protocol.gibberish.GIBBERISH_64x64"));
  }

  /** A hash table containing the path to the protocol icon in different sizes. */
  private static Hashtable<String, String> iconPathsTable = new Hashtable<String, String>();

  static {
    iconPathsTable.put(
        ProtocolIcon.ICON_SIZE_16x16,
        getResources().getImagePath("service.protocol.gibberish.GIBBERISH_16x16"));

    iconPathsTable.put(
        ProtocolIcon.ICON_SIZE_32x32,
        getResources().getImagePath("service.protocol.gibberish.GIBBERISH_32x32"));

    iconPathsTable.put(
        ProtocolIcon.ICON_SIZE_48x48,
        getResources().getImagePath("service.protocol.gibberish.GIBBERISH_48x48"));

    iconPathsTable.put(
        ProtocolIcon.ICON_SIZE_64x64,
        getResources().getImagePath("service.protocol.gibberish.GIBBERISH_64x64"));
  }

  /**
   * Implements the <tt>ProtocolIcon.getSupportedSizes()</tt> method. Returns an iterator to a set
   * containing the supported icon sizes.
   *
   * @return an iterator to a set containing the supported icon sizes
   */
  public Iterator<String> getSupportedSizes() {
    return iconsTable.keySet().iterator();
  }

  /** Returne TRUE if a icon with the given size is supported, FALSE-otherwise. */
  public boolean isSizeSupported(String iconSize) {
    return iconsTable.containsKey(iconSize);
  }

  /**
   * Returns the icon image in the given size.
   *
   * @param iconSize the icon size; one of ICON_SIZE_XXX constants
   */
  public byte[] getIcon(String iconSize) {
    return iconsTable.get(iconSize);
  }

  /**
   * Returns a path to the icon with the given size.
   *
   * @param iconSize the size of the icon we're looking for
   * @return the path to the icon with the given size
   */
  public String getIconPath(String iconSize) {
    return iconPathsTable.get(iconSize);
  }

  /**
   * Returns the icon image used to represent the protocol connecting state.
   *
   * @return the icon image used to represent the protocol connecting state
   */
  public byte[] getConnectingIcon() {
    return getImageInBytes("gibberishOnlineIcon");
  }

  /**
   * Returns the byte representation of the image corresponding to the given identifier.
   *
   * @param imageID the identifier of the image
   * @return the byte representation of the image corresponding to the given identifier.
   */
  private static byte[] getImageInBytes(String imageID) {
    InputStream in = getResources().getImageInputStream(imageID);

    if (in == null) return null;
    byte[] image = null;
    try {
      image = new byte[in.available()];

      in.read(image);
    } catch (IOException e) {
      logger.error("Failed to load image:" + imageID, e);
    }

    return image;
  }

  public static ResourceManagementService getResources() {
    if (resourcesService == null) {
      ServiceReference serviceReference =
          GibberishActivator.bundleContext.getServiceReference(
              ResourceManagementService.class.getName());

      if (serviceReference == null) return null;

      resourcesService =
          (ResourceManagementService) GibberishActivator.bundleContext.getService(serviceReference);
    }

    return resourcesService;
  }
}
/**
 * The <tt>GibberishAccountRegistrationWizard</tt> is an implementation of the
 * <tt>AccountRegistrationWizard</tt> for the Gibberish protocol. It allows the user to create and
 * configure a new Gibberish account.
 *
 * @author Emil Ivov
 */
public class GibberishAccountRegistrationWizard extends DesktopAccountRegistrationWizard {
  private final Logger logger = Logger.getLogger(GibberishAccountRegistrationWizard.class);

  /** The first page of the gibberish account registration wizard. */
  private FirstWizardPage firstWizardPage;

  /** The object that we use to store details on an account that we will be creating. */
  private GibberishAccountRegistration registration = new GibberishAccountRegistration();

  private ProtocolProviderService protocolProvider;

  /**
   * Creates an instance of <tt>GibberishAccountRegistrationWizard</tt>.
   *
   * @param wizardContainer the wizard container, where this wizard is added
   */
  public GibberishAccountRegistrationWizard(WizardContainer wizardContainer) {
    setWizardContainer(wizardContainer);

    wizardContainer.setFinishButtonText(Resources.getString("service.gui.SIGN_IN"));
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getIcon</code> method. Returns the icon to be
   * used for this wizard.
   *
   * @return byte[]
   */
  public byte[] getIcon() {
    return Resources.getImage(Resources.GIBBERISH_LOGO);
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getPageImage</code> method. Returns the image
   * used to decorate the wizard page
   *
   * @return byte[] the image used to decorate the wizard page
   */
  public byte[] getPageImage() {
    return Resources.getImage(Resources.PAGE_IMAGE);
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getProtocolName</code> method. Returns the
   * protocol name for this wizard.
   *
   * @return String
   */
  public String getProtocolName() {
    return Resources.getString("plugin.gibberishaccregwizz.PROTOCOL_NAME");
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getProtocolDescription
   * </code> method. Returns the description of the protocol for this wizard.
   *
   * @return String
   */
  public String getProtocolDescription() {
    return Resources.getString("plugin.gibberishaccregwizz.PROTOCOL_DESCRIPTION");
  }

  /**
   * Returns the set of pages contained in this wizard.
   *
   * @return Iterator
   */
  public Iterator<WizardPage> getPages() {
    java.util.List<WizardPage> pages = new ArrayList<WizardPage>();
    firstWizardPage = new FirstWizardPage(this);

    pages.add(firstWizardPage);

    return pages.iterator();
  }

  /**
   * Returns the set of data that user has entered through this wizard.
   *
   * @return Iterator
   */
  public Iterator<Map.Entry<String, String>> getSummary() {
    Map<String, String> summaryTable = new Hashtable<String, String>();

    summaryTable.put("User ID", registration.getUserID());

    return summaryTable.entrySet().iterator();
  }

  /**
   * Defines the operations that will be executed when the user clicks on the wizard "Signin"
   * button.
   *
   * @return the created <tt>ProtocolProviderService</tt> corresponding to the new account
   * @throws OperationFailedException if the operation didn't succeed
   */
  public ProtocolProviderService signin() throws OperationFailedException {
    firstWizardPage.commitPage();

    return signin(registration.getUserID(), null);
  }

  /**
   * Defines the operations that will be executed when the user clicks on the wizard "Signin"
   * button.
   *
   * @param userName the user name to sign in with
   * @param password the password to sign in with
   * @return the created <tt>ProtocolProviderService</tt> corresponding to the new account
   * @throws OperationFailedException if the operation didn't succeed
   */
  public ProtocolProviderService signin(String userName, String password)
      throws OperationFailedException {
    ProtocolProviderFactory factory =
        GibberishAccRegWizzActivator.getGibberishProtocolProviderFactory();

    return this.installAccount(factory, userName);
  }

  /**
   * Creates an account for the given user and password.
   *
   * @param providerFactory the ProtocolProviderFactory which will create the account
   * @param user the user identifier
   * @return the <tt>ProtocolProviderService</tt> for the new account.
   */
  public ProtocolProviderService installAccount(
      ProtocolProviderFactory providerFactory, String user) throws OperationFailedException {
    Hashtable<String, String> accountProperties = new Hashtable<String, String>();

    accountProperties.put(
        ProtocolProviderFactory.ACCOUNT_ICON_PATH,
        "resources/images/protocol/gibberish/gibberish32x32.png");

    if (registration.isRememberPassword()) {
      accountProperties.put(ProtocolProviderFactory.PASSWORD, registration.getPassword());
    }

    if (isModification()) {
      providerFactory.uninstallAccount(protocolProvider.getAccountID());
      this.protocolProvider = null;
      setModification(false);
    }

    try {
      AccountID accountID = providerFactory.installAccount(user, accountProperties);

      ServiceReference serRef = providerFactory.getProviderForAccount(accountID);

      protocolProvider =
          (ProtocolProviderService) GibberishAccRegWizzActivator.bundleContext.getService(serRef);
    } catch (IllegalStateException exc) {
      logger.warn(exc.getMessage());

      throw new OperationFailedException(
          "Account already exists.", OperationFailedException.IDENTIFICATION_CONFLICT);
    } catch (Exception exc) {
      logger.warn(exc.getMessage());

      throw new OperationFailedException(
          "Failed to add account", OperationFailedException.GENERAL_ERROR);
    }

    return protocolProvider;
  }

  /**
   * Fills the UserID and Password fields in this panel with the data comming from the given
   * protocolProvider.
   *
   * @param protocolProvider The <tt>ProtocolProviderService</tt> to load the data from.
   */
  public void loadAccount(ProtocolProviderService protocolProvider) {
    setModification(true);

    this.protocolProvider = protocolProvider;

    this.registration = new GibberishAccountRegistration();

    this.firstWizardPage.loadAccount(protocolProvider);
  }

  /**
   * Returns the registration object, which will store all the data through the wizard.
   *
   * @return the registration object, which will store all the data through the wizard
   */
  public GibberishAccountRegistration getRegistration() {
    return registration;
  }

  /**
   * Returns the size of this wizard.
   *
   * @return the size of this wizard
   */
  public Dimension getSize() {
    return new Dimension(600, 500);
  }

  /**
   * Returns the identifier of the page to show first in the wizard.
   *
   * @return the identifier of the page to show first in the wizard.
   */
  public Object getFirstPageIdentifier() {
    return firstWizardPage.getIdentifier();
  }

  /**
   * Returns the identifier of the page to show last in the wizard.
   *
   * @return the identifier of the page to show last in the wizard.
   */
  public Object getLastPageIdentifier() {
    return firstWizardPage.getIdentifier();
  }

  /**
   * Returns an example string, which should indicate to the user how the user name should look
   * like.
   *
   * @return an example string, which should indicate to the user how the user name should look
   *     like.
   */
  public String getUserNameExample() {
    return FirstWizardPage.USER_NAME_EXAMPLE;
  }

  /**
   * Indicates whether this wizard enables the simple "sign in" form shown when the user opens the
   * application for the first time. The simple "sign in" form allows user to configure her account
   * in one click, just specifying her username and password and leaving any other configuration as
   * by default.
   *
   * @return <code>true</code> if the simple "Sign in" form is enabled or <code>false</code>
   *     otherwise.
   */
  public boolean isSimpleFormEnabled() {
    return false;
  }

  /**
   * Returns a simple account registration form that would be the first form shown to the user. Only
   * if the user needs more settings she'll choose to open the advanced wizard, consisted by all
   * pages.
   *
   * @param isCreateAccount indicates if the simple form should be opened as a create account form
   *     or as a login form
   * @return a simple account registration form
   */
  public Object getSimpleForm(boolean isCreateAccount) {
    firstWizardPage = new FirstWizardPage(this);
    return firstWizardPage.getSimpleForm();
  }
}
/**
 * A default implementation of the <tt>ResourceManagementService</tt>.
 *
 * @author Damian Minkov
 * @author Yana Stamcheva
 * @author Lubomir Marinov
 * @author Adam Netocny
 */
public class ResourceManagementServiceImpl extends AbstractResourcesService {
  /**
   * The <tt>Logger</tt> used by the <tt>ResourceManagementServiceImpl</tt> class and its instances
   * for logging output.
   */
  private static final Logger logger = Logger.getLogger(ResourceManagementServiceImpl.class);

  /** UI Service reference. */
  private UIService uiService = null;

  /** Initializes already registered default resource packs. */
  ResourceManagementServiceImpl() {
    super(ResourceManagementActivator.bundleContext);

    UIService serv = getUIService();
    if (serv != null) {
      serv.repaintUI();
    }
  }

  /**
   * Returns the <tt>UIService</tt> obtained from the bundle context.
   *
   * @return the <tt>UIService</tt> obtained from the bundle context
   */
  private UIService getUIService() {
    if (uiService == null) {
      uiService =
          ServiceUtils.getService(ResourceManagementActivator.bundleContext, UIService.class);
    }
    return uiService;
  }

  /**
   * Gets a reference to the <tt>UIService</tt> when this one is registered.
   *
   * @param event the <tt>ServiceEvent</tt> that has notified us
   */
  @Override
  public void serviceChanged(ServiceEvent event) {
    super.serviceChanged(event);

    Object sService =
        ResourceManagementActivator.bundleContext.getService(event.getServiceReference());

    if (sService instanceof UIService
        && uiService == null
        && event.getType() == ServiceEvent.REGISTERED) {
      uiService = (UIService) sService;
      uiService.repaintUI();
    } else if (sService instanceof UIService && event.getType() == ServiceEvent.UNREGISTERING) {
      if (uiService != null && uiService.equals(sService)) {
        uiService = null;
      }
    }
  }

  /** Repaints the whole UI when a skin pack has changed. */
  @Override
  protected void onSkinPackChanged() {
    UIService serv = getUIService();
    if (serv != null) {
      serv.repaintUI();
    }
  }

  /**
   * Returns the int representation of the color corresponding to the given key.
   *
   * @param key The key of the color in the colors properties file.
   * @return the int representation of the color corresponding to the given key.
   */
  public int getColor(String key) {
    String res = getColorResources().get(key);

    if (res == null) {
      logger.error("Missing color resource for key: " + key);

      return 0xFFFFFF;
    } else return Integer.parseInt(res, 16);
  }

  /**
   * Returns the string representation of the color corresponding to the given key.
   *
   * @param key The key of the color in the colors properties file.
   * @return the string representation of the color corresponding to the given key.
   */
  public String getColorString(String key) {
    String res = getColorResources().get(key);

    if (res == null) {
      logger.error("Missing color resource for key: " + key);

      return "0xFFFFFF";
    } else return res;
  }

  /**
   * Returns the <tt>InputStream</tt> of the image corresponding to the given path.
   *
   * @param path The path to the image file.
   * @return the <tt>InputStream</tt> of the image corresponding to the given path.
   */
  public InputStream getImageInputStreamForPath(String path) {
    SkinPack skinPack = getSkinPack();
    if (skinPack != null) {
      if (skinPack.getClass().getClassLoader().getResourceAsStream(path) != null) {
        return skinPack.getClass().getClassLoader().getResourceAsStream(path);
      }
    }

    ImagePack imagePack = getImagePack();
    if (path != null && imagePack != null)
      return imagePack.getClass().getClassLoader().getResourceAsStream(path);

    return null;
  }

  /**
   * Returns the <tt>InputStream</tt> of the image corresponding to the given key.
   *
   * @param streamKey The identifier of the image in the resource properties file.
   * @return the <tt>InputStream</tt> of the image corresponding to the given key.
   */
  public InputStream getImageInputStream(String streamKey) {
    String path = getImagePath(streamKey);

    if (path == null || path.length() == 0) {
      logger.warn("Missing resource for key: " + streamKey);
      return null;
    }

    return getImageInputStreamForPath(path);
  }

  /**
   * Returns the <tt>URL</tt> of the image corresponding to the given key.
   *
   * @param urlKey The identifier of the image in the resource properties file.
   * @return the <tt>URL</tt> of the image corresponding to the given key
   */
  public URL getImageURL(String urlKey) {
    String path = getImagePath(urlKey);

    if (path == null || path.length() == 0) {
      if (logger.isInfoEnabled()) logger.info("Missing resource for key: " + urlKey);
      return null;
    }
    return getImageURLForPath(path);
  }

  /**
   * Returns the <tt>URL</tt> of the image corresponding to the given path.
   *
   * @param path The path to the given image file.
   * @return the <tt>URL</tt> of the image corresponding to the given path.
   */
  public URL getImageURLForPath(String path) {
    SkinPack skinPack = getSkinPack();
    if (skinPack != null) {
      if (skinPack.getClass().getClassLoader().getResource(path) != null) {
        return skinPack.getClass().getClassLoader().getResource(path);
      }
    }

    ImagePack imagePack = getImagePack();
    return imagePack.getClass().getClassLoader().getResource(path);
  }

  /**
   * Returns the <tt>URL</tt> of the sound corresponding to the given property key.
   *
   * @return the <tt>URL</tt> of the sound corresponding to the given property key.
   */
  public URL getSoundURL(String urlKey) {
    String path = getSoundPath(urlKey);

    if (path == null || path.length() == 0) {
      logger.warn("Missing resource for key: " + urlKey);
      return null;
    }
    return getSoundURLForPath(path);
  }

  /**
   * Returns the <tt>URL</tt> of the sound corresponding to the given path.
   *
   * @param path the path, for which we're looking for a sound URL
   * @return the <tt>URL</tt> of the sound corresponding to the given path.
   */
  public URL getSoundURLForPath(String path) {
    return getSoundPack().getClass().getClassLoader().getResource(path);
  }

  /**
   * Loads an image from a given image identifier.
   *
   * @param imageID The identifier of the image.
   * @return The image for the given identifier.
   */
  @Override
  public byte[] getImageInBytes(String imageID) {
    InputStream in = getImageInputStream(imageID);

    if (in == null) return null;

    byte[] image = null;

    try {
      image = new byte[in.available()];
      in.read(image);
    } catch (IOException e) {
      logger.error("Failed to load image:" + imageID, e);
    }

    return image;
  }

  /**
   * Loads an image from a given image identifier.
   *
   * @param imageID The identifier of the image.
   * @return The image for the given identifier.
   */
  @Override
  public ImageIcon getImage(String imageID) {
    URL imageURL = getImageURL(imageID);

    return (imageURL == null) ? null : new ImageIcon(imageURL);
  }

  /**
   * Builds a new skin bundle from the zip file content.
   *
   * @param zipFile Zip file with skin information.
   * @return <tt>File</tt> for the bundle.
   * @throws Exception When something goes wrong.
   */
  public File prepareSkinBundleFromZip(File zipFile) throws Exception {
    return SkinJarBuilder.createBundleFromZip(zipFile, getImagePack());
  }

  /**
   * Gets the specified setting from the config service if present, otherwise from the embedded
   * resources (resources/config/defaults.properties).
   *
   * @param key The setting to lookup.
   * @return The setting for the key or {@code null} if not found.
   */
  @Override
  public String getSettingsString(String key) {
    Object configValue = ResourceManagementActivator.getConfigService().getProperty(key);
    if (configValue == null) {
      configValue = super.getSettingsString(key);
    }

    return configValue == null ? null : configValue.toString();
  }
}
/**
 * @author Lyubomir Marinov
 * @author Pawel Domas
 */
public class BundleImpl implements Bundle {
  /** The Logger */
  private Logger logger = Logger.getLogger(BundleImpl.class.getName());

  private BundleActivator bundleActivator;

  private BundleContext bundleContext;

  private final long bundleId;

  private BundleStartLevel bundleStartLevel;

  private final FrameworkImpl framework;

  private final String location;

  private int state = INSTALLED;

  public BundleImpl(FrameworkImpl framework, long bundleId, String location) {
    this.framework = framework;
    this.bundleId = bundleId;
    this.location = location;
  }

  public <A> A adapt(Class<A> type) {
    Object adapt;

    if (BundleStartLevel.class.equals(type)) {
      if (getBundleId() == 0) adapt = null;
      else
        synchronized (this) {
          if (bundleStartLevel == null) bundleStartLevel = new BundleStartLevelImpl(this);

          adapt = bundleStartLevel;
        }
    } else adapt = null;

    @SuppressWarnings("unchecked")
    A a = (A) adapt;

    return a;
  }

  public int compareTo(Bundle other) {
    long thisBundleId = getBundleId();
    long otherBundleId = other.getBundleId();

    if (thisBundleId < otherBundleId) return -1;
    else if (thisBundleId == otherBundleId) return 0;
    else return 1;
  }

  public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
    // TODO Auto-generated method stub
    return null;
  }

  public BundleContext getBundleContext() {
    switch (getState()) {
      case STARTING:
      case ACTIVE:
      case STOPPING:
        return bundleContext;
      default:
        return null;
    }
  }

  public long getBundleId() {
    return bundleId;
  }

  public File getDataFile(String filename) {
    // TODO Auto-generated method stub
    return null;
  }

  public URL getEntry(String path) {
    // TODO Auto-generated method stub
    return null;
  }

  public Enumeration<String> getEntryPaths(String path) {
    // TODO Auto-generated method stub
    return null;
  }

  public FrameworkImpl getFramework() {
    return framework;
  }

  public Dictionary<String, String> getHeaders() {
    return getHeaders(null);
  }

  public Dictionary<String, String> getHeaders(String locale) {
    // TODO Auto-generated method stub
    return null;
  }

  public long getLastModified() {
    // TODO Auto-generated method stub
    return 0;
  }

  public String getLocation() {
    return (getBundleId() == 0) ? Constants.SYSTEM_BUNDLE_LOCATION : location;
  }

  public ServiceReference<?>[] getRegisteredServices() {
    return framework.getRegisteredServices();
  }

  public URL getResource(String name) {
    // TODO Auto-generated method stub
    return null;
  }

  public Enumeration<URL> getResources(String name) throws IOException {
    // TODO Auto-generated method stub
    return null;
  }

  public ServiceReference<?>[] getServicesInUse() {
    // TODO Auto-generated method stub
    return null;
  }

  public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
    // TODO Auto-generated method stub
    return null;
  }

  public int getState() {
    return state;
  }

  public String getSymbolicName() {
    // TODO Auto-generated method stub
    return null;
  }

  public Version getVersion() {
    // TODO Auto-generated method stub
    return null;
  }

  public boolean hasPermission(Object permission) {
    // TODO Auto-generated method stub
    return false;
  }

  public Class<?> loadClass(String name) throws ClassNotFoundException {
    return Class.forName(name);
  }

  protected void setBundleContext(BundleContext bundleContext) {
    this.bundleContext = bundleContext;
  }

  protected void setState(int state) {
    int oldState = getState();

    if (oldState != state) {
      this.state = state;

      int newState = getState();

      if (oldState != newState) stateChanged(oldState, newState);
    }
  }

  public void start() throws BundleException {
    start(0);
  }

  public void start(int options) throws BundleException {
    if (getState() == UNINSTALLED) throw new IllegalStateException("Bundle.UNINSTALLED");

    BundleStartLevel bundleStartLevel = adapt(BundleStartLevel.class);
    FrameworkStartLevel frameworkStartLevel = getFramework().adapt(FrameworkStartLevel.class);

    if ((bundleStartLevel != null)
        && (bundleStartLevel.getStartLevel() > frameworkStartLevel.getStartLevel())) {
      if ((options & START_TRANSIENT) == START_TRANSIENT) throw new BundleException("startLevel");
      else return;
    }

    if (getState() == ACTIVE) return;

    if (getState() == INSTALLED) setState(RESOLVED);

    setState(STARTING);

    String location = getLocation();

    if (location != null) {
      BundleActivator bundleActivator = null;
      Throwable exception = null;

      try {
        bundleActivator = (BundleActivator) loadClass(location.replace('/', '.')).newInstance();

        bundleActivator.start(getBundleContext());
      } catch (Throwable t) {
        logger.log(Level.SEVERE, "Error starting bundle: " + bundleActivator, t);

        if (t instanceof ThreadDeath) throw (ThreadDeath) t;
        else exception = t;
      }

      if (exception == null) this.bundleActivator = bundleActivator;
      else {
        setState(STOPPING);
        setState(RESOLVED);
        getFramework().fireBundleEvent(BundleEvent.STOPPED, this);
        throw new BundleException("BundleActivator.start", exception);
      }
    }

    if (getState() == UNINSTALLED) throw new IllegalStateException("Bundle.UNINSTALLED");

    setState(ACTIVE);
  }

  protected void stateChanged(int oldState, int newState) {
    switch (newState) {
      case ACTIVE:
        getFramework().fireBundleEvent(BundleEvent.STARTED, this);
        break;
      case RESOLVED:
        setBundleContext(null);
        break;
      case STARTING:
        setBundleContext(new BundleContextImpl(getFramework(), this));

        /*
         * BundleEvent.STARTING is only delivered to
         * SynchronousBundleListeners, it is not delivered to
         * BundleListeners.
         */
        break;
      case STOPPING:
        /*
         * BundleEvent.STOPPING is only delivered to
         * SynchronousBundleListeners, it is not delivered to
         * BundleListeners.
         */
        break;
    }
  }

  public void stop() throws BundleException {
    stop(0);
  }

  public void stop(int options) throws BundleException {
    boolean wasActive = false;

    switch (getState()) {
      case ACTIVE:
        wasActive = true;
      case STARTING:
        setState(STOPPING);

        Throwable exception = null;

        if (wasActive && (bundleActivator != null)) {
          try {
            bundleActivator.stop(getBundleContext());
          } catch (Throwable t) {
            if (t instanceof ThreadDeath) throw (ThreadDeath) t;
            else exception = t;
          }
          this.bundleActivator = null;
        }

        if (getState() == UNINSTALLED) throw new BundleException("Bundle.UNINSTALLED");

        setState(RESOLVED);
        getFramework().fireBundleEvent(BundleEvent.STOPPED, this);

        if (exception != null) throw new BundleException("BundleActivator.stop", exception);
        break;

      case UNINSTALLED:
        throw new IllegalStateException("Bundle.UNINSTALLED");
      default:
        break;
    }
  }

  public void uninstall() throws BundleException {
    // TODO Auto-generated method stub
  }

  public void update() throws BundleException {
    update(null);
  }

  public void update(InputStream input) throws BundleException {
    // TODO Auto-generated method stub
  }
}
/**
 * Implements <tt>BundleActivator</tt> for the neomedia bundle.
 *
 * @author Martin Andre
 * @author Emil Ivov
 * @author Lyubomir Marinov
 * @author Boris Grozev
 */
public class NeomediaActivator implements BundleActivator {

  /**
   * The <tt>Logger</tt> used by the <tt>NeomediaActivator</tt> class and its instances for logging
   * output.
   */
  private final Logger logger = Logger.getLogger(NeomediaActivator.class);

  /** Indicates if the audio configuration form should be disabled, i.e. not visible to the user. */
  private static final String AUDIO_CONFIG_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.AUDIO_CONFIG_DISABLED";

  /** Indicates if the video configuration form should be disabled, i.e. not visible to the user. */
  private static final String VIDEO_CONFIG_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.VIDEO_CONFIG_DISABLED";

  /** Indicates if the H.264 configuration form should be disabled, i.e. not visible to the user. */
  private static final String H264_CONFIG_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.h264config.DISABLED";

  /** Indicates if the ZRTP configuration form should be disabled, i.e. not visible to the user. */
  private static final String ZRTP_CONFIG_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.zrtpconfig.DISABLED";

  /**
   * Indicates if the call recording config form should be disabled, i.e. not visible to the user.
   */
  private static final String CALL_RECORDING_CONFIG_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.callrecordingconfig.DISABLED";

  /**
   * The name of the notification pop-up event displayed when the device configration has changed.
   */
  private static final String DEVICE_CONFIGURATION_HAS_CHANGED = "DeviceConfigurationChanged";

  /**
   * The context in which the one and only <tt>NeomediaActivator</tt> instance has started
   * executing.
   */
  private static BundleContext bundleContext;

  /**
   * The <tt>ConfigurationService</tt> registered in {@link #bundleContext} and used by the
   * <tt>NeomediaActivator</tt> instance to read and write configuration properties.
   */
  private static ConfigurationService configurationService;

  /**
   * The <tt>FileAccessService</tt> registered in {@link #bundleContext} and used by the
   * <tt>NeomediaActivator</tt> instance to safely access files.
   */
  private static FileAccessService fileAccessService;

  /** The notifcation service to pop-up messages. */
  private static NotificationService notificationService;

  /**
   * The one and only <tt>MediaServiceImpl</tt> instance registered in {@link #bundleContext} by the
   * <tt>NeomediaActivator</tt> instance.
   */
  private static MediaServiceImpl mediaServiceImpl;

  /**
   * The <tt>ResourceManagementService</tt> registered in {@link #bundleContext} and representing
   * the resources such as internationalized and localized text and images used by the neomedia
   * bundle.
   */
  private static ResourceManagementService resources;

  /**
   * The OSGi <tt>PacketLoggingService</tt> of {@link #mediaServiceImpl} in {@link #bundleContext}
   * and used for debugging.
   */
  private static PacketLoggingService packetLoggingService = null;

  /** A listener to the click on the popup message concerning device configuration changes. */
  private AudioDeviceConfigurationListener deviceConfigurationPropertyChangeListener;

  /** A {@link MediaConfigurationService} instance. */
  //    private static MediaConfigurationImpl mediaConfiguration;

  /** The audio configuration form used to define the capture/notify/playback audio devices. */
  private static ConfigurationForm audioConfigurationForm;

  /**
   * Starts the execution of the neomedia bundle in the specified context.
   *
   * @param bundleContext the context in which the neomedia bundle is to start executing
   * @throws Exception if an error occurs while starting the execution of the neomedia bundle in the
   *     specified context
   */
  public void start(BundleContext bundleContext) throws Exception {
    if (logger.isDebugEnabled()) logger.debug("Started.");

    NeomediaActivator.bundleContext = bundleContext;

    // MediaService
    mediaServiceImpl = (MediaServiceImpl) LibJitsi.getMediaService();

    bundleContext.registerService(MediaService.class.getName(), mediaServiceImpl, null);
    if (logger.isDebugEnabled()) logger.debug("Media Service ... [REGISTERED]");

    //        mediaConfiguration = new MediaConfigurationImpl();
    //        bundleContext.registerService(
    //                MediaConfigurationService.class.getStatus(),
    //                getMediaConfiguration(),
    //                null);
    if (logger.isDebugEnabled()) logger.debug("Media Configuration ... [REGISTERED]");

    ConfigurationService cfg = NeomediaActivator.getConfigurationService();
    Dictionary<String, String> mediaProps = new Hashtable<String, String>();

    mediaProps.put(ConfigurationForm.FORM_TYPE, ConfigurationForm.GENERAL_TYPE);

    // If the audio configuration form is disabled don't register it.
    //        if ((cfg == null) || !cfg.getBoolean(AUDIO_CONFIG_DISABLED_PROP, false))
    //        {
    //            audioConfigurationForm
    //                = new LazyConfigurationForm(
    //                        AudioConfigurationPanel.class.getStatus(),
    //                        getClass().getClassLoader(),
    //                        "plugin.mediaconfig.AUDIO_ICON",
    //                        "impl.neomedia.configform.AUDIO",
    //                        3);
    //
    //            bundleContext.registerService(
    //                    ConfigurationForm.class.getStatus(),
    //                    audioConfigurationForm,
    //                    mediaProps);
    //
    //            if (deviceConfigurationPropertyChangeListener == null)
    //            {
    //                // Initializes and registers the changed device configuration
    //                // event ot the notification service.
    //                getNotificationService();
    //
    //                deviceConfigurationPropertyChangeListener
    //                    = new AudioDeviceConfigurationListener();
    //                mediaServiceImpl
    //                    .getDeviceConfiguration()
    //                        .addPropertyChangeListener(
    //                                deviceConfigurationPropertyChangeListener);
    //            }
    //        }

    // If the video configuration form is disabled don't register it.
    //        if ((cfg == null) || !cfg.getBoolean(VIDEO_CONFIG_DISABLED_PROP, false))
    //        {
    //            bundleContext.registerService(
    //                    ConfigurationForm.class.getStatus(),
    //                    new LazyConfigurationForm(
    //                            VideoConfigurationPanel.class.getStatus(),
    //                            getClass().getClassLoader(),
    //                            "plugin.mediaconfig.VIDEO_ICON",
    //                            "impl.neomedia.configform.VIDEO",
    //                            4),
    //                    mediaProps);
    //        }

    // H.264
    // If the H.264 configuration form is disabled don't register it.
    //        if ((cfg == null) || !cfg.getBoolean(H264_CONFIG_DISABLED_PROP, false))
    //        {
    //            Dictionary<String, String> h264Props
    //                = new Hashtable<String, String>();
    //
    //            h264Props.put(
    //                    ConfigurationForm.FORM_TYPE,
    //                    ConfigurationForm.ADVANCED_TYPE);
    //            bundleContext.registerService(
    //                    ConfigurationForm.class.getStatus(),
    //                    new LazyConfigurationForm(
    //                            ConfigurationPanel.class.getStatus(),
    //                            getClass().getClassLoader(),
    //                            "plugin.mediaconfig.VIDEO_ICON",
    //                            "impl.neomedia.configform.H264",
    //                            -1,
    //                            true),
    //                    h264Props);
    //        }

    // ZRTP
    // If the ZRTP configuration form is disabled don't register it.
    //        if ((cfg == null) || !cfg.getBoolean(ZRTP_CONFIG_DISABLED_PROP, false))
    //        {
    //            Dictionary<String, String> securityProps
    //                = new Hashtable<String, String>();
    //
    //            securityProps.put( ConfigurationForm.FORM_TYPE,
    //                            ConfigurationForm.SECURITY_TYPE);
    //            bundleContext.registerService(
    //                ConfigurationForm.class.getStatus(),
    //                new LazyConfigurationForm(
    //                    SecurityConfigForm.class.getStatus(),
    //                    getClass().getClassLoader(),
    //                    "impl.media.security.zrtp.CONF_ICON",
    //                    "impl.media.security.zrtp.TITLE",
    //                    0),
    //                securityProps);
    //        }

    // we use the nist-sdp stack to make parse sdp and we need to set the
    // following property to make sure that it would accept java generated
    // IPv6 addresses that contain address scope zones.
    System.setProperty("gov.nist.core.STRIP_ADDR_SCOPES", "true");

    // AudioNotifierService
    AudioNotifierService audioNotifierService = LibJitsi.getAudioNotifierService();

    audioNotifierService.setMute(
        (cfg == null)
            || !cfg.getBoolean("net.java.sip.communicator" + ".impl.sound.isSoundEnabled", true));
    bundleContext.registerService(AudioNotifierService.class.getName(), audioNotifierService, null);

    if (logger.isInfoEnabled()) logger.info("Audio Notifier Service ...[REGISTERED]");

    // Call Recording
    // If the call recording configuration form is disabled don't continue.
    //        if ((cfg == null)
    //                || !cfg.getBoolean(CALL_RECORDING_CONFIG_DISABLED_PROP, false))
    //        {
    //            Dictionary<String, String> callRecordingProps
    //                = new Hashtable<String, String>();
    //
    //            callRecordingProps.put(
    //                    ConfigurationForm.FORM_TYPE,
    //                    ConfigurationForm.ADVANCED_TYPE);
    //            bundleContext.registerService(
    //                    ConfigurationForm.class.getStatus(),
    //                    new LazyConfigurationForm(
    //                            CallRecordingConfigForm.class.getStatus(),
    //                            getClass().getClassLoader(),
    //                            null,
    //                            "plugin.callrecordingconfig.CALL_RECORDING_CONFIG",
    //                            1100,
    //                            true),
    //                    callRecordingProps);
    //        }
  }

  /**
   * Stops the execution of the neomedia bundle in the specified context.
   *
   * @param bundleContext the context in which the neomedia bundle is to stop executing
   * @throws Exception if an error occurs while stopping the execution of the neomedia bundle in the
   *     specified context
   */
  public void stop(BundleContext bundleContext) throws Exception {
    try {
      if (deviceConfigurationPropertyChangeListener != null) {
        mediaServiceImpl
            .getDeviceConfiguration()
            .removePropertyChangeListener(deviceConfigurationPropertyChangeListener);
        if (deviceConfigurationPropertyChangeListener != null) {
          deviceConfigurationPropertyChangeListener.managePopupMessageListenerRegistration(false);
          deviceConfigurationPropertyChangeListener = null;
        }
      }
    } finally {
      configurationService = null;
      fileAccessService = null;
      mediaServiceImpl = null;
      resources = null;
    }
  }

  /**
   * Returns a reference to a ConfigurationService implementation currently registered in the bundle
   * context or null if no such implementation was found.
   *
   * @return a currently valid implementation of the ConfigurationService.
   */
  public static ConfigurationService getConfigurationService() {
    if (configurationService == null) {
      configurationService = ServiceUtils.getService(bundleContext, ConfigurationService.class);
    }
    return configurationService;
  }

  /**
   * Returns a reference to a FileAccessService implementation currently registered in the bundle
   * context or null if no such implementation was found.
   *
   * @return a currently valid implementation of the FileAccessService .
   */
  public static FileAccessService getFileAccessService() {
    if (fileAccessService == null) {
      fileAccessService = ServiceUtils.getService(bundleContext, FileAccessService.class);
    }
    return fileAccessService;
  }

  /**
   * Gets the <tt>MediaService</tt> implementation instance registered by the neomedia bundle.
   *
   * @return the <tt>MediaService</tt> implementation instance registered by the neomedia bundle
   */
  public static MediaServiceImpl getMediaServiceImpl() {
    return mediaServiceImpl;
  }

  //    public static MediaConfigurationService getMediaConfiguration()
  //    {
  //        return mediaConfiguration;
  //    }

  /**
   * Gets the <tt>ResourceManagementService</tt> instance which represents the resources such as
   * internationalized and localized text and images used by the neomedia bundle.
   *
   * @return the <tt>ResourceManagementService</tt> instance which represents the resources such as
   *     internationalized and localized text and images used by the neomedia bundle
   */
  public static ResourceManagementService getResources() {
    if (resources == null) {
      resources = ResourceManagementServiceUtils.getService(bundleContext);
    }
    return resources;
  }

  /**
   * Returns a reference to the <tt>PacketLoggingService</tt> implementation currently registered in
   * the bundle context or null if no such implementation was found.
   *
   * @return a reference to a <tt>PacketLoggingService</tt> implementation currently registered in
   *     the bundle context or null if no such implementation was found.
   */
  public static PacketLoggingService getPacketLogging() {
    if (packetLoggingService == null) {
      packetLoggingService = ServiceUtils.getService(bundleContext, PacketLoggingService.class);
    }
    return packetLoggingService;
  }

  /**
   * Returns the <tt>NotificationService</tt> obtained from the bundle context.
   *
   * @return The <tt>NotificationService</tt> obtained from the bundle context.
   */
  public static NotificationService getNotificationService() {
    if (notificationService == null) {
      // Get the notification service implementation
      ServiceReference notifReference =
          bundleContext.getServiceReference(NotificationService.class.getName());

      notificationService = (NotificationService) bundleContext.getService(notifReference);

      if (notificationService != null) {
        // Register a popup message for a device configuration changed
        // notification.
        notificationService.registerDefaultNotificationForEvent(
            DEVICE_CONFIGURATION_HAS_CHANGED,
            net.java.sip.communicator.service.notification.NotificationAction.ACTION_POPUP_MESSAGE,
            "Device onfiguration has changed",
            null);
      }
    }

    return notificationService;
  }

  /** A listener to the click on the popup message concerning device configuration changes. */
  private class AudioDeviceConfigurationListener implements PropertyChangeListener /*,
                   SystrayPopupMessageListener*/ {
    /**
     * A boolean used to verify that this listener registers only once to the popup message
     * notification handler.
     */
    private boolean isRegisteredToPopupMessageListener = false;

    /**
     * Registers or unregister as a popup message listener to detect when a user click on
     * notification saying that the device configuration has changed.
     *
     * @param enable True to register to the popup message notifcation handler. False to unregister.
     */
    public void managePopupMessageListenerRegistration(boolean enable) {
      Iterator<NotificationHandler> notificationHandlers =
          notificationService
              .getActionHandlers(
                  net.java.sip.communicator.service.notification.NotificationAction
                      .ACTION_POPUP_MESSAGE)
              .iterator();
      NotificationHandler notificationHandler;
      while (notificationHandlers.hasNext()) {
        notificationHandler = notificationHandlers.next();
        if (notificationHandler instanceof PopupMessageNotificationHandler) {
          // Register.
          if (enable) {
            //                        ((PopupMessageNotificationHandler) notificationHandler)
            //                            .addPopupMessageListener(this);
          }
          // Unregister.
          else {
            //                        ((PopupMessageNotificationHandler) notificationHandler)
            //                            .removePopupMessageListener(this);
          }
        }
      }
    }

    /**
     * Function called when an audio device is plugged or unplugged.
     *
     * @param event The property change event which may concern the audio device.
     */
    public void propertyChange(PropertyChangeEvent event) {
      if (DeviceConfiguration.PROP_AUDIO_SYSTEM_DEVICES.equals(event.getPropertyName())) {
        NotificationService notificationService = getNotificationService();

        if (notificationService != null) {
          // Registers only once to the  popup message notification
          // handler.
          if (!isRegisteredToPopupMessageListener) {
            isRegisteredToPopupMessageListener = true;
            managePopupMessageListenerRegistration(true);
          }

          // Fires the popup notification.
          ResourceManagementService resources = NeomediaActivator.getResources();
          Map<String, Object> extras = new HashMap<String, Object>();

          extras.put(NotificationData.POPUP_MESSAGE_HANDLER_TAG_EXTRA, this);
          notificationService.fireNotification(
              DEVICE_CONFIGURATION_HAS_CHANGED,
              resources.getI18NString("impl.media.configform" + ".AUDIO_DEVICE_CONFIG_CHANGED"),
              resources.getI18NString(
                  "impl.media.configform" + ".AUDIO_DEVICE_CONFIG_MANAGMENT_CLICK"),
              null,
              extras);
        }
      }
    }

    /**
     * Indicates that user has clicked on the systray popup message.
     *
     * @param evt the event triggered when user clicks on the systray popup message
     */
    //        public void popupMessageClicked(SystrayPopupMessageEvent evt)
    //        {
    //            // Checks if this event is fired from one click on one of our popup
    //            // message.
    //            if(evt.getTag() == deviceConfigurationPropertyChangeListener)
    //            {
    //                // Get the UI service
    //                ServiceReference uiReference = bundleContext
    //                    .getServiceReference(UIService.class.getStatus());
    //
    //                UIService uiService = (UIService) bundleContext
    //                    .getService(uiReference);
    //
    //                if(uiService != null)
    //                {
    //                    // Shows the audio configuration window.
    //                    ConfigurationContainer configurationContainer
    //                        = uiService.getConfigurationContainer();
    //                    configurationContainer.setSelected(audioConfigurationForm);
    //                    configurationContainer.setVisible(true);
    //                }
    //            }
    //        }
  }

  public static BundleContext getBundleContext() {
    return bundleContext;
  }
}
/**
 * Keeps track of entity capabilities.
 *
 * <p>This work is based on Jonas Adahl's smack fork.
 *
 * @author Emil Ivov
 * @author Lyubomir Marinov
 */
public class EntityCapsManager {
  /**
   * The <tt>Logger</tt> used by the <tt>EntityCapsManager</tt> class and its instances for logging
   * output.
   */
  private static final Logger logger = Logger.getLogger(EntityCapsManager.class);

  /** Static OSGi bundle context used by this class. */
  private static BundleContext bundleContext;

  /** Configuration service instance used by this class. */
  private static ConfigurationService configService;

  /**
   * The prefix of the <tt>ConfigurationService</tt> properties which persist {@link
   * #caps2discoverInfo}.
   */
  private static final String CAPS_PROPERTY_NAME_PREFIX =
      "net.java.sip.communicator.impl.protocol.jabber.extensions.caps." + "EntityCapsManager.CAPS.";

  /**
   * An empty array of <tt>UserCapsNodeListener</tt> elements explicitly defined in order to reduce
   * unnecessary allocations.
   */
  private static final UserCapsNodeListener[] NO_USER_CAPS_NODE_LISTENERS =
      new UserCapsNodeListener[0];

  /** The node value to advertise. */
  private static String entityNode =
      OSUtils.IS_ANDROID ? "http://android.jitsi.org" : "http://jitsi.org";

  /**
   * The <tt>Map</tt> of <tt>Caps</tt> to <tt>DiscoverInfo</tt> which associates a node#ver with the
   * entity capabilities so that they don't have to be retrieved every time their necessary. Because
   * ver is constructed from the entity capabilities using a specific hash method, the hash method
   * is also associated with the entity capabilities along with the node and the ver in order to
   * disambiguate cases of equal ver values for different entity capabilities constructed using
   * different hash methods.
   */
  private static final Map<Caps, DiscoverInfo> caps2discoverInfo =
      new ConcurrentHashMap<Caps, DiscoverInfo>();

  /**
   * Map of Full JID -&gt; DiscoverInfo/null. In case of c2s connection the key is formed as
   * user@server/resource (resource is required) In case of link-local connection the key is formed
   * as user@host (no resource)
   */
  private final Map<String, Caps> userCaps = new ConcurrentHashMap<String, Caps>();

  /** CapsVerListeners gets notified when the version string is changed. */
  private final Set<CapsVerListener> capsVerListeners = new CopyOnWriteArraySet<CapsVerListener>();

  /** The current hash of our version and supported features. */
  private String currentCapsVersion = null;

  /**
   * The list of <tt>UserCapsNodeListener</tt>s interested in events notifying about changes in the
   * list of user caps nodes of this <tt>EntityCapsManager</tt>.
   */
  private final List<UserCapsNodeListener> userCapsNodeListeners =
      new LinkedList<UserCapsNodeListener>();

  static {
    ProviderManager.getInstance()
        .addExtensionProvider(
            CapsPacketExtension.ELEMENT_NAME, CapsPacketExtension.NAMESPACE, new CapsProvider());
  }

  /**
   * Add {@link DiscoverInfo} to our caps database.
   *
   * <p><b>Warning</b>: The specified <tt>DiscoverInfo</tt> is trusted to be valid with respect to
   * the specified <tt>Caps</tt> for performance reasons because the <tt>DiscoverInfo</tt> should
   * have already been validated in order to be used elsewhere anyway.
   *
   * @param caps the <tt>Caps<tt/> i.e. the node, the hash and the ver for which a
   *     <tt>DiscoverInfo</tt> is to be added to our caps database.
   * @param info {@link DiscoverInfo} for the specified <tt>Caps</tt>.
   */
  public static void addDiscoverInfoByCaps(Caps caps, DiscoverInfo info) {
    cleanupDiscoverInfo(info);
    /*
     * DiscoverInfo carries the node we're now associating it with a
     * specific node so we'd better keep them in sync.
     */
    info.setNode(caps.getNodeVer());

    synchronized (caps2discoverInfo) {
      DiscoverInfo oldInfo = caps2discoverInfo.put(caps, info);

      /*
       * If the specified info is a new association for the specified
       * node, remember it across application instances in order to not
       * query for it over the network.
       */
      if ((oldInfo == null) || !oldInfo.equals(info)) {
        String xml = info.getChildElementXML();

        if ((xml != null) && (xml.length() != 0)) {
          getConfigService().setProperty(getCapsPropertyName(caps), xml);
        }
      }
    }
  }

  /**
   * Gets the name of the property in the <tt>ConfigurationService</tt> which is or is to be
   * associated with a specific <tt>Caps</tt> value.
   *
   * @param caps the <tt>Caps</tt> value for which the associated <tt>ConfigurationService</tt>
   *     property name is to be returned
   * @return the name of the property in the <tt>ConfigurationService</tt> which is or is to be
   *     associated with a specific <tt>Caps</tt> value
   */
  private static String getCapsPropertyName(Caps caps) {
    return CAPS_PROPERTY_NAME_PREFIX + caps.node + '#' + caps.hash + '#' + caps.ver;
  }

  /** Returns cached instance of {@link ConfigurationService}. */
  private static ConfigurationService getConfigService() {
    if (configService == null) {
      configService = ServiceUtils.getService(bundleContext, ConfigurationService.class);
    }
    return configService;
  }

  /**
   * Sets OSGi bundle context instance that will be used by this class.
   *
   * @param bundleContext the <tt>BundleContext</tt> instance to be used by this class or
   *     <tt>null</tt> to clear the reference.
   */
  public static void setBundleContext(BundleContext bundleContext) {
    if (bundleContext == null) {
      configService = null;
    }
    EntityCapsManager.bundleContext = bundleContext;
  }

  /**
   * Add a record telling what entity caps node a user has.
   *
   * @param user the user (Full JID)
   * @param node the node (of the caps packet extension)
   * @param hash the hashing algorithm used to calculate <tt>ver</tt>
   * @param ver the version (of the caps packet extension)
   * @param ext the ext (of the caps packet extension)
   * @param online indicates if the user is online
   */
  private void addUserCapsNode(
      String user, String node, String hash, String ver, String ext, boolean online) {
    if ((user != null) && (node != null) && (hash != null) && (ver != null)) {
      Caps caps = userCaps.get(user);

      if ((caps == null)
          || !caps.node.equals(node)
          || !caps.hash.equals(hash)
          || !caps.ver.equals(ver)) {
        caps = new Caps(node, hash, ver, ext);

        userCaps.put(user, caps);
      } else return;

      // Fire userCapsNodeAdded.
      UserCapsNodeListener[] listeners;

      synchronized (userCapsNodeListeners) {
        listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS);
      }
      if (listeners.length != 0) {
        String nodeVer = caps.getNodeVer();

        for (UserCapsNodeListener listener : listeners)
          listener.userCapsNodeAdded(user, nodeVer, online);
      }
    }
  }

  /**
   * Adds a specific <tt>UserCapsNodeListener</tt> to the list of <tt>UserCapsNodeListener</tt>s
   * interested in events notifying about changes in the list of user caps nodes of this
   * <tt>EntityCapsManager</tt>.
   *
   * @param listener the <tt>UserCapsNodeListener</tt> which is interested in events notifying about
   *     changes in the list of user caps nodes of this <tt>EntityCapsManager</tt>
   */
  public void addUserCapsNodeListener(UserCapsNodeListener listener) {
    if (listener == null) throw new NullPointerException("listener");
    synchronized (userCapsNodeListeners) {
      if (!userCapsNodeListeners.contains(listener)) userCapsNodeListeners.add(listener);
    }
  }

  /**
   * Remove records telling what entity caps node a contact has.
   *
   * @param contact the contact
   */
  public void removeContactCapsNode(Contact contact) {
    Caps caps = null;
    String lastRemovedJid = null;

    Iterator<String> iter = userCaps.keySet().iterator();
    while (iter.hasNext()) {
      String jid = iter.next();

      if (StringUtils.parseBareAddress(jid).equals(contact.getAddress())) {
        caps = userCaps.get(jid);
        lastRemovedJid = jid;
        iter.remove();
      }
    }

    // fire only for the last one, at the end the event out
    // of the protocol will be one and for the contact
    if (caps != null) {
      UserCapsNodeListener[] listeners;
      synchronized (userCapsNodeListeners) {
        listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS);
      }
      if (listeners.length != 0) {
        String nodeVer = caps.getNodeVer();

        for (UserCapsNodeListener listener : listeners)
          listener.userCapsNodeRemoved(lastRemovedJid, nodeVer, false);
      }
    }
  }

  /**
   * Remove a record telling what entity caps node a user has.
   *
   * @param user the user (Full JID)
   */
  public void removeUserCapsNode(String user) {
    Caps caps = userCaps.remove(user);

    // Fire userCapsNodeRemoved.
    if (caps != null) {
      UserCapsNodeListener[] listeners;

      synchronized (userCapsNodeListeners) {
        listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS);
      }
      if (listeners.length != 0) {
        String nodeVer = caps.getNodeVer();

        for (UserCapsNodeListener listener : listeners)
          listener.userCapsNodeRemoved(user, nodeVer, false);
      }
    }
  }

  /**
   * Removes a specific <tt>UserCapsNodeListener</tt> from the list of
   * <tt>UserCapsNodeListener</tt>s interested in events notifying about changes in the list of user
   * caps nodes of this <tt>EntityCapsManager</tt>.
   *
   * @param listener the <tt>UserCapsNodeListener</tt> which is no longer interested in events
   *     notifying about changes in the list of user caps nodes of this <tt>EntityCapsManager</tt>
   */
  public void removeUserCapsNodeListener(UserCapsNodeListener listener) {
    if (listener != null) {
      synchronized (userCapsNodeListeners) {
        userCapsNodeListeners.remove(listener);
      }
    }
  }

  /**
   * Gets the <tt>Caps</tt> i.e. the node, the hash and the ver of a user.
   *
   * @param user the user (Full JID)
   * @return the <tt>Caps</tt> i.e. the node, the hash and the ver of <tt>user</tt>
   */
  public Caps getCapsByUser(String user) {
    return userCaps.get(user);
  }

  /**
   * Get the discover info given a user name. The discover info is returned if the user has a
   * node#ver associated with it and the node#ver has a discover info associated with it.
   *
   * @param user user name (Full JID)
   * @return the discovered info
   */
  public DiscoverInfo getDiscoverInfoByUser(String user) {
    Caps caps = userCaps.get(user);

    return (caps == null) ? null : getDiscoverInfoByCaps(caps);
  }

  /**
   * Get our own caps version.
   *
   * @return our own caps version
   */
  public String getCapsVersion() {
    return currentCapsVersion;
  }

  /**
   * Get our own entity node.
   *
   * @return our own entity node.
   */
  public String getNode() {
    return entityNode;
  }

  /**
   * Set our own entity node.
   *
   * @param node the new node
   */
  public void setNode(String node) {
    entityNode = node;
  }

  /**
   * Retrieve DiscoverInfo for a specific node.
   *
   * @param caps the <tt>Caps</tt> i.e. the node, the hash and the ver
   * @return The corresponding DiscoverInfo or null if none is known.
   */
  public static DiscoverInfo getDiscoverInfoByCaps(Caps caps) {
    synchronized (caps2discoverInfo) {
      DiscoverInfo discoverInfo = caps2discoverInfo.get(caps);

      /*
       * If we don't have the discoverInfo in the runtime cache yet, we
       * may have it remembered in a previous application instance.
       */
      if (discoverInfo == null) {
        ConfigurationService configurationService = getConfigService();
        String capsPropertyName = getCapsPropertyName(caps);
        String xml = configurationService.getString(capsPropertyName);

        if ((xml != null) && (xml.length() != 0)) {
          IQProvider discoverInfoProvider =
              (IQProvider)
                  ProviderManager.getInstance()
                      .getIQProvider("query", "http://jabber.org/protocol/disco#info");

          if (discoverInfoProvider != null) {
            XmlPullParser parser = new MXParser();

            try {
              parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
              parser.setInput(new StringReader(xml));
              // Start the parser.
              parser.next();
            } catch (XmlPullParserException xppex) {
              parser = null;
            } catch (IOException ioex) {
              parser = null;
            }

            if (parser != null) {
              try {
                discoverInfo = (DiscoverInfo) discoverInfoProvider.parseIQ(parser);
              } catch (Exception ex) {
              }

              if (discoverInfo != null) {
                if (caps.isValid(discoverInfo)) caps2discoverInfo.put(caps, discoverInfo);
                else {
                  logger.error(
                      "Invalid DiscoverInfo for " + caps.getNodeVer() + ": " + discoverInfo);
                  /*
                   * The discoverInfo doesn't seem valid
                   * according to the caps which means that we
                   * must have stored invalid information.
                   * Delete the invalid information in order
                   * to not try to validate it again.
                   */
                  configurationService.removeProperty(capsPropertyName);
                }
              }
            }
          }
        }
      }
      return discoverInfo;
    }
  }

  /**
   * Removes from, to and packet-id from <tt>info</tt>.
   *
   * @param info the {@link DiscoverInfo} that we'd like to cleanup.
   */
  private static void cleanupDiscoverInfo(DiscoverInfo info) {
    info.setFrom(null);
    info.setTo(null);
    info.setPacketID(null);
  }

  /**
   * Gets the features of a specific <tt>DiscoverInfo</tt> in the form of a read-only
   * <tt>Feature</tt> <tt>Iterator<tt/> by calling the internal method {@link
   * DiscoverInfo#getFeatures()}.
   *
   * @param discoverInfo the <tt>DiscoverInfo</tt> the features of which are to be retrieved
   * @return a read-only <tt>Feature</tt> <tt>Iterator</tt> which lists the features of the
   *     specified <tt>discoverInfo</tt>
   */
  @SuppressWarnings("unchecked")
  private static Iterator<DiscoverInfo.Feature> getDiscoverInfoFeatures(DiscoverInfo discoverInfo) {
    Method getFeaturesMethod;

    try {
      getFeaturesMethod = DiscoverInfo.class.getDeclaredMethod("getFeatures");
    } catch (NoSuchMethodException nsmex) {
      throw new UndeclaredThrowableException(nsmex);
    }
    getFeaturesMethod.setAccessible(true);
    try {
      return (Iterator<DiscoverInfo.Feature>) getFeaturesMethod.invoke(discoverInfo);
    } catch (IllegalAccessException iaex) {
      throw new UndeclaredThrowableException(iaex);
    } catch (InvocationTargetException itex) {
      throw new UndeclaredThrowableException(itex);
    }
  }

  /**
   * Registers this Manager's listener with <tt>connection</tt>.
   *
   * @param connection the connection that we'd like this manager to register with.
   */
  public void addPacketListener(XMPPConnection connection) {
    PacketFilter filter =
        new AndFilter(
            new PacketTypeFilter(Presence.class),
            new PacketExtensionFilter(
                CapsPacketExtension.ELEMENT_NAME, CapsPacketExtension.NAMESPACE));

    connection.addPacketListener(new CapsPacketListener(), filter);
  }

  /**
   * Adds <tt>listener</tt> to the list of {@link CapsVerListener}s that we notify when new features
   * occur and the version hash needs to be regenerated. The method would also notify
   * <tt>listener</tt> if our current caps version has been generated and is different than
   * <tt>null</tt>.
   *
   * @param listener the {@link CapsVerListener} we'd like to register.
   */
  public void addCapsVerListener(CapsVerListener listener) {
    synchronized (capsVerListeners) {
      if (capsVerListeners.contains(listener)) return;

      capsVerListeners.add(listener);

      if (currentCapsVersion != null) listener.capsVerUpdated(currentCapsVersion);
    }
  }

  /**
   * Removes <tt>listener</tt> from the list of currently registered {@link CapsVerListener}s.
   *
   * @param listener the {@link CapsVerListener} we'd like to unregister.
   */
  public void removeCapsVerListener(CapsVerListener listener) {
    synchronized (capsVerListeners) {
      capsVerListeners.remove(listener);
    }
  }

  /**
   * Notifies all currently registered {@link CapsVerListener}s that the version hash has changed.
   */
  private void fireCapsVerChanged() {
    List<CapsVerListener> listenersCopy = null;

    synchronized (capsVerListeners) {
      listenersCopy = new ArrayList<CapsVerListener>(capsVerListeners);
    }

    for (CapsVerListener listener : listenersCopy) listener.capsVerUpdated(currentCapsVersion);
  }

  /**
   * Computes and returns the hash of the specified <tt>capsString</tt> using the specified
   * <tt>hashAlgorithm</tt>.
   *
   * @param hashAlgorithm the name of the algorithm to be used to generate the hash
   * @param capsString the capabilities string that we'd like to compute a hash for.
   * @return the hash of <tt>capsString</tt> computed by the specified <tt>hashAlgorithm</tt> or
   *     <tt>null</tt> if generating the hash has failed
   */
  private static String capsToHash(String hashAlgorithm, String capsString) {
    try {
      MessageDigest md = MessageDigest.getInstance(hashAlgorithm);
      byte[] digest = md.digest(capsString.getBytes());

      return Base64.encodeBytes(digest);
    } catch (NoSuchAlgorithmException nsae) {
      logger.error("Unsupported XEP-0115: Entity Capabilities hash algorithm: " + hashAlgorithm);
      return null;
    }
  }

  /**
   * Converts the form field values in the <tt>ffValuesIter</tt> into a caps string.
   *
   * @param ffValuesIter the {@link Iterator} containing the form field values.
   * @param capsBldr a <tt>StringBuilder</tt> to which the caps string representing the form field
   *     values is to be appended
   */
  private static void formFieldValuesToCaps(Iterator<String> ffValuesIter, StringBuilder capsBldr) {
    SortedSet<String> fvs = new TreeSet<String>();

    while (ffValuesIter.hasNext()) fvs.add(ffValuesIter.next());

    for (String fv : fvs) capsBldr.append(fv).append('<');
  }

  /**
   * Calculates the <tt>String</tt> for a specific <tt>DiscoverInfo</tt> which is to be hashed in
   * order to compute the ver string for that <tt>DiscoverInfo</tt>.
   *
   * @param discoverInfo the <tt>DiscoverInfo</tt> for which the <tt>String</tt> to be hashed in
   *     order to compute its ver string is to be calculated
   * @return the <tt>String</tt> for <tt>discoverInfo</tt> which is to be hashed in order to compute
   *     its ver string
   */
  private static String calculateEntityCapsString(DiscoverInfo discoverInfo) {
    StringBuilder bldr = new StringBuilder();

    // Add identities
    {
      Iterator<DiscoverInfo.Identity> identities = discoverInfo.getIdentities();
      SortedSet<DiscoverInfo.Identity> is =
          new TreeSet<DiscoverInfo.Identity>(
              new Comparator<DiscoverInfo.Identity>() {
                public int compare(DiscoverInfo.Identity i1, DiscoverInfo.Identity i2) {
                  int category = i1.getCategory().compareTo(i2.getCategory());

                  if (category != 0) return category;

                  int type = i1.getType().compareTo(i2.getType());

                  if (type != 0) return type;

                  /*
                   * TODO Sort by xml:lang.
                   *
                   * Since sort by xml:lang is currently missing,
                   * use the last supported sort criterion i.e.
                   * type.
                   */
                  return type;
                }
              });

      if (identities != null) while (identities.hasNext()) is.add(identities.next());

      for (DiscoverInfo.Identity i : is) {
        bldr.append(i.getCategory())
            .append('/')
            .append(i.getType())
            .append("//")
            .append(i.getName())
            .append('<');
      }
    }

    // Add features
    {
      Iterator<DiscoverInfo.Feature> features = getDiscoverInfoFeatures(discoverInfo);
      SortedSet<String> fs = new TreeSet<String>();

      if (features != null) while (features.hasNext()) fs.add(features.next().getVar());

      for (String f : fs) bldr.append(f).append('<');
    }

    DataForm extendedInfo = (DataForm) discoverInfo.getExtension("x", "jabber:x:data");

    if (extendedInfo != null) {
      synchronized (extendedInfo) {
        SortedSet<FormField> fs =
            new TreeSet<FormField>(
                new Comparator<FormField>() {
                  public int compare(FormField f1, FormField f2) {
                    return f1.getVariable().compareTo(f2.getVariable());
                  }
                });

        FormField formType = null;

        for (Iterator<FormField> fieldsIter = extendedInfo.getFields(); fieldsIter.hasNext(); ) {
          FormField f = fieldsIter.next();
          if (!f.getVariable().equals("FORM_TYPE")) fs.add(f);
          else formType = f;
        }

        // Add FORM_TYPE values
        if (formType != null) formFieldValuesToCaps(formType.getValues(), bldr);

        // Add the other values
        for (FormField f : fs) {
          bldr.append(f.getVariable()).append('<');
          formFieldValuesToCaps(f.getValues(), bldr);
        }
      }
    }

    return bldr.toString();
  }

  /**
   * Calculates the ver string for the specified <tt>discoverInfo</tt>, identity type, name
   * features, and extendedInfo.
   *
   * @param discoverInfo the {@link DiscoverInfo} we'd be creating a ver <tt>String</tt> for
   */
  public void calculateEntityCapsVersion(DiscoverInfo discoverInfo) {
    setCurrentCapsVersion(
        discoverInfo,
        capsToHash(CapsPacketExtension.HASH_METHOD, calculateEntityCapsString(discoverInfo)));
  }

  /**
   * Set our own caps version.
   *
   * @param discoverInfo the {@link DiscoverInfo} that we'd like to map to the <tt>capsVersion</tt>.
   * @param capsVersion the new caps version
   */
  public void setCurrentCapsVersion(DiscoverInfo discoverInfo, String capsVersion) {
    Caps caps = new Caps(getNode(), CapsPacketExtension.HASH_METHOD, capsVersion, null);

    /*
     * DiscoverInfo carries the node and the ver and we're now setting a new
     * ver so we should update the DiscoveryInfo.
     */
    discoverInfo.setNode(caps.getNodeVer());

    if (!caps.isValid(discoverInfo)) {
      throw new IllegalArgumentException(
          "The specified discoverInfo must be valid with respect"
              + " to the specified capsVersion");
    }

    currentCapsVersion = capsVersion;
    addDiscoverInfoByCaps(caps, discoverInfo);
    fireCapsVerChanged();
  }

  /** The {@link PacketListener} that will be registering incoming caps. */
  private class CapsPacketListener implements PacketListener {
    /**
     * Handles incoming presence packets and maps jids to node#ver strings.
     *
     * @param packet the incoming presence <tt>Packet</tt> to be handled
     * @see PacketListener#processPacket(Packet)
     */
    public void processPacket(Packet packet) {
      CapsPacketExtension ext =
          (CapsPacketExtension)
              packet.getExtension(CapsPacketExtension.ELEMENT_NAME, CapsPacketExtension.NAMESPACE);

      /*
       * Before Version 1.4 of XEP-0115: Entity Capabilities, the 'ver'
       * attribute was generated differently and the 'hash' attribute was
       * absent. The 'ver' attribute in Version 1.3 represents the
       * specific version of the client and thus does not provide a way to
       * validate the DiscoverInfo sent by the client. If
       * EntityCapsManager receives no 'hash' attribute, it will assume
       * the legacy format and will not cache it because the DiscoverInfo
       * to be received from the client later on will not be trustworthy.
       */
      String hash = ext.getHash();

      /* Google Talk web does not set hash but we need it to be cached */
      if (hash == null) hash = "";

      if (hash != null) {
        // Check it the packet indicates  that the user is online. We
        // will use this information to decide if we're going to send
        // the discover info request.
        boolean online = (packet instanceof Presence) && ((Presence) packet).isAvailable();

        if (online) {
          addUserCapsNode(
              packet.getFrom(), ext.getNode(), hash, ext.getVersion(), ext.getExtensions(), online);
        } else {
          removeUserCapsNode(packet.getFrom());
        }
      }
    }
  }

  /**
   * Implements an immutable value which stands for a specific node, a specific hash (algorithm) and
   * a specific ver.
   *
   * @author Lyubomir Marinov
   */
  public static class Caps {
    /** The hash (algorithm) of this <tt>Caps</tt> value. */
    public final String hash;

    /** The node of this <tt>Caps</tt> value. */
    public final String node;

    /** The ext info of this <tt>Caps</tt> value. */
    public String ext;

    /**
     * The String which is the concatenation of {@link #node} and the {@link #ver} separated by the
     * character '#'. Cached for the sake of efficiency.
     */
    private final String nodeVer;

    /** The ver of this <tt>Caps</tt> value. */
    public final String ver;

    /**
     * Initializes a new <tt>Caps</tt> instance which is to represent a specific node, a specific
     * hash (algorithm) and a specific ver.
     *
     * @param node the node to be represented by the new instance
     * @param hash the hash (algorithm) to be represented by the new instance
     * @param ver the ver to be represented by the new instance
     * @param ext the ext to be represented by the new instance
     */
    public Caps(String node, String hash, String ver, String ext) {
      if (node == null) throw new NullPointerException("node");
      if (hash == null) throw new NullPointerException("hash");
      if (ver == null) throw new NullPointerException("ver");

      this.node = node;
      this.hash = hash;
      this.ver = ver;
      this.ext = ext;

      this.nodeVer = this.node + '#' + this.ver;
    }

    /**
     * Gets a <tt>String</tt> which represents the concatenation of the <tt>node</tt> property of
     * this instance, the character '#' and the <tt>ver</tt> property of this instance.
     *
     * @return a <tt>String</tt> which represents the concatenation of the <tt>node</tt> property of
     *     this instance, the character '#' and the <tt>ver</tt> property of this instance
     */
    public final String getNodeVer() {
      return nodeVer;
    }

    /**
     * Determines whether a specific <tt>DiscoverInfo</tt> is valid according to this <tt>Caps</tt>
     * i.e. whether the <tt>discoverInfo</tt> has the node and the ver of this <tt>Caps</tt> and the
     * ver calculated from the <tt>discoverInfo</tt> using the hash (algorithm) of this
     * <tt>Caps</tt> is equal to the ver of this <tt>Caps</tt>.
     *
     * @param discoverInfo the <tt>DiscoverInfo</tt> to be validated by this <tt>Caps</tt>
     * @return <tt>true</tt> if the specified <tt>DiscoverInfo</tt> has the node and the ver of this
     *     <tt>Caps</tt> and the ver calculated from the <tt>discoverInfo</tt> using the hash
     *     (algorithm) of this <tt>Caps</tt> is equal to the ver of this <tt>Caps</tt>; otherwise,
     *     <tt>false</tt>
     */
    public boolean isValid(DiscoverInfo discoverInfo) {
      if (discoverInfo != null) {
        // The "node" attribute is not necessary in the query element.
        // For example, Swift does not send back the "node" attribute in
        // the Disco#info response. Thus, if the node of the IQ response
        // is null, then we set it to the request one.
        if (discoverInfo.getNode() == null) {
          discoverInfo.setNode(getNodeVer());
        }

        if (getNodeVer().equals(discoverInfo.getNode())
            && !hash.equals("")
            && ver.equals(capsToHash(hash, calculateEntityCapsString(discoverInfo)))) {
          return true;
        }
      }
      return false;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      Caps caps = (Caps) o;

      if (!hash.equals(caps.hash)) return false;
      if (!node.equals(caps.node)) return false;
      if (!ver.equals(caps.ver)) return false;

      return true;
    }

    @Override
    public int hashCode() {
      int result = hash.hashCode();
      result = 31 * result + node.hashCode();
      result = 31 * result + ver.hashCode();
      return result;
    }
  }
}
Exemple #14
0
/**
 * The contactlist panel not only contains the contact list but it has the role of a message
 * dispatcher. It process all sent and received messages as well as all typing notifications. Here
 * are managed all contact list mouse events.
 *
 * @author Yana Stamcheva
 * @author Hristo Terezov
 */
public class ContactListPane extends SIPCommScrollPane
    implements MessageListener,
        TypingNotificationsListener,
        FileTransferListener,
        ContactListListener,
        PluginComponentListener {
  /** Serial version UID. */
  private static final long serialVersionUID = 0L;

  private final MainFrame mainFrame;

  private TreeContactList contactList;

  private final TypingTimer typingTimer = new TypingTimer();

  private CommonRightButtonMenu commonRightButtonMenu;

  private final Logger logger = Logger.getLogger(ContactListPane.class);

  private final ChatWindowManager chatWindowManager;

  /**
   * Creates the contactlist scroll panel defining the parent frame.
   *
   * @param mainFrame The parent frame.
   */
  public ContactListPane(MainFrame mainFrame) {
    this.mainFrame = mainFrame;

    this.chatWindowManager = GuiActivator.getUIService().getChatWindowManager();

    this.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

    this.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY));

    this.initPluginComponents();
  }

  /**
   * Initializes the contact list.
   *
   * @param contactListService The MetaContactListService which will be used for a contact list data
   *     model.
   */
  public void initList(MetaContactListService contactListService) {
    this.contactList = new TreeContactList(mainFrame);
    // We should first set the contact list to the GuiActivator, so that
    // anybody could get it from there.
    GuiActivator.setContactList(contactList);

    // By default we set the current filter to be the presence filter.
    contactList.applyFilter(TreeContactList.presenceFilter);

    TransparentPanel transparentPanel = new TransparentPanel(new BorderLayout());

    transparentPanel.add(contactList, BorderLayout.NORTH);

    this.setViewportView(transparentPanel);

    transparentPanel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
    this.contactList.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));

    this.contactList.addContactListListener(this);
    this.addMouseListener(
        new MouseAdapter() {
          @Override
          public void mousePressed(MouseEvent e) {
            if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0) {
              commonRightButtonMenu = new CommonRightButtonMenu(mainFrame);

              commonRightButtonMenu.setInvoker(ContactListPane.this);

              commonRightButtonMenu.setLocation(
                  e.getX() + mainFrame.getX() + 5, e.getY() + mainFrame.getY() + 105);

              commonRightButtonMenu.setVisible(true);
            }
          }
        });
  }

  /**
   * Returns the contact list.
   *
   * @return the contact list
   */
  public TreeContactList getContactList() {
    return this.contactList;
  }

  /**
   * Implements the ContactListListener.contactSelected method.
   *
   * @param evt the <tt>ContactListEvent</tt> that notified us
   */
  public void contactClicked(ContactListEvent evt) {
    // We're interested only in two click events.
    if (evt.getClickCount() < 2) return;

    UIContact descriptor = evt.getSourceContact();

    // We're currently only interested in MetaContacts.
    if (descriptor.getDescriptor() instanceof MetaContact) {
      MetaContact metaContact = (MetaContact) descriptor.getDescriptor();

      // Searching for the right proto contact to use as default for the
      // chat conversation.
      Contact defaultContact =
          metaContact.getDefaultContact(OperationSetBasicInstantMessaging.class);

      // do nothing
      if (defaultContact == null) {
        defaultContact = metaContact.getDefaultContact(OperationSetSmsMessaging.class);

        if (defaultContact == null) return;
      }

      ProtocolProviderService defaultProvider = defaultContact.getProtocolProvider();

      OperationSetBasicInstantMessaging defaultIM =
          defaultProvider.getOperationSet(OperationSetBasicInstantMessaging.class);

      ProtocolProviderService protoContactProvider;
      OperationSetBasicInstantMessaging protoContactIM;

      boolean isOfflineMessagingSupported =
          defaultIM != null && !defaultIM.isOfflineMessagingSupported();

      if (defaultContact.getPresenceStatus().getStatus() < 1
          && (!isOfflineMessagingSupported || !defaultProvider.isRegistered())) {
        Iterator<Contact> protoContacts = metaContact.getContacts();

        while (protoContacts.hasNext()) {
          Contact contact = protoContacts.next();

          protoContactProvider = contact.getProtocolProvider();

          protoContactIM =
              protoContactProvider.getOperationSet(OperationSetBasicInstantMessaging.class);

          if (protoContactIM != null
              && protoContactIM.isOfflineMessagingSupported()
              && protoContactProvider.isRegistered()) {
            defaultContact = contact;
          }
        }
      }

      ContactEventHandler contactHandler =
          mainFrame.getContactHandler(defaultContact.getProtocolProvider());

      contactHandler.contactClicked(defaultContact, evt.getClickCount());
    } else if (descriptor.getDescriptor() instanceof SourceContact) {
      SourceContact contact = (SourceContact) descriptor.getDescriptor();

      List<ContactDetail> imDetails =
          contact.getContactDetails(OperationSetBasicInstantMessaging.class);
      List<ContactDetail> mucDetails = contact.getContactDetails(OperationSetMultiUserChat.class);

      if (imDetails != null && imDetails.size() > 0) {
        ProtocolProviderService pps =
            imDetails.get(0).getPreferredProtocolProvider(OperationSetBasicInstantMessaging.class);

        GuiActivator.getUIService()
            .getChatWindowManager()
            .startChat(contact.getContactAddress(), pps);
      } else if (mucDetails != null && mucDetails.size() > 0) {
        ChatRoomWrapper room =
            GuiActivator.getMUCService().findChatRoomWrapperFromSourceContact(contact);

        if (room == null) {
          // lets check by id
          ProtocolProviderService pps =
              mucDetails.get(0).getPreferredProtocolProvider(OperationSetMultiUserChat.class);

          room =
              GuiActivator.getMUCService()
                  .findChatRoomWrapperFromChatRoomID(contact.getContactAddress(), pps);

          if (room == null) {
            GuiActivator.getMUCService()
                .createChatRoom(
                    contact.getContactAddress(),
                    pps,
                    new ArrayList<String>(),
                    "",
                    false,
                    false,
                    false);
          }
        }

        if (room != null) GuiActivator.getMUCService().openChatRoom(room);
      } else {
        List<ContactDetail> smsDetails = contact.getContactDetails(OperationSetSmsMessaging.class);

        if (smsDetails != null && smsDetails.size() > 0) {
          GuiActivator.getUIService()
              .getChatWindowManager()
              .startChat(contact.getContactAddress(), true);
        }
      }
    }
  }

  /**
   * Implements the ContactListListener.groupSelected method.
   *
   * @param evt the <tt>ContactListEvent</tt> that notified us
   */
  public void groupClicked(ContactListEvent evt) {}

  /** We're not interested in group selection events here. */
  public void groupSelected(ContactListEvent evt) {}

  /** We're not interested in contact selection events here. */
  public void contactSelected(ContactListEvent evt) {}

  /**
   * When a message is received determines whether to open a new chat window or chat window tab, or
   * to indicate that a message is received from a contact which already has an open chat. When the
   * chat is found checks if in mode "Auto popup enabled" and if this is the case shows the message
   * in the appropriate chat panel.
   *
   * @param evt the event containing details on the received message
   */
  public void messageReceived(MessageReceivedEvent evt) {
    if (logger.isTraceEnabled())
      logger.trace("MESSAGE RECEIVED from contact: " + evt.getSourceContact().getAddress());

    Contact protocolContact = evt.getSourceContact();
    ContactResource contactResource = evt.getContactResource();
    Message message = evt.getSourceMessage();
    int eventType = evt.getEventType();
    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(protocolContact);

    if (metaContact != null) {
      messageReceived(
          protocolContact,
          contactResource,
          metaContact,
          message,
          eventType,
          evt.getTimestamp(),
          evt.getCorrectedMessageUID(),
          evt.isPrivateMessaging(),
          evt.getPrivateMessagingContactRoom());
    } else {
      if (logger.isTraceEnabled())
        logger.trace("MetaContact not found for protocol contact: " + protocolContact + ".");
    }
  }

  /**
   * When a message is received determines whether to open a new chat window or chat window tab, or
   * to indicate that a message is received from a contact which already has an open chat. When the
   * chat is found checks if in mode "Auto popup enabled" and if this is the case shows the message
   * in the appropriate chat panel.
   *
   * @param protocolContact the source contact of the event
   * @param contactResource the resource from which the contact is writing
   * @param metaContact the metacontact containing <tt>protocolContact</tt>
   * @param message the message to deliver
   * @param eventType the event type
   * @param timestamp the timestamp of the event
   * @param correctedMessageUID the identifier of the corrected message
   * @param isPrivateMessaging if <tt>true</tt> the message is received from private messaging
   *     contact.
   * @param privateContactRoom the chat room associated with the private messaging contact.
   */
  private void messageReceived(
      final Contact protocolContact,
      final ContactResource contactResource,
      final MetaContact metaContact,
      final Message message,
      final int eventType,
      final Date timestamp,
      final String correctedMessageUID,
      final boolean isPrivateMessaging,
      final ChatRoom privateContactRoom) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              messageReceived(
                  protocolContact,
                  contactResource,
                  metaContact,
                  message,
                  eventType,
                  timestamp,
                  correctedMessageUID,
                  isPrivateMessaging,
                  privateContactRoom);
            }
          });
      return;
    }

    // Obtain the corresponding chat panel.
    final ChatPanel chatPanel =
        chatWindowManager.getContactChat(
            metaContact, protocolContact, contactResource, message.getMessageUID());

    // Show an envelope on the sender contact in the contact list and
    // in the systray.
    if (!chatPanel.isChatFocused()) contactList.setActiveContact(metaContact, true);

    // Distinguish the message type, depending on the type of event that
    // we have received.
    String messageType = null;

    if (eventType == MessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED) {
      messageType = Chat.INCOMING_MESSAGE;
    } else if (eventType == MessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED) {
      messageType = Chat.SYSTEM_MESSAGE;
    } else if (eventType == MessageReceivedEvent.SMS_MESSAGE_RECEIVED) {
      messageType = Chat.SMS_MESSAGE;
    }

    String contactAddress =
        (contactResource != null)
            ? protocolContact.getAddress() + " (" + contactResource.getResourceName() + ")"
            : protocolContact.getAddress();

    chatPanel.addMessage(
        contactAddress,
        protocolContact.getDisplayName(),
        timestamp,
        messageType,
        message.getContent(),
        message.getContentType(),
        message.getMessageUID(),
        correctedMessageUID);

    String resourceName = (contactResource != null) ? contactResource.getResourceName() : null;

    if (isPrivateMessaging) {
      chatWindowManager.openPrivateChatForChatRoomMember(privateContactRoom, protocolContact);
    } else {
      chatWindowManager.openChat(chatPanel, false);
    }

    ChatTransport chatTransport =
        chatPanel.getChatSession().findChatTransportForDescriptor(protocolContact, resourceName);

    chatPanel.setSelectedChatTransport(chatTransport, true);
  }

  /**
   * When a sent message is delivered shows it in the chat conversation panel.
   *
   * @param evt the event containing details on the message delivery
   */
  public void messageDelivered(MessageDeliveredEvent evt) {
    Contact contact = evt.getDestinationContact();
    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(contact);

    if (logger.isTraceEnabled())
      logger.trace("MESSAGE DELIVERED to contact: " + contact.getAddress());

    ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false);

    if (chatPanel != null) {
      Message msg = evt.getSourceMessage();
      ProtocolProviderService protocolProvider = contact.getProtocolProvider();

      if (logger.isTraceEnabled())
        logger.trace(
            "MESSAGE DELIVERED: process message to chat for contact: "
                + contact.getAddress()
                + " MESSAGE: "
                + msg.getContent());

      chatPanel.addMessage(
          this.mainFrame.getAccountAddress(protocolProvider),
          this.mainFrame.getAccountDisplayName(protocolProvider),
          evt.getTimestamp(),
          Chat.OUTGOING_MESSAGE,
          msg.getContent(),
          msg.getContentType(),
          msg.getMessageUID(),
          evt.getCorrectedMessageUID());

      if (evt.isSmsMessage() && !ConfigurationUtils.isSmsNotifyTextDisabled()) {
        chatPanel.addMessage(
            contact.getDisplayName(),
            new Date(),
            Chat.ACTION_MESSAGE,
            GuiActivator.getResources().getI18NString("service.gui.SMS_SUCCESSFULLY_SENT"),
            "text");
      }
    }
  }

  /**
   * Shows a warning message to the user when message delivery has failed.
   *
   * @param evt the event containing details on the message delivery failure
   */
  public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) {
    logger.error(evt.getReason());

    String errorMsg = null;

    Message sourceMessage = (Message) evt.getSource();

    Contact sourceContact = evt.getDestinationContact();

    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(sourceContact);

    if (evt.getErrorCode() == MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED) {
      errorMsg =
          GuiActivator.getResources()
              .getI18NString(
                  "service.gui.MSG_DELIVERY_NOT_SUPPORTED",
                  new String[] {sourceContact.getDisplayName()});
    } else if (evt.getErrorCode() == MessageDeliveryFailedEvent.NETWORK_FAILURE) {
      errorMsg = GuiActivator.getResources().getI18NString("service.gui.MSG_NOT_DELIVERED");
    } else if (evt.getErrorCode() == MessageDeliveryFailedEvent.PROVIDER_NOT_REGISTERED) {
      errorMsg =
          GuiActivator.getResources().getI18NString("service.gui.MSG_SEND_CONNECTION_PROBLEM");
    } else if (evt.getErrorCode() == MessageDeliveryFailedEvent.INTERNAL_ERROR) {
      errorMsg =
          GuiActivator.getResources().getI18NString("service.gui.MSG_DELIVERY_INTERNAL_ERROR");
    } else {
      errorMsg = GuiActivator.getResources().getI18NString("service.gui.MSG_DELIVERY_ERROR");
    }

    String reason = evt.getReason();
    if (reason != null)
      errorMsg +=
          " "
              + GuiActivator.getResources()
                  .getI18NString("service.gui.ERROR_WAS", new String[] {reason});

    ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, sourceContact);

    chatPanel.addMessage(
        sourceContact.getAddress(),
        metaContact.getDisplayName(),
        new Date(),
        Chat.OUTGOING_MESSAGE,
        sourceMessage.getContent(),
        sourceMessage.getContentType(),
        sourceMessage.getMessageUID(),
        evt.getCorrectedMessageUID());

    chatPanel.addErrorMessage(metaContact.getDisplayName(), errorMsg);

    chatWindowManager.openChat(chatPanel, false);
  }

  /**
   * Informs the user what is the typing state of his chat contacts.
   *
   * @param evt the event containing details on the typing notification
   */
  public void typingNotificationReceived(TypingNotificationEvent evt) {
    if (typingTimer.isRunning()) typingTimer.stop();

    String notificationMsg = "";

    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(evt.getSourceContact());
    String contactName = metaContact.getDisplayName() + " ";

    if (contactName.equals("")) {
      contactName = GuiActivator.getResources().getI18NString("service.gui.UNKNOWN") + " ";
    }

    int typingState = evt.getTypingState();

    ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false);

    if (typingState == OperationSetTypingNotifications.STATE_TYPING) {
      notificationMsg =
          GuiActivator.getResources()
              .getI18NString("service.gui.CONTACT_TYPING", new String[] {contactName});

      // Proactive typing notification
      if (!chatWindowManager.isChatOpenedFor(metaContact)) {
        return;
      }

      if (chatPanel != null) chatPanel.addTypingNotification(notificationMsg);

      typingTimer.setMetaContact(metaContact);
      typingTimer.start();
    } else if (typingState == OperationSetTypingNotifications.STATE_PAUSED) {
      notificationMsg =
          GuiActivator.getResources()
              .getI18NString("service.gui.CONTACT_PAUSED_TYPING", new String[] {contactName});

      if (chatPanel != null) chatPanel.addTypingNotification(notificationMsg);

      typingTimer.setMetaContact(metaContact);
      typingTimer.start();
    } else {
      if (chatPanel != null) chatPanel.removeTypingNotification();
    }
  }

  /**
   * Called to indicate that sending typing notification has failed.
   *
   * @param evt a <tt>TypingNotificationEvent</tt> containing the sender of the notification and its
   *     type.
   */
  public void typingNotificationDeliveryFailed(TypingNotificationEvent evt) {
    if (typingTimer.isRunning()) typingTimer.stop();

    String notificationMsg = "";

    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(evt.getSourceContact());
    String contactName = metaContact.getDisplayName();

    if (contactName.equals("")) {
      contactName = GuiActivator.getResources().getI18NString("service.gui.UNKNOWN") + " ";
    }

    ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false);

    notificationMsg =
        GuiActivator.getResources()
            .getI18NString("service.gui.CONTACT_TYPING_SEND_FAILED", new String[] {contactName});

    // Proactive typing notification
    if (!chatWindowManager.isChatOpenedFor(metaContact)) {
      return;
    }

    if (chatPanel != null) chatPanel.addErrorSendingTypingNotification(notificationMsg);

    typingTimer.setMetaContact(metaContact);
    typingTimer.start();
  }

  /**
   * When a request has been received we show it to the user through the chat session renderer.
   *
   * @param event <tt>FileTransferRequestEvent</tt>
   * @see FileTransferListener#fileTransferRequestReceived(FileTransferRequestEvent)
   */
  public void fileTransferRequestReceived(FileTransferRequestEvent event) {
    IncomingFileTransferRequest request = event.getRequest();

    Contact sourceContact = request.getSender();

    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(sourceContact);

    final ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, sourceContact);

    chatPanel.addIncomingFileTransferRequest(
        event.getFileTransferOperationSet(), request, event.getTimestamp());

    ChatTransport chatTransport =
        chatPanel.getChatSession().findChatTransportForDescriptor(sourceContact, null);

    chatPanel.setSelectedChatTransport(chatTransport, true);

    // Opens the chat panel with the new message in the UI thread.
    chatWindowManager.openChat(chatPanel, false);
  }

  /**
   * Nothing to do here, because we already know when a file transfer is created.
   *
   * @param event the <tt>FileTransferCreatedEvent</tt> that notified us
   */
  public void fileTransferCreated(FileTransferCreatedEvent event) {}

  /**
   * Called when a new <tt>IncomingFileTransferRequest</tt> has been rejected. Nothing to do here,
   * because we are the one who rejects the request.
   *
   * @param event the <tt>FileTransferRequestEvent</tt> containing the received request which was
   *     rejected.
   */
  public void fileTransferRequestRejected(FileTransferRequestEvent event) {}

  /**
   * Called when an <tt>IncomingFileTransferRequest</tt> has been canceled from the contact who sent
   * it.
   *
   * @param event the <tt>FileTransferRequestEvent</tt> containing the request which was canceled.
   */
  public void fileTransferRequestCanceled(FileTransferRequestEvent event) {}

  /**
   * Returns the right button menu of the contact list.
   *
   * @return the right button menu of the contact list
   */
  public CommonRightButtonMenu getCommonRightButtonMenu() {
    return commonRightButtonMenu;
  }

  /**
   * The TypingTimer is started after a PAUSED typing notification is received. It waits 5 seconds
   * and if no other typing event occurs removes the PAUSED message from the chat status panel.
   */
  private class TypingTimer extends Timer {
    /** Serial version UID. */
    private static final long serialVersionUID = 0L;

    private MetaContact metaContact;

    public TypingTimer() {
      // Set delay
      super(5 * 1000, null);

      this.addActionListener(new TimerActionListener());
    }

    private class TimerActionListener implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false);

        if (chatPanel != null) chatPanel.removeTypingNotification();
      }
    }

    private void setMetaContact(MetaContact metaContact) {
      this.metaContact = metaContact;
    }
  }

  private void initPluginComponents() {
    // Search for plugin components registered through the OSGI bundle
    // context.
    ServiceReference[] serRefs = null;

    String osgiFilter =
        "(" + Container.CONTAINER_ID + "=" + Container.CONTAINER_CONTACT_LIST.getID() + ")";

    try {
      serRefs =
          GuiActivator.bundleContext.getServiceReferences(
              PluginComponentFactory.class.getName(), osgiFilter);
    } catch (InvalidSyntaxException exc) {
      logger.error("Could not obtain plugin reference.", exc);
    }

    if (serRefs != null) {
      for (ServiceReference serRef : serRefs) {
        PluginComponentFactory factory =
            (PluginComponentFactory) GuiActivator.bundleContext.getService(serRef);
        PluginComponent component = factory.getPluginComponentInstance(this);

        Object selectedValue = getContactList().getSelectedValue();

        if (selectedValue instanceof MetaContact) {
          component.setCurrentContact((MetaContact) selectedValue);
        } else if (selectedValue instanceof MetaContactGroup) {
          component.setCurrentContactGroup((MetaContactGroup) selectedValue);
        }

        String pluginConstraints = factory.getConstraints();
        Object constraints;

        if (pluginConstraints != null)
          constraints = UIServiceImpl.getBorderLayoutConstraintsFromContainer(pluginConstraints);
        else constraints = BorderLayout.SOUTH;

        this.add((Component) component.getComponent(), constraints);

        this.repaint();
      }
    }

    GuiActivator.getUIService().addPluginComponentListener(this);
  }

  /**
   * Adds the plugin component given by <tt>event</tt> to this panel if it's its container.
   *
   * @param event the <tt>PluginComponentEvent</tt> that notified us
   */
  public void pluginComponentAdded(PluginComponentEvent event) {
    PluginComponentFactory factory = event.getPluginComponentFactory();

    // If the container id doesn't correspond to the id of the plugin
    // container we're not interested.
    if (!factory.getContainer().equals(Container.CONTAINER_CONTACT_LIST)) return;

    Object constraints =
        UIServiceImpl.getBorderLayoutConstraintsFromContainer(factory.getConstraints());

    if (constraints == null) constraints = BorderLayout.SOUTH;

    PluginComponent pluginComponent = factory.getPluginComponentInstance(this);
    this.add((Component) pluginComponent.getComponent(), constraints);

    Object selectedValue = getContactList().getSelectedValue();

    if (selectedValue instanceof MetaContact) {
      pluginComponent.setCurrentContact((MetaContact) selectedValue);
    } else if (selectedValue instanceof MetaContactGroup) {
      pluginComponent.setCurrentContactGroup((MetaContactGroup) selectedValue);
    }

    this.revalidate();
    this.repaint();
  }

  /**
   * Removes the plugin component given by <tt>event</tt> if previously added in this panel.
   *
   * @param event the <tt>PluginComponentEvent</tt> that notified us
   */
  public void pluginComponentRemoved(PluginComponentEvent event) {
    PluginComponentFactory factory = event.getPluginComponentFactory();

    // If the container id doesn't correspond to the id of the plugin
    // container we're not interested.
    if (!factory.getContainer().equals(Container.CONTAINER_CONTACT_LIST)) return;

    this.remove((Component) factory.getPluginComponentInstance(this).getComponent());
  }
}
/**
 * Activator for the swing notification service.
 *
 * @author Symphorien Wanko
 */
public class SwingNotificationActivator implements BundleActivator {
  /** The bundle context in which we started */
  public static BundleContext bundleContext;

  /** A reference to the configuration service. */
  private static ConfigurationService configService;

  /** Logger for this class. */
  private static final Logger logger = Logger.getLogger(SwingNotificationActivator.class);

  /** A reference to the resource management service. */
  private static ResourceManagementService resourcesService;

  /**
   * Start the swing notification service
   *
   * @param bc
   * @throws java.lang.Exception
   */
  public void start(BundleContext bc) throws Exception {
    if (logger.isInfoEnabled()) logger.info("Swing Notification ...[  STARTING ]");

    bundleContext = bc;

    PopupMessageHandler handler = null;
    handler = new PopupMessageHandlerSwingImpl();

    getConfigurationService();

    bc.registerService(PopupMessageHandler.class.getName(), handler, null);

    if (logger.isInfoEnabled()) logger.info("Swing Notification ...[REGISTERED]");
  }

  public void stop(BundleContext arg0) throws Exception {}

  /**
   * Returns the <tt>ConfigurationService</tt> obtained from the bundle context.
   *
   * @return the <tt>ConfigurationService</tt> obtained from the bundle context
   */
  public static ConfigurationService getConfigurationService() {
    if (configService == null) {
      ServiceReference configReference =
          bundleContext.getServiceReference(ConfigurationService.class.getName());

      configService = (ConfigurationService) bundleContext.getService(configReference);
    }

    return configService;
  }

  /**
   * Returns the <tt>ResourceManagementService</tt> obtained from the bundle context.
   *
   * @return the <tt>ResourceManagementService</tt> obtained from the bundle context
   */
  public static ResourceManagementService getResources() {
    if (resourcesService == null)
      resourcesService = ResourceManagementServiceUtils.getService(bundleContext);
    return resourcesService;
  }
}
/**
 * The Dialog Class
 *
 * @author Frank Kunz The dialog class draws the basic dialog with a grid layout. The dialog
 *     consists of three main parts. A settings panel, a table panel and three buttons.
 */
public final class UARTProtocolAnalysisDialog extends BaseToolDialog<UARTDataSet>
    implements ExportAware<UARTDataSet> {
  // INNER TYPES

  /** Provides a combobox renderer for {@link UARTParity} values. */
  static final class UARTParityItemRenderer extends EnumItemRenderer<Parity> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final Parity aValue) {
      String text = super.getDisplayValue(aValue);
      if (Parity.EVEN.equals(aValue)) {
        text = "Even parity";
      } else if (Parity.NONE.equals(aValue)) {
        text = "No parity";
      } else if (Parity.ODD.equals(aValue)) {
        text = "Odd parity";
      }
      return text;
    }
  }

  /** Provides a combobox renderer for {@link UARTStopBits} values. */
  static final class UARTStopBitsItemRenderer extends EnumItemRenderer<StopBits> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final StopBits aValue) {
      String text = super.getDisplayValue(aValue);
      if (StopBits.ONE.equals(aValue)) {
        text = "1";
      } else if (StopBits.ONE_HALF.equals(aValue)) {
        text = "1.5";
      } else if (StopBits.TWO.equals(aValue)) {
        text = "2";
      }
      return text;
    }
  }

  /** Provides a combobox renderer for {@link BitOrder} values. */
  static final class UARTBitOrderItemRenderer extends EnumItemRenderer<BitOrder> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final BitOrder aValue) {
      String text = super.getDisplayValue(aValue);
      if (BitOrder.LSB_FIRST.equals(aValue)) {
        text = "LSB first";
      } else if (BitOrder.MSB_FIRST.equals(aValue)) {
        text = "MSB first";
      }
      return text;
    }
  }

  /** Provides a combobox renderer for {@link BitEncoding} values. */
  static final class UARTBitEncodingItemRenderer extends EnumItemRenderer<BitEncoding> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final BitEncoding aValue) {
      String text = super.getDisplayValue(aValue);
      if (BitEncoding.HIGH_IS_MARK.equals(aValue)) {
        text = "High is mark (1)";
      } else if (BitEncoding.HIGH_IS_SPACE.equals(aValue)) {
        text = "High is space (0)";
      }
      return text;
    }
  }

  /** Provides a combobox renderer for {@link BitLevel} values. */
  static final class UARTIdleLevelItemRenderer extends EnumItemRenderer<BitLevel> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final BitLevel aValue) {
      String text = super.getDisplayValue(aValue);
      if (BitLevel.HIGH.equals(aValue)) {
        text = "High (start = L, stop = H)";
      } else if (BitLevel.LOW.equals(aValue)) {
        text = "Low (start = H, stop = L)";
      }
      return text;
    }
  }

  // CONSTANTS

  private static final long serialVersionUID = 1L;

  private static final Logger LOG = Logger.getLogger(UARTProtocolAnalysisDialog.class.getName());

  // VARIABLES

  private JComboBox rxd;
  private JComboBox txd;
  private JComboBox cts;
  private JComboBox rts;
  private JComboBox dtr;
  private JComboBox dsr;
  private JComboBox dcd;
  private JComboBox ri;
  private JComboBox parity;
  private JComboBox bits;
  private JComboBox stop;
  private JComboBox bitEncoding;
  private JComboBox bitOrder;
  private JComboBox idleLevel;
  private JCheckBox autoDetectBaudRate;
  private JComboBox baudrate;
  private JEditorPane outText;

  private RestorableAction runAnalysisAction;
  private Action closeAction;
  private Action exportAction;

  // CONSTRUCTORS

  /**
   * Creates a new UARTProtocolAnalysisDialog instance.
   *
   * @param aOwner the owner of this dialog;
   * @param aToolContext the tool context;
   * @param aContext the OSGi bundle context to use;
   * @param aTool the {@link UARTAnalyser} tool.
   */
  public UARTProtocolAnalysisDialog(
      final Window aOwner,
      final ToolContext aToolContext,
      final BundleContext aContext,
      final UARTAnalyser aTool) {
    super(aOwner, aToolContext, aContext, aTool);

    initDialog();

    setLocationRelativeTo(getOwner());
  }

  // METHODS

  /** {@inheritDoc} */
  @Override
  public void exportToFile(
      final File aOutputFile, final nl.lxtreme.ols.tool.base.ExportAware.ExportFormat aFormat)
      throws IOException {
    if (ExportFormat.HTML.equals(aFormat)) {
      storeToHtmlFile(aOutputFile, getLastResult());
    } else if (ExportFormat.CSV.equals(aFormat)) {
      storeToCsvFile(aOutputFile, getLastResult());
    }
  }

  /** {@inheritDoc} */
  @Override
  public void readPreferences(final UserSettings aSettings) {
    // Issue #114: avoid setting illegal values...
    setComboBoxIndex(this.rxd, aSettings, "rxd");
    setComboBoxIndex(this.txd, aSettings, "txd");
    setComboBoxIndex(this.cts, aSettings, "cts");
    setComboBoxIndex(this.rts, aSettings, "rts");
    setComboBoxIndex(this.dtr, aSettings, "dtr");
    setComboBoxIndex(this.dsr, aSettings, "dsr");
    setComboBoxIndex(this.dcd, aSettings, "dcd");
    setComboBoxIndex(this.ri, aSettings, "ri");

    this.parity.setSelectedIndex(aSettings.getInt("parity", this.parity.getSelectedIndex()));
    this.bits.setSelectedIndex(aSettings.getInt("bits", this.bits.getSelectedIndex()));
    this.stop.setSelectedIndex(aSettings.getInt("stop", this.stop.getSelectedIndex()));
    this.idleLevel.setSelectedIndex(
        aSettings.getInt("idle-state", this.idleLevel.getSelectedIndex()));
    this.bitEncoding.setSelectedIndex(
        aSettings.getInt("bit-encoding", this.bitEncoding.getSelectedIndex()));
    this.bitOrder.setSelectedIndex(aSettings.getInt("bit-order", this.bitOrder.getSelectedIndex()));
    this.baudrate.setSelectedItem(Integer.valueOf(aSettings.getInt("baudrate", 9600)));
    this.autoDetectBaudRate.setSelected(
        aSettings.getBoolean("auto-baudrate", this.autoDetectBaudRate.isSelected()));
  }

  /** {@inheritDoc} */
  @Override
  public void reset() {
    this.outText.setText(getEmptyHtmlPage());
    this.outText.setEditable(false);

    this.runAnalysisAction.restore();

    setControlsEnabled(true);

    this.exportAction.setEnabled(false);
  }

  /** @see nl.lxtreme.ols.api.Configurable#writePreferences(nl.lxtreme.ols.api.UserSettings) */
  @Override
  public void writePreferences(final UserSettings aSettings) {
    aSettings.putInt("rxd", this.rxd.getSelectedIndex());
    aSettings.putInt("txd", this.txd.getSelectedIndex());
    aSettings.putInt("cts", this.cts.getSelectedIndex());
    aSettings.putInt("rts", this.rts.getSelectedIndex());
    aSettings.putInt("dtr", this.dtr.getSelectedIndex());
    aSettings.putInt("dsr", this.dsr.getSelectedIndex());
    aSettings.putInt("dcd", this.dcd.getSelectedIndex());
    aSettings.putInt("ri", this.ri.getSelectedIndex());
    aSettings.putInt("parity", this.parity.getSelectedIndex());
    aSettings.putInt("bits", this.bits.getSelectedIndex());
    aSettings.putInt("stop", this.stop.getSelectedIndex());
    aSettings.putInt("idle-state", this.idleLevel.getSelectedIndex());
    aSettings.putInt("bit-encoding", this.bitEncoding.getSelectedIndex());
    aSettings.putInt("bit-order", this.bitOrder.getSelectedIndex());
    aSettings.putInt("baudrate", ((Integer) this.baudrate.getSelectedItem()).intValue());
    aSettings.putBoolean("auto-baudrate", this.autoDetectBaudRate.isSelected());
  }

  /** {@inheritDoc} */
  @Override
  protected void onToolEnded(final UARTDataSet aAnalysisResult) {
    try {
      final String htmlPage;
      if (aAnalysisResult != null) {
        htmlPage = toHtmlPage(null /* aFile */, aAnalysisResult);
      } else {
        htmlPage = getEmptyHtmlPage();
      }

      this.outText.setText(htmlPage);
      this.outText.setEditable(false);

      this.runAnalysisAction.restore();
    } catch (final IOException exception) {
      // Make sure to handle IO-interrupted exceptions properly!
      if (!HostUtils.handleInterruptedException(exception)) {
        // Should not happen in this situation!
        throw new RuntimeException(exception);
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  protected void onToolStarted() {
    // NO-op
  }

  /** {@inheritDoc} */
  @Override
  protected void prepareToolTask(final ToolTask<UARTDataSet> aToolTask) {
    final UARTAnalyserTask toolTask = (UARTAnalyserTask) aToolTask;

    // The value at index zero is "Unused", so extracting one of all items
    // causes all "unused" values to be equivalent to -1, which is interpreted
    // as not used...
    toolTask.setRxdIndex(this.rxd.getSelectedIndex() - 1);
    toolTask.setTxdIndex(this.txd.getSelectedIndex() - 1);
    toolTask.setCtsIndex(this.cts.getSelectedIndex() - 1);
    toolTask.setRtsIndex(this.rts.getSelectedIndex() - 1);
    toolTask.setDcdIndex(this.dcd.getSelectedIndex() - 1);
    toolTask.setRiIndex(this.ri.getSelectedIndex() - 1);
    toolTask.setDsrIndex(this.dsr.getSelectedIndex() - 1);
    toolTask.setDtrIndex(this.dtr.getSelectedIndex() - 1);
    // Handle the auto detect option for baudrates...
    if (this.autoDetectBaudRate.isSelected()) {
      toolTask.setBaudRate(UARTAnalyserTask.AUTO_DETECT_BAUDRATE);
    } else {
      toolTask.setBaudRate(((Integer) this.baudrate.getSelectedItem()).intValue());
    }

    // Other properties...
    toolTask.setIdleLevel((BitLevel) this.idleLevel.getSelectedItem());
    toolTask.setBitEncoding((BitEncoding) this.bitEncoding.getSelectedItem());
    toolTask.setBitOrder((BitOrder) this.bitOrder.getSelectedItem());
    toolTask.setParity((Parity) this.parity.getSelectedItem());
    toolTask.setStopBits((StopBits) this.stop.getSelectedItem());
    toolTask.setBitCount(NumberUtils.smartParseInt((String) this.bits.getSelectedItem(), 8));
  }

  /**
   * set the controls of the dialog enabled/disabled
   *
   * @param aEnable status of the controls
   */
  @Override
  protected void setControlsEnabled(final boolean aEnable) {
    this.rxd.setEnabled(aEnable);
    this.txd.setEnabled(aEnable);
    this.cts.setEnabled(aEnable);
    this.rts.setEnabled(aEnable);
    this.dtr.setEnabled(aEnable);
    this.dsr.setEnabled(aEnable);
    this.dcd.setEnabled(aEnable);
    this.ri.setEnabled(aEnable);
    this.parity.setEnabled(aEnable);
    this.bits.setEnabled(aEnable);
    this.stop.setEnabled(aEnable);
    this.idleLevel.setEnabled(aEnable);
    this.bitEncoding.setEnabled(aEnable);
    this.bitOrder.setEnabled(aEnable);

    this.closeAction.setEnabled(aEnable);
    this.exportAction.setEnabled(aEnable);
  }

  /**
   * Creates the HTML template for exports to HTML.
   *
   * @param aExporter the HTML exporter instance to use, cannot be <code>null</code>.
   * @return a HTML exporter filled with the template, never <code>null</code>.
   */
  private HtmlExporter createHtmlTemplate(final HtmlExporter aExporter) {
    aExporter.addCssStyle("body { font-family: sans-serif; } ");
    aExporter.addCssStyle(
        "table { border-width: 1px; border-spacing: 0px; border-color: gray;"
            + " border-collapse: collapse; border-style: solid; margin-bottom: 15px; } ");
    aExporter.addCssStyle(
        "table th { border-width: 1px; padding: 2px; border-style: solid; border-color: gray;"
            + " background-color: #C0C0FF; text-align: left; font-weight: bold; font-family: sans-serif; } ");
    aExporter.addCssStyle(
        "table td { border-width: 1px; padding: 2px; border-style: solid; border-color: gray;"
            + " font-family: monospace; } ");
    aExporter.addCssStyle(".error { color: red; } ");
    aExporter.addCssStyle(".warning { color: orange; } ");
    aExporter.addCssStyle(".date { text-align: right; font-size: x-small; margin-bottom: 15px; } ");
    aExporter.addCssStyle(".w100 { width: 100%; } ");
    aExporter.addCssStyle(".w35 { width: 35%; } ");
    aExporter.addCssStyle(".w30 { width: 30%; } ");
    aExporter.addCssStyle(".w15 { width: 15%; } ");
    aExporter.addCssStyle(".w10 { width: 10%; } ");
    aExporter.addCssStyle(".w8 { width: 8%; } ");
    aExporter.addCssStyle(".w7 { width: 7%; } ");

    final Element body = aExporter.getBody();
    body.addChild(H1).addContent("UART Analysis results");
    body.addChild(HR);
    body.addChild(DIV).addAttribute("class", "date").addContent("Generated: ", "{date-now}");

    Element table, tr, thead, tbody;

    table = body.addChild(TABLE).addAttribute("class", "w100");

    tbody = table.addChild(TBODY);
    tr = tbody.addChild(TR);
    tr.addChild(TH).addAttribute("colspan", "2").addContent("Statistics");
    tr = tbody.addChild(TR);
    tr.addChild(TD).addAttribute("class", "w30").addContent("Decoded bytes");
    tr.addChild(TD).addContent("{decoded-bytes}");
    tr = tbody.addChild(TR);
    tr.addChild(TD).addAttribute("class", "w30").addContent("Detected bus errors");
    tr.addChild(TD).addContent("{detected-bus-errors}");
    tr = tbody.addChild(TR);
    tr.addChild(TD).addAttribute("class", "w30").addContent("Baudrate");
    tr.addChild(TD).addContent("{baudrate}");

    table = body.addChild(TABLE).addAttribute("class", "w100");
    thead = table.addChild(THEAD);
    tr = thead.addChild(TR);
    tr.addChild(TH).addAttribute("class", "w30").addAttribute("colspan", "2");
    tr.addChild(TH).addAttribute("class", "w35").addAttribute("colspan", "4").addContent("RxD");
    tr.addChild(TH).addAttribute("class", "w35").addAttribute("colspan", "4").addContent("TxD");
    tr = thead.addChild(TR);
    tr.addChild(TH).addAttribute("class", "w15").addContent("Index");
    tr.addChild(TH).addAttribute("class", "w15").addContent("Time");
    tr.addChild(TH).addAttribute("class", "w10").addContent("Hex");
    tr.addChild(TH).addAttribute("class", "w10").addContent("Bin");
    tr.addChild(TH).addAttribute("class", "w8").addContent("Dec");
    tr.addChild(TH).addAttribute("class", "w7").addContent("ASCII");
    tr.addChild(TH).addAttribute("class", "w10").addContent("Hex");
    tr.addChild(TH).addAttribute("class", "w10").addContent("Bin");
    tr.addChild(TH).addAttribute("class", "w8").addContent("Dec");
    tr.addChild(TH).addAttribute("class", "w7").addContent("ASCII");
    tbody = table.addChild(TBODY);
    tbody.addContent("{decoded-data}");

    return aExporter;
  }

  /** @return */
  private JPanel createPreviewPane() {
    final JPanel panTable = new JPanel(new GridLayout(1, 1, 0, 0));

    this.outText = new JEditorPane("text/html", getEmptyHtmlPage());
    this.outText.setEditable(false);

    panTable.add(new JScrollPane(this.outText));

    return panTable;
  }

  /** @return */
  private JPanel createSettingsPane() {
    final int channelCount = getData().getChannels();

    final Integer[] baudrates = new Integer[AsyncSerialDataDecoder.COMMON_BAUDRATES.length];
    for (int i = 0; i < baudrates.length; i++) {
      baudrates[i] = Integer.valueOf(AsyncSerialDataDecoder.COMMON_BAUDRATES[i]);
    }
    final String[] bitarray = new String[10];
    // allow symbol lengths between 5 and 14 bits...
    for (int i = 0; i < bitarray.length; i++) {
      bitarray[i] = String.format("%d", Integer.valueOf(i + 5));
    }

    final JPanel settings = new JPanel(new SpringLayout());

    SpringLayoutUtils.addSeparator(settings, "Settings");

    settings.add(createRightAlignedLabel("RxD"));
    this.rxd = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.rxd);

    settings.add(createRightAlignedLabel("TxD"));
    this.txd = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.txd);

    settings.add(createRightAlignedLabel("CTS"));
    this.cts = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.cts);

    settings.add(createRightAlignedLabel("RTS"));
    this.rts = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.rts);

    settings.add(createRightAlignedLabel("DTR"));
    this.dtr = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.dtr);

    settings.add(createRightAlignedLabel("DSR"));
    this.dsr = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.dsr);

    settings.add(createRightAlignedLabel("DCD"));
    this.dcd = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.dcd);

    settings.add(createRightAlignedLabel("RI"));
    this.ri = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.ri);

    settings.add(createRightAlignedLabel("Baudrate"));
    this.autoDetectBaudRate = new JCheckBox("Auto detect");
    settings.add(this.autoDetectBaudRate);

    settings.add(new JLabel(""));
    this.baudrate = new JComboBox(baudrates);
    // Issue #90: allow custom baudrates to be specified...
    this.baudrate.setEditable(true);
    this.baudrate.setSelectedIndex(0);
    settings.add(this.baudrate);

    this.autoDetectBaudRate.addItemListener(
        new ItemListener() {
          @Override
          public void itemStateChanged(final ItemEvent aEvent) {
            final JCheckBox cb = (JCheckBox) aEvent.getSource();
            UARTProtocolAnalysisDialog.this.baudrate.setEnabled(!cb.isSelected());
          }
        });

    settings.add(createRightAlignedLabel("Parity"));
    this.parity = new JComboBox(Parity.values());
    this.parity.setSelectedIndex(0);
    this.parity.setRenderer(new UARTParityItemRenderer());
    settings.add(this.parity);

    settings.add(createRightAlignedLabel("Bits"));
    this.bits = new JComboBox(bitarray);
    this.bits.setSelectedIndex(3);
    settings.add(this.bits);

    settings.add(createRightAlignedLabel("Stopbits"));
    this.stop = new JComboBox(StopBits.values());
    this.stop.setSelectedIndex(0);
    this.stop.setRenderer(new UARTStopBitsItemRenderer());
    settings.add(this.stop);

    settings.add(createRightAlignedLabel("Idle level"));
    this.idleLevel = new JComboBox(BitLevel.values());
    this.idleLevel.setSelectedIndex(0);
    this.idleLevel.setRenderer(new UARTIdleLevelItemRenderer());
    settings.add(this.idleLevel);

    settings.add(createRightAlignedLabel("Bit encoding"));
    this.bitEncoding = new JComboBox(BitEncoding.values());
    this.bitEncoding.setSelectedIndex(0);
    this.bitEncoding.setRenderer(new UARTBitEncodingItemRenderer());
    settings.add(this.bitEncoding);

    settings.add(createRightAlignedLabel("Bit order"));
    this.bitOrder = new JComboBox(BitOrder.values());
    this.bitOrder.setSelectedIndex(0);
    this.bitOrder.setRenderer(new UARTBitOrderItemRenderer());
    settings.add(this.bitOrder);

    SpringLayoutUtils.makeEditorGrid(settings, 10, 4);

    return settings;
  }

  /**
   * generate a HTML page
   *
   * @param empty if this is true an empty output is generated
   * @return String with HTML data
   */
  private String getEmptyHtmlPage() {
    final HtmlExporter exporter = createHtmlTemplate(ExportUtils.createHtmlExporter());
    return exporter.toString(
        new MacroResolver() {
          @Override
          public Object resolve(final String aMacro, final Element aParent) {
            if ("date-now".equals(aMacro)) {
              final DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
              return df.format(new Date());
            } else if ("decoded-bytes".equals(aMacro)
                || "detected-bus-errors".equals(aMacro)
                || "baudrate".equals(aMacro)) {
              return "-";
            } else if ("decoded-data".equals(aMacro)) {
              return null;
            }
            return null;
          }
        });
  }

  /** Initializes this dialog. */
  private void initDialog() {
    setMinimumSize(new Dimension(640, 480));

    final JComponent settingsPane = createSettingsPane();
    final JComponent previewPane = createPreviewPane();

    final JPanel contentPane = new JPanel(new GridBagLayout());
    contentPane.add(
        settingsPane,
        new GridBagConstraints(
            0,
            0,
            1,
            1,
            0.0,
            0.0,
            GridBagConstraints.NORTH,
            GridBagConstraints.NONE,
            new Insets(2, 0, 2, 0),
            0,
            0));
    contentPane.add(
        previewPane,
        new GridBagConstraints(
            1,
            0,
            1,
            1,
            1.0,
            1.0,
            GridBagConstraints.NORTH,
            GridBagConstraints.BOTH,
            new Insets(2, 0, 2, 0),
            0,
            0));

    final JButton runAnalysisButton = ToolUtils.createRunAnalysisButton(this);
    this.runAnalysisAction = (RestorableAction) runAnalysisButton.getAction();

    final JButton exportButton = ToolUtils.createExportButton(this);
    this.exportAction = exportButton.getAction();
    this.exportAction.setEnabled(false);

    final JButton closeButton = ToolUtils.createCloseButton();
    this.closeAction = closeButton.getAction();

    final JComponent buttons =
        SwingComponentUtils.createButtonPane(runAnalysisButton, exportButton, closeButton);

    SwingComponentUtils.setupWindowContentPane(this, contentPane, buttons, runAnalysisButton);
  }

  /**
   * exports the data to a CSV file
   *
   * @param aFile File object
   */
  private void storeToCsvFile(final File aFile, final UARTDataSet aDataSet) {
    try {
      final CsvExporter exporter = ExportUtils.createCsvExporter(aFile);

      exporter.setHeaders(
          "index",
          "start-time",
          "end-time",
          "event?",
          "event-type",
          "RxD event",
          "TxD event",
          "RxD data",
          "TxD data");

      final List<UARTData> decodedData = aDataSet.getData();
      for (int i = 0; i < decodedData.size(); i++) {
        final UARTData ds = decodedData.get(i);

        final String startTime = Unit.Time.format(aDataSet.getTime(ds.getStartSampleIndex()));
        final String endTime = Unit.Time.format(aDataSet.getTime(ds.getEndSampleIndex()));

        String eventType = null;
        String rxdEvent = null;
        String txdEvent = null;
        String rxdData = null;
        String txdData = null;

        switch (ds.getType()) {
          case UARTData.UART_TYPE_EVENT:
            eventType = ds.getEventName();
            break;

          case UARTData.UART_TYPE_RXEVENT:
            rxdEvent = ds.getEventName();
            break;

          case UARTData.UART_TYPE_TXEVENT:
            txdEvent = ds.getEventName();
            break;

          case UARTData.UART_TYPE_RXDATA:
            rxdData = Integer.toString(ds.getData());
            break;

          case UARTData.UART_TYPE_TXDATA:
            txdData = Integer.toString(ds.getData());
            break;

          default:
            break;
        }

        exporter.addRow(
            Integer.valueOf(i),
            startTime,
            endTime,
            Boolean.valueOf(ds.isEvent()),
            eventType,
            rxdEvent,
            txdEvent,
            rxdData,
            txdData);
      }

      exporter.close();
    } catch (final IOException exception) {
      // Make sure to handle IO-interrupted exceptions properly!
      if (!HostUtils.handleInterruptedException(exception)) {
        LOG.log(Level.WARNING, "CSV export failed!", exception);
      }
    }
  }

  /**
   * stores the data to a HTML file
   *
   * @param aFile file object
   */
  private void storeToHtmlFile(final File aFile, final UARTDataSet aDataSet) {
    try {
      toHtmlPage(aFile, aDataSet);
    } catch (final IOException exception) {
      // Make sure to handle IO-interrupted exceptions properly!
      if (!HostUtils.handleInterruptedException(exception)) {
        LOG.log(Level.WARNING, "HTML export failed!", exception);
      }
    }
  }

  /**
   * generate a HTML page
   *
   * @param empty if this is true an empty output is generated
   * @return String with HTML data
   */
  private String toHtmlPage(final File aFile, final UARTDataSet aDataSet) throws IOException {
    final int bitCount = Integer.parseInt((String) this.bits.getSelectedItem());
    final int bitAdder = ((bitCount % 4) != 0) ? 1 : 0;

    final MacroResolver macroResolver =
        new MacroResolver() {
          @Override
          public Object resolve(final String aMacro, final Element aParent) {
            if ("date-now".equals(aMacro)) {
              final DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
              return df.format(new Date());
            } else if ("decoded-bytes".equals(aMacro)) {
              return Integer.valueOf(aDataSet.getDecodedSymbols());
            } else if ("detected-bus-errors".equals(aMacro)) {
              return Integer.valueOf(aDataSet.getDetectedErrors());
            } else if ("baudrate".equals(aMacro)) {
              final String baudrate;
              if (aDataSet.getBaudRate() <= 0) {
                baudrate = "<span class='error'>Baudrate calculation failed!</span>";
              } else {
                baudrate =
                    String.format(
                        "%d (exact: %d)",
                        Integer.valueOf(aDataSet.getBaudRate()),
                        Integer.valueOf(aDataSet.getBaudRateExact()));
                if (!aDataSet.isBitLengthUsable()) {
                  return baudrate.concat(
                      " <span class='warning'>The baudrate may be wrong, use a higher samplerate to avoid this!</span>");
                }

                return baudrate;
              }
            } else if ("decoded-data".equals(aMacro)) {
              final List<UARTData> decodedData = aDataSet.getData();
              Element tr;

              for (int i = 0; i < decodedData.size(); i++) {
                final UARTData ds = decodedData.get(i);

                if (ds.isEvent()) {
                  String rxEventData = "";
                  String txEventData = "";

                  String bgColor;
                  if (UARTData.UART_TYPE_EVENT == ds.getType()) {
                    rxEventData = txEventData = ds.getEventName();
                    bgColor = "#e0e0e0";
                  } else if (UARTData.UART_TYPE_RXEVENT == ds.getType()) {
                    rxEventData = ds.getEventName();
                    bgColor = "#c0ffc0";
                  } else if (UARTData.UART_TYPE_TXEVENT == ds.getType()) {
                    txEventData = ds.getEventName();
                    bgColor = "#c0ffc0";
                  } else {
                    // unknown event
                    bgColor = "#ff8000";
                  }

                  if (txEventData.endsWith("_ERR") || rxEventData.endsWith("_ERR")) {
                    bgColor = "#ff8000";
                  }

                  tr =
                      aParent
                          .addChild(TR)
                          .addAttribute("style", "background-color: " + bgColor + ";");
                  tr.addChild(TD).addContent(String.valueOf(i));
                  tr.addChild(TD)
                      .addContent(Unit.Time.format(aDataSet.getTime(ds.getStartSampleIndex())));
                  tr.addChild(TD).addContent(rxEventData);
                  tr.addChild(TD);
                  tr.addChild(TD);
                  tr.addChild(TD);
                  tr.addChild(TD).addContent(txEventData);
                  tr.addChild(TD);
                  tr.addChild(TD);
                  tr.addChild(TD);
                } else {
                  String rxDataHex = "", rxDataBin = "", rxDataDec = "", rxDataASCII = "";
                  String txDataHex = "", txDataBin = "", txDataDec = "", txDataASCII = "";

                  // Normal data...
                  if (UARTData.UART_TYPE_RXDATA == ds.getType()) {
                    final int rxData = ds.getData();

                    rxDataHex = integerToHexString(rxData, (bitCount / 4) + bitAdder);
                    rxDataBin = integerToBinString(rxData, bitCount);
                    rxDataDec = String.valueOf(rxData);
                    rxDataASCII = toASCII((char) rxData);
                  } else
                  /* if ( UARTData.UART_TYPE_TXDATA == ds.getType() ) */
                  {
                    final int txData = ds.getData();

                    txDataHex = integerToHexString(txData, (bitCount / 4) + bitAdder);
                    txDataBin = integerToBinString(txData, bitCount);
                    txDataDec = String.valueOf(txData);
                    txDataASCII = toASCII(txData);
                  }

                  tr = aParent.addChild(TR);
                  tr.addChild(TD).addContent(String.valueOf(i));
                  tr.addChild(TD)
                      .addContent(Unit.Time.format(aDataSet.getTime(ds.getStartSampleIndex())));
                  tr.addChild(TD).addContent("0x", rxDataHex);
                  tr.addChild(TD).addContent("0b", rxDataBin);
                  tr.addChild(TD).addContent(rxDataDec);
                  tr.addChild(TD).addContent(rxDataASCII);
                  tr.addChild(TD).addContent("0x", txDataHex);
                  tr.addChild(TD).addContent("0b", txDataBin);
                  tr.addChild(TD).addContent(txDataDec);
                  tr.addChild(TD).addContent(txDataASCII);
                }
              }
            }
            return null;
          }
        };

    if (aFile == null) {
      final HtmlExporter exporter = createHtmlTemplate(ExportUtils.createHtmlExporter());
      return exporter.toString(macroResolver);
    } else {
      final HtmlFileExporter exporter =
          (HtmlFileExporter) createHtmlTemplate(ExportUtils.createHtmlExporter(aFile));
      exporter.write(macroResolver);
      exporter.close();
    }

    return null;
  }
}
/**
 * Tests whether accaounts are uninstalled properly. It is important that tests from this class be
 * called last since they will install the accounts that have been used to test the implementations.
 * Apart from uninstallation tests the class also contains tests that remove and reinstall the
 * protocol provider bundle in order to verify that accounts are persistent.
 *
 * @author Emil Ivov
 */
public class TestAccountUninstallation extends TestCase {
  private static final Logger logger = Logger.getLogger(TestAccountUninstallation.class);

  private SipSlickFixture fixture = new SipSlickFixture();

  /**
   * Constructs a test instance
   *
   * @param name The name of the test.
   */
  public TestAccountUninstallation(String name) {
    super(name);
  }

  /**
   * JUnit setup method.
   *
   * @throws Exception in case anything goes wrong.
   */
  protected void setUp() throws Exception {
    super.setUp();
    fixture.setUp();
  }

  /**
   * JUnit teardown method.
   *
   * @throws Exception in case anything goes wrong.
   */
  protected void tearDown() throws Exception {
    fixture.tearDown();
    super.tearDown();
  }

  /**
   * Returns a suite containing tests in this class in the order that we'd like them executed.
   *
   * @return a Test suite containing tests in this class in the order that we'd like them executed.
   */
  public static Test suite() {
    TestSuite suite = new TestSuite();

    suite.addTest(new TestAccountUninstallation("testProviderUnregister"));
    suite.addTest(new TestAccountUninstallation("testInstallationPersistency"));
    suite.addTest(new TestAccountUninstallation("testUninstallAccount"));

    return suite;
  }

  /**
   * Unregisters both providers and verifies whether they have changed state accordingly.
   *
   * @throws OperationFailedException if unregister fails with an error.
   */
  public void testProviderUnregister() throws OperationFailedException {
    // make sure providers are still registered
    assertEquals(fixture.provider1.getRegistrationState(), RegistrationState.REGISTERED);
    assertEquals(fixture.provider2.getRegistrationState(), RegistrationState.REGISTERED);

    UnregistrationEventCollector collector1 = new UnregistrationEventCollector();
    UnregistrationEventCollector collector2 = new UnregistrationEventCollector();

    fixture.provider1.addRegistrationStateChangeListener(collector1);
    fixture.provider2.addRegistrationStateChangeListener(collector2);

    // unregister both providers
    fixture.provider1.unregister();
    fixture.provider2.unregister();

    collector1.waitForEvent(10000);
    collector2.waitForEvent(10000);

    assertTrue(
        "Provider did not distribute unregister events", 2 <= collector1.collectedNewStates.size());
    assertTrue(
        "Provider did not distribute unregister events", 2 <= collector2.collectedNewStates.size());

    // make sure both providers are now unregistered.
    assertEquals(
        "Provider state after calling unregister().",
        RegistrationState.UNREGISTERED,
        fixture.provider1.getRegistrationState());
    assertEquals(
        "Provider state after calling unregister().",
        RegistrationState.UNREGISTERED,
        fixture.provider2.getRegistrationState());
  }

  /**
   * Stops and removes the tested bundle, verifies that it has unregistered its provider, then
   * reloads and restarts the bundle and verifies that the protocol provider is reRegistered in the
   * bundle context.
   *
   * @throws java.lang.Exception if an exception occurs during testing.
   */
  public void testInstallationPersistency() throws Exception {
    Bundle providerBundle = fixture.findProtocolProviderBundle(fixture.provider1);

    // set the global providerBundle reference that we will be using
    // in the last series of tests (Account uninstallation persistency)
    SipSlickFixture.providerBundle = providerBundle;

    assertNotNull("Couldn't find a bundle for the tested provider", providerBundle);

    providerBundle.stop();

    assertTrue(
        "Couldn't stop the protocol provider bundle. State was " + providerBundle.getState(),
        Bundle.ACTIVE != providerBundle.getState() && Bundle.STOPPING != providerBundle.getState());

    providerBundle.uninstall();

    assertEquals(
        "Couldn't stop the protocol provider bundle.",
        Bundle.UNINSTALLED,
        providerBundle.getState());

    // verify that the provider is no longer available
    ServiceReference[] sipProviderRefs = null;
    try {
      sipProviderRefs =
          fixture.bc.getServiceReferences(
              ProtocolProviderService.class.getName(),
              "(&"
                  + "("
                  + ProtocolProviderFactory.PROTOCOL
                  + "="
                  + ProtocolNames.SIP
                  + ")"
                  + "("
                  + ProtocolProviderFactory.USER_ID
                  + "="
                  + fixture.userID1
                  + ")"
                  + ")");
    } catch (InvalidSyntaxException ex) {
      fail("We apparently got our filter wrong: " + ex.getMessage());
    }

    // make sure we didn't see a service
    assertTrue(
        "A Protocol Provider Service was still regged as an osgi service "
            + "for SIP URI:"
            + fixture.userID1
            + "After it was explicitly uninstalled",
        sipProviderRefs == null || sipProviderRefs.length == 0);

    // verify that the provider factory knows that we have uninstalled the
    // provider.
    assertTrue(
        "The SIP provider factory kept a reference to the provider we just "
            + "uninstalled (uri="
            + fixture.userID1
            + ")",
        fixture.providerFactory.getRegisteredAccounts().isEmpty()
            && fixture.providerFactory.getProviderForAccount(fixture.provider1.getAccountID())
                == null);

    // Now reinstall the bundle
    providerBundle = fixture.bc.installBundle(providerBundle.getLocation());

    // set the global providerBundle reference that we will be using
    // in the last series of tests (Account uninstallation persistency)
    SipSlickFixture.providerBundle = providerBundle;

    assertEquals(
        "Couldn't re-install protocol provider bundle.",
        Bundle.INSTALLED,
        providerBundle.getState());

    AccountManagerUtils.startBundleAndWaitStoredAccountsLoaded(
        fixture.bc, providerBundle, ProtocolNames.SIP);
    assertEquals(
        "Couldn't re-start protocol provider bundle.", Bundle.ACTIVE, providerBundle.getState());

    // Make sure that the provider is there again.
    // verify that the provider is no longer available
    try {
      sipProviderRefs =
          fixture.bc.getServiceReferences(
              ProtocolProviderService.class.getName(),
              "(&"
                  + "("
                  + ProtocolProviderFactory.PROTOCOL
                  + "="
                  + ProtocolNames.SIP
                  + ")"
                  + "("
                  + ProtocolProviderFactory.USER_ID
                  + "="
                  + fixture.userID1
                  + ")"
                  + ")");
    } catch (InvalidSyntaxException ex) {
      fail("We apparently got our filter wrong " + ex.getMessage());
    }

    // make sure we didn't see a service
    assertTrue(
        "A Protocol Provider Service was not restored after being"
            + "reinstalled. SIP URI:"
            + fixture.userID1,
        sipProviderRefs != null && sipProviderRefs.length > 0);

    ServiceReference[] sipFactoryRefs = null;
    try {
      sipFactoryRefs =
          fixture.bc.getServiceReferences(
              ProtocolProviderFactory.class.getName(),
              "(" + ProtocolProviderFactory.PROTOCOL + "=" + ProtocolNames.SIP + ")");
    } catch (InvalidSyntaxException ex) {
      fail("We apparently got our filter wrong " + ex.getMessage());
    }

    // we're the ones who've reinstalled the factory so it's our
    // responsibility to update the fixture.
    fixture.providerFactory = (ProtocolProviderFactory) fixture.bc.getService(sipFactoryRefs[0]);
    fixture.provider1 = (ProtocolProviderService) fixture.bc.getService(sipProviderRefs[0]);

    // verify that the provider is also restored in the provider factory
    // itself
    assertTrue(
        "The SIP provider did not restore its own reference to the provider "
            + "that we just reinstalled (URI="
            + fixture.userID1
            + ")",
        !fixture.providerFactory.getRegisteredAccounts().isEmpty()
            && fixture.providerFactory.getProviderForAccount(fixture.provider1.getAccountID())
                != null);
  }

  /** Uinstalls our test account and makes sure it really has been removed. */
  public void testUninstallAccount() {
    assertFalse(
        "No installed accounts found", fixture.providerFactory.getRegisteredAccounts().isEmpty());

    assertNotNull(
        "Found no provider corresponding to URI " + fixture.userID1,
        fixture.providerFactory.getProviderForAccount(fixture.provider1.getAccountID()));

    assertTrue(
        "Failed to remove a provider corresponding to URI " + fixture.userID1,
        fixture.providerFactory.uninstallAccount(fixture.provider1.getAccountID()));
    assertTrue(
        "Failed to remove a provider corresponding to URI " + fixture.userID1,
        fixture.providerFactory.uninstallAccount(fixture.provider2.getAccountID()));

    // make sure no providers have remained installed.
    ServiceReference[] sipProviderRefs = null;
    try {
      sipProviderRefs =
          fixture.bc.getServiceReferences(
              ProtocolProviderService.class.getName(),
              "(" + ProtocolProviderFactory.PROTOCOL + "=" + ProtocolNames.SIP + ")");
    } catch (InvalidSyntaxException ex) {
      fail("We apparently got our filter wrong " + ex.getMessage());
    }

    // make sure we didn't see a service
    assertTrue(
        "A Protocol Provider Service was still regged as an osgi "
            + "service for SIP URI:"
            + fixture.userID1
            + "After it was explicitly uninstalled",
        sipProviderRefs == null || sipProviderRefs.length == 0);

    // verify that the provider factory knows that we have uninstalled the
    // provider.
    assertTrue(
        "The SIP provider factory kept a reference to the provider we just "
            + "uninstalled (uri="
            + fixture.userID1
            + ")",
        fixture.providerFactory.getRegisteredAccounts().isEmpty()
            && fixture.providerFactory.getProviderForAccount(fixture.provider1.getAccountID())
                == null);
  }

  /**
   * A class that would plugin as a registration listener to a protocol provider and simply record
   * all events that it sees and notifyAll() if it sees an event that notifies us of a completed
   * registration.
   */
  public class UnregistrationEventCollector implements RegistrationStateChangeListener {
    public List<RegistrationState> collectedNewStates = new LinkedList<RegistrationState>();

    /**
     * The method would simply register all received events so that they could be available for
     * later inspection by the unit tests. In the case where a registraiton event notifying us of a
     * completed registration is seen, the method would call notifyAll().
     *
     * @param evt ProviderStatusChangeEvent the event describing the status change.
     */
    public void registrationStateChanged(RegistrationStateChangeEvent evt) {
      logger.debug("Received a RegistrationStateChangeEvent: " + evt);

      collectedNewStates.add(evt.getNewState());

      if (evt.getNewState().equals(RegistrationState.UNREGISTERED)) {
        logger.debug("We're registered and will notify those who wait");
        synchronized (this) {
          notifyAll();
        }
      }
    }

    /**
     * Blocks until an event notifying us of the awaited state change is received or until waitFor
     * miliseconds pass (whichever happens first).
     *
     * @param waitFor the number of miliseconds that we should be waiting for an event before simply
     *     bailing out.
     */
    public void waitForEvent(long waitFor) {
      logger.trace("Waiting for a RegistrationStateChangeEvent");

      synchronized (this) {
        if (collectedNewStates.contains(RegistrationState.UNREGISTERED)) {
          logger.trace("Event already received. " + collectedNewStates);
          return;
        }

        try {
          wait(waitFor);

          if (collectedNewStates.size() > 0)
            logger.trace("Received a RegistrationStateChangeEvent.");
          else logger.trace("No RegistrationStateChangeEvent received for " + waitFor + "ms.");

        } catch (InterruptedException ex) {
          logger.debug("Interrupted while waiting for a " + "RegistrationStateChangeEvent", ex);
        }
      }
    }
  }
}
/**
 * The advanced configuration panel.
 *
 * @author Yana Stamcheva
 */
public class AdvancedConfigurationPanel extends TransparentPanel
    implements ConfigurationForm, ConfigurationContainer, ServiceListener, ListSelectionListener {
  /** Serial version UID. */
  private static final long serialVersionUID = 0L;

  /** The <tt>Logger</tt> used by this <tt>AdvancedConfigurationPanel</tt> for logging output. */
  private final Logger logger = Logger.getLogger(AdvancedConfigurationPanel.class);

  /** The configuration list. */
  private final JList configList = new JList();

  /** The center panel. */
  private final JPanel centerPanel = new TransparentPanel(new BorderLayout());

  /** Creates an instance of the <tt>AdvancedConfigurationPanel</tt>. */
  public AdvancedConfigurationPanel() {
    super(new BorderLayout(10, 0));

    initList();

    centerPanel.setPreferredSize(new Dimension(500, 500));

    add(centerPanel, BorderLayout.CENTER);
  }

  /** Initializes the config list. */
  private void initList() {
    configList.setModel(new DefaultListModel());
    configList.setCellRenderer(new ConfigListCellRenderer());
    configList.addListSelectionListener(this);
    configList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

    JScrollPane configScrollList = new JScrollPane();

    configScrollList.getVerticalScrollBar().setUnitIncrement(30);

    configScrollList.getViewport().add(configList);

    add(configScrollList, BorderLayout.WEST);

    String osgiFilter =
        "(" + ConfigurationForm.FORM_TYPE + "=" + ConfigurationForm.ADVANCED_TYPE + ")";
    ServiceReference[] confFormsRefs = null;
    try {
      confFormsRefs =
          AdvancedConfigActivator.bundleContext.getServiceReferences(
              ConfigurationForm.class.getName(), osgiFilter);
    } catch (InvalidSyntaxException ex) {
    }

    if (confFormsRefs != null) {
      for (int i = 0; i < confFormsRefs.length; i++) {
        ConfigurationForm form =
            (ConfigurationForm) AdvancedConfigActivator.bundleContext.getService(confFormsRefs[i]);

        if (form.isAdvanced()) this.addConfigForm(form);
      }
    }
  }

  /**
   * Shows on the right the configuration form given by the given <tt>ConfigFormDescriptor</tt>.
   *
   * @param configForm the configuration form to show
   */
  private void showFormContent(ConfigurationForm configForm) {
    this.centerPanel.removeAll();

    JComponent configFormPanel = (JComponent) configForm.getForm();

    configFormPanel.setOpaque(false);

    this.centerPanel.add(configFormPanel, BorderLayout.CENTER);

    this.centerPanel.revalidate();
    this.centerPanel.repaint();
  }

  /**
   * Handles registration of a new configuration form.
   *
   * @param event the <tt>ServiceEvent</tt> that notified us
   */
  public void serviceChanged(ServiceEvent event) {
    Object sService = AdvancedConfigActivator.bundleContext.getService(event.getServiceReference());

    // we don't care if the source service is not a configuration form
    if (!(sService instanceof ConfigurationForm)) return;

    ConfigurationForm configForm = (ConfigurationForm) sService;

    /*
     * This AdvancedConfigurationPanel is an advanced ConfigurationForm so
     * don't try to add it to itself.
     */
    if ((configForm == this) || !configForm.isAdvanced()) return;

    switch (event.getType()) {
      case ServiceEvent.REGISTERED:
        if (logger.isInfoEnabled())
          logger.info("Handling registration of a new Configuration Form.");

        this.addConfigForm(configForm);
        break;

      case ServiceEvent.UNREGISTERING:
        this.removeConfigForm(configForm);
        break;
    }
  }

  /**
   * Adds a new <tt>ConfigurationForm</tt> to this list.
   *
   * @param configForm The <tt>ConfigurationForm</tt> to add.
   */
  public void addConfigForm(ConfigurationForm configForm) {
    if (configForm == null) throw new IllegalArgumentException("configForm");

    DefaultListModel listModel = (DefaultListModel) configList.getModel();

    int i = 0;
    int count = listModel.getSize();
    int configFormIndex = configForm.getIndex();
    for (; i < count; i++) {
      ConfigurationForm form = (ConfigurationForm) listModel.get(i);

      if (configFormIndex < form.getIndex()) break;
    }
    listModel.add(i, configForm);
  }

  /**
   * Implements <code>ApplicationWindow.show</code> method.
   *
   * @param isVisible specifies whether the frame is to be visible or not.
   */
  @Override
  public void setVisible(boolean isVisible) {
    if (isVisible && configList.getSelectedIndex() < 0) {
      this.configList.setSelectedIndex(0);
    }
    super.setVisible(isVisible);
  }

  /**
   * Removes a <tt>ConfigurationForm</tt> from this list.
   *
   * @param configForm The <tt>ConfigurationForm</tt> to remove.
   */
  public void removeConfigForm(ConfigurationForm configForm) {
    DefaultListModel listModel = (DefaultListModel) configList.getModel();

    for (int count = listModel.getSize(), i = count - 1; i >= 0; i--) {
      ConfigurationForm form = (ConfigurationForm) listModel.get(i);

      if (form.equals(configForm)) {
        listModel.remove(i);
        /*
         * TODO We may just consider not allowing duplicates on addition
         * and then break here.
         */
      }
    }
  }

  /** A custom cell renderer that represents a <tt>ConfigurationForm</tt>. */
  private class ConfigListCellRenderer extends DefaultListCellRenderer {
    /** Serial version UID. */
    private static final long serialVersionUID = 0L;

    private boolean isSelected = false;

    private final Color selectedColor =
        new Color(
            AdvancedConfigActivator.getResources().getColor("service.gui.LIST_SELECTION_COLOR"));

    /**
     * Creates an instance of <tt>ConfigListCellRenderer</tt> and specifies that this renderer is
     * transparent.
     */
    public ConfigListCellRenderer() {
      this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
      this.setOpaque(false);
    }

    /**
     * Returns the component representing the cell given by parameters.
     *
     * @param list the parent list
     * @param value the value of the cell
     * @param index the index of the cell
     * @param isSelected indicates if the cell is selected
     * @param cellHasFocus indicates if the cell has the focus
     * @return the component representing the cell
     */
    public Component getListCellRendererComponent(
        JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      ConfigurationForm configForm = (ConfigurationForm) value;

      this.isSelected = isSelected;
      this.setText(configForm.getTitle());

      return this;
    }

    /**
     * Paint a background for all groups and a round blue border and background when a cell is
     * selected.
     *
     * @param g the <tt>Graphics</tt> object
     */
    public void paintComponent(Graphics g) {
      Graphics g2 = g.create();
      try {
        internalPaintComponent(g2);
      } finally {
        g2.dispose();
      }
      super.paintComponent(g);
    }

    /**
     * Paint a background for all groups and a round blue border and background when a cell is
     * selected.
     *
     * @param g the <tt>Graphics</tt> object
     */
    private void internalPaintComponent(Graphics g) {
      AntialiasingManager.activateAntialiasing(g);

      Graphics2D g2 = (Graphics2D) g;

      if (isSelected) {
        g2.setColor(selectedColor);
        g2.fillRect(0, 0, this.getWidth(), this.getHeight());
      }
    }
  }

  /**
   * Called when user selects a component in the list of configuration forms.
   *
   * @param e the <tt>ListSelectionEvent</tt>
   */
  public void valueChanged(ListSelectionEvent e) {
    if (!e.getValueIsAdjusting()) {
      ConfigurationForm configForm = (ConfigurationForm) configList.getSelectedValue();

      if (configForm != null) showFormContent(configForm);
    }
  }

  /**
   * Selects the given <tt>ConfigurationForm</tt>.
   *
   * @param configForm the <tt>ConfigurationForm</tt> to select
   */
  public void setSelected(ConfigurationForm configForm) {
    configList.setSelectedValue(configForm, true);
  }

  /**
   * Returns the title of the form.
   *
   * @return the title of the form
   */
  public String getTitle() {
    return AdvancedConfigActivator.getResources().getI18NString("service.gui.ADVANCED");
  }

  /**
   * Returns the icon of the form.
   *
   * @return a byte array containing the icon of the form
   */
  public byte[] getIcon() {
    return AdvancedConfigActivator.getResources()
        .getImageInBytes("plugin.advancedconfig.PLUGIN_ICON");
  }

  /**
   * Returns the form component.
   *
   * @return the form component
   */
  public Object getForm() {
    return this;
  }

  /**
   * Returns the index of the form in its parent container.
   *
   * @return the index of the form in its parent container
   */
  public int getIndex() {
    return 300;
  }

  /**
   * Indicates if the form is an advanced form.
   *
   * @return <tt>true</tt> to indicate that this is an advanced form, otherwise returns
   *     <tt>false</tt>
   */
  public boolean isAdvanced() {
    return false;
  }

  /**
   * Validates the currently selected configuration form. This method is meant to be used by
   * configuration forms the re-validate when a new component has been added or size has changed.
   */
  public void validateCurrentForm() {}
}
Exemple #19
0
/**
 * The <tt>StatusSubMenu</tt> provides a menu which allow to select the status for each of the
 * protocol providers registered when the menu appears
 *
 * @author Nicolas Chamouard
 */
public class StatusSubMenu extends JMenu {
  /** A reference of <tt>Systray</tt> */
  private SystrayServiceJdicImpl parentSystray;

  /** Contains all accounts and corresponding menus. */
  private Hashtable accountSelectors = new Hashtable();

  private Logger logger = Logger.getLogger(StatusSubMenu.class);

  /**
   * Creates an instance of <tt>StatusSubMenu</tt>.
   *
   * @param tray a reference of the parent <tt>Systray</tt>
   */
  public StatusSubMenu(SystrayServiceJdicImpl tray) {

    parentSystray = tray;

    this.setText(Resources.getString("setStatus"));
    this.setIcon(Resources.getImage("statusMenuIcon"));

    /* makes the menu look better */
    this.setPreferredSize(new java.awt.Dimension(28, 24));

    this.init();
  }

  /**
   * Adds the account corresponding to the given protocol provider to this menu.
   *
   * @param protocolProvider the protocol provider corresponding to the account to add
   */
  private void addAccount(ProtocolProviderService protocolProvider) {
    OperationSetPresence presence =
        (OperationSetPresence) protocolProvider.getOperationSet(OperationSetPresence.class);

    if (presence == null) {
      StatusSimpleSelector simpleSelector =
          new StatusSimpleSelector(parentSystray, protocolProvider);

      this.accountSelectors.put(protocolProvider.getAccountID(), simpleSelector);
      this.add(simpleSelector);
    } else {
      StatusSelector statusSelector = new StatusSelector(parentSystray, protocolProvider, presence);

      this.accountSelectors.put(protocolProvider.getAccountID(), statusSelector);
      this.add(statusSelector);

      presence.addProviderPresenceStatusListener(new SystrayProviderPresenceStatusListener());
    }
  }

  /**
   * Removes the account corresponding to the given protocol provider from this menu.
   *
   * @param protocolProvider the protocol provider corresponding to the account to remove.
   */
  private void removeAccount(ProtocolProviderService protocolProvider) {
    Component c = (Component) this.accountSelectors.get(protocolProvider.getAccountID());

    this.remove(c);
  }

  /**
   * We fill the protocolProviderTable with all running protocol providers at the start of the
   * bundle.
   */
  private void init() {
    SystrayActivator.bundleContext.addServiceListener(new ProtocolProviderServiceListener());

    ServiceReference[] protocolProviderRefs = null;
    try {
      protocolProviderRefs =
          SystrayActivator.bundleContext.getServiceReferences(
              ProtocolProviderService.class.getName(), null);
    } catch (InvalidSyntaxException ex) {
      // this shouldn't happen since we're providing no parameter string
      // but let's log just in case.
      logger.error("Error while retrieving service refs", ex);
      return;
    }

    // in case we found any
    if (protocolProviderRefs != null) {

      for (int i = 0; i < protocolProviderRefs.length; i++) {
        ProtocolProviderService provider =
            (ProtocolProviderService)
                SystrayActivator.bundleContext.getService(protocolProviderRefs[i]);

        boolean isHidden =
            provider.getAccountID().getAccountProperties().get("HIDDEN_PROTOCOL") != null;

        if (!isHidden) this.addAccount(provider);
      }
    }
  }

  /**
   * Listens for <tt>ServiceEvent</tt>s indicating that a <tt>ProtocolProviderService</tt> has been
   * registered and completes the account status menu.
   */
  private class ProtocolProviderServiceListener implements ServiceListener {
    /**
     * When a service is registered or unregistered, we update the provider tables and add/remove
     * listeners (if it supports BasicInstantMessenging implementation)
     *
     * @param event ServiceEvent
     */
    public void serviceChanged(ServiceEvent event) {
      // if the event is caused by a bundle being stopped, we don't want to
      // know
      if (event.getServiceReference().getBundle().getState() == Bundle.STOPPING) {
        return;
      }

      Object service = SystrayActivator.bundleContext.getService(event.getServiceReference());

      if (!(service instanceof ProtocolProviderService)) return;

      ProtocolProviderService provider = (ProtocolProviderService) service;

      if (event.getType() == ServiceEvent.REGISTERED) addAccount(provider);

      if (event.getType() == ServiceEvent.UNREGISTERING) removeAccount(provider);
    }
  }

  /**
   * Listens for all providerStatusChanged and providerStatusMessageChanged events in order to
   * refresh the account status panel, when a status is changed.
   */
  private class SystrayProviderPresenceStatusListener implements ProviderPresenceStatusListener {
    /** Fired when an account has changed its status. We update the icon in the menu. */
    public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) {
      ProtocolProviderService pps = evt.getProvider();

      StatusSelector selectorBox = (StatusSelector) accountSelectors.get(pps.getAccountID());

      if (selectorBox == null) return;

      selectorBox.updateStatus(evt.getNewStatus());
    }

    public void providerStatusMessageChanged(PropertyChangeEvent evt) {}
  }
}
Exemple #20
0
/** Denotes a front-end controller for the client. */
public final class ClientController implements ActionProvider, CaptureCallback, AnalysisCallback {
  // INNER TYPES

  /** Provides a default tool context implementation. */
  static final class DefaultToolContext implements ToolContext {
    // VARIABLES

    private final int startSampleIdx;
    private final int endSampleIdx;

    // CONSTRUCTORS

    /**
     * Creates a new DefaultToolContext instance.
     *
     * @param aStartSampleIdx the starting sample index;
     * @param aEndSampleIdx the ending sample index.
     */
    public DefaultToolContext(final int aStartSampleIdx, final int aEndSampleIdx) {
      this.startSampleIdx = aStartSampleIdx;
      this.endSampleIdx = aEndSampleIdx;
    }

    /** @see nl.lxtreme.ols.api.tools.ToolContext#getEndSampleIndex() */
    @Override
    public int getEndSampleIndex() {
      return this.endSampleIdx;
    }

    /** @see nl.lxtreme.ols.api.tools.ToolContext#getLength() */
    @Override
    public int getLength() {
      return Math.max(0, this.endSampleIdx - this.startSampleIdx);
    }

    /** @see nl.lxtreme.ols.api.tools.ToolContext#getStartSampleIndex() */
    @Override
    public int getStartSampleIndex() {
      return this.startSampleIdx;
    }
  }

  // CONSTANTS

  private static final Logger LOG = Logger.getLogger(ClientController.class.getName());

  // VARIABLES

  private final ActionManager actionManager;
  private final BundleContext bundleContext;
  private final DataContainer dataContainer;
  private final EventListenerList evenListeners;
  private final ProjectManager projectManager;
  private final Host host;

  private MainFrame mainFrame;

  private volatile DeviceController currentDevCtrl;

  // CONSTRUCTORS

  /** Creates a new ClientController instance. */
  public ClientController(
      final BundleContext aBundleContext, final Host aHost, final ProjectManager aProjectManager) {
    this.bundleContext = aBundleContext;
    this.host = aHost;
    this.projectManager = aProjectManager;

    this.dataContainer = new DataContainer(this.projectManager);
    this.actionManager = new ActionManager();
    this.evenListeners = new EventListenerList();

    fillActionManager(this.actionManager);
  }

  // METHODS

  /**
   * Adds a cursor change listener.
   *
   * @param aListener the listener to add, cannot be <code>null</code>.
   */
  public void addCursorChangeListener(final DiagramCursorChangeListener aListener) {
    this.evenListeners.add(DiagramCursorChangeListener.class, aListener);
  }

  /**
   * Adds the given device controller to this controller.
   *
   * @param aDeviceController the device controller to add, cannot be <code>null</code>.
   */
  public void addDevice(final DeviceController aDeviceController) {
    if (this.mainFrame != null) {
      if (this.mainFrame.addDeviceMenuItem(aDeviceController)) {
        this.currentDevCtrl = aDeviceController;
      }
    }

    updateActions();
  }

  /**
   * Adds the given exporter to this controller.
   *
   * @param aExporter the exporter to add, cannot be <code>null</code>.
   */
  public void addExporter(final Exporter aExporter) {
    if (this.mainFrame != null) {
      this.mainFrame.addExportMenuItem(aExporter.getName());
    }

    updateActions();
  }

  /**
   * Adds the given tool to this controller.
   *
   * @param aTool the tool to add, cannot be <code>null</code>.
   */
  public void addTool(final Tool aTool) {
    if (this.mainFrame != null) {
      this.mainFrame.addToolMenuItem(aTool.getName());
    }

    updateActions();
  }

  /** @see nl.lxtreme.ols.api.tools.AnalysisCallback#analysisAborted(java.lang.String) */
  @Override
  public void analysisAborted(final String aReason) {
    setStatus("Analysis aborted! " + aReason);

    updateActions();
  }

  /**
   * @see
   *     nl.lxtreme.ols.api.tools.AnalysisCallback#analysisComplete(nl.lxtreme.ols.api.data.CapturedData)
   */
  @Override
  public void analysisComplete(final CapturedData aNewCapturedData) {
    if (aNewCapturedData != null) {
      this.dataContainer.setCapturedData(aNewCapturedData);
    }
    if (this.mainFrame != null) {
      repaintMainFrame();
    }

    setStatus("");
    updateActions();
  }

  /** Cancels the current capturing (if in progress). */
  public void cancelCapture() {
    final DeviceController deviceController = getDeviceController();
    if (deviceController == null) {
      return;
    }

    deviceController.cancel();
  }

  /** @see nl.lxtreme.ols.api.devices.CaptureCallback#captureAborted(java.lang.String) */
  @Override
  public void captureAborted(final String aReason) {
    setStatus("Capture aborted! " + aReason);
    updateActions();
  }

  /**
   * @see
   *     nl.lxtreme.ols.api.devices.CaptureCallback#captureComplete(nl.lxtreme.ols.api.data.CapturedData)
   */
  @Override
  public void captureComplete(final CapturedData aCapturedData) {
    setCapturedData(aCapturedData);

    setStatus("Capture finished at {0,date,medium} {0,time,medium}.", new Date());

    updateActions();
  }

  /**
   * Captures the data of the current device controller.
   *
   * @param aParent the parent window to use, can be <code>null</code>.
   * @return <code>true</code> if the capture succeeded, <code>false</code> otherwise.
   * @throws IOException in case of I/O problems.
   */
  public boolean captureData(final Window aParent) {
    final DeviceController devCtrl = getDeviceController();
    if (devCtrl == null) {
      return false;
    }

    try {
      if (devCtrl.setupCapture(aParent)) {
        setStatus(
            "Capture from {0} started at {1,date,medium} {1,time,medium} ...",
            devCtrl.getName(), new Date());

        devCtrl.captureData(this);
        return true;
      }

      return false;
    } catch (IOException exception) {
      captureAborted("I/O problem: " + exception.getMessage());

      // Make sure to handle IO-interrupted exceptions properly!
      if (!HostUtils.handleInterruptedException(exception)) {
        exception.printStackTrace();
      }

      return false;
    } finally {
      updateActions();
    }
  }

  /** @see nl.lxtreme.ols.api.devices.CaptureCallback#captureStarted(int, int, int) */
  @Override
  public synchronized void captureStarted(
      final int aSampleRate, final int aChannelCount, final int aChannelMask) {
    final Runnable runner =
        new Runnable() {
          @Override
          public void run() {
            updateActions();
          }
        };

    if (SwingUtilities.isEventDispatchThread()) {
      runner.run();
    } else {
      SwingUtilities.invokeLater(runner);
    }
  }

  /** Clears all current cursors. */
  public void clearAllCursors() {
    for (int i = 0; i < CapturedData.MAX_CURSORS; i++) {
      this.dataContainer.setCursorPosition(i, null);
    }
    fireCursorChangedEvent(0, -1); // removed...

    updateActions();
  }

  /** Clears the current device controller. */
  public void clearDeviceController() {
    this.currentDevCtrl = null;
  }

  /**
   * Clears the current project, and start over as it were a new project, in which no captured data
   * is shown.
   */
  public void createNewProject() {
    this.projectManager.createNewProject();

    if (this.mainFrame != null) {
      this.mainFrame.repaint();
    }

    updateActions();
  }

  /** Exits the client application. */
  public void exit() {
    if (this.host != null) {
      this.host.exit();
    }
  }

  /**
   * Exports the current diagram to the given exporter.
   *
   * @param aExporter the exporter to export to, cannot be <code>null</code>.
   * @param aOutputStream the output stream to write the export to, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void exportTo(final Exporter aExporter, final OutputStream aOutputStream)
      throws IOException {
    if (this.mainFrame != null) {
      aExporter.export(this.dataContainer, this.mainFrame.getDiagramScrollPane(), aOutputStream);
    }
  }

  /** @see nl.lxtreme.ols.client.ActionProvider#getAction(java.lang.String) */
  public Action getAction(final String aID) {
    return this.actionManager.getAction(aID);
  }

  /** @return the dataContainer */
  public DataContainer getDataContainer() {
    return this.dataContainer;
  }

  /**
   * Returns the current device controller.
   *
   * @return the current device controller, can be <code>null</code>.
   */
  public DeviceController getDeviceController() {
    return this.currentDevCtrl;
  }

  /**
   * Returns all current tools known to the OSGi framework.
   *
   * @return a collection of tools, never <code>null</code>.
   */
  public final Collection<DeviceController> getDevices() {
    final List<DeviceController> tools = new ArrayList<DeviceController>();
    synchronized (this.bundleContext) {
      try {
        final ServiceReference[] serviceRefs =
            this.bundleContext.getAllServiceReferences(DeviceController.class.getName(), null);
        for (ServiceReference serviceRef : serviceRefs) {
          tools.add((DeviceController) this.bundleContext.getService(serviceRef));
        }
      } catch (InvalidSyntaxException exception) {
        throw new RuntimeException(exception);
      }
    }
    return tools;
  }

  /**
   * Returns the exporter with the given name.
   *
   * @param aName the name of the exporter to return, cannot be <code>null</code>.
   * @return the exporter with the given name, can be <code>null</code> if no such exporter is
   *     found.
   * @throws IllegalArgumentException in case the given name was <code>null</code> or empty.
   */
  public Exporter getExporter(final String aName) throws IllegalArgumentException {
    if ((aName == null) || aName.trim().isEmpty()) {
      throw new IllegalArgumentException("Name cannot be null or empty!");
    }

    try {
      final ServiceReference[] serviceRefs =
          this.bundleContext.getAllServiceReferences(Exporter.class.getName(), null);
      final int count = (serviceRefs == null) ? 0 : serviceRefs.length;

      for (int i = 0; i < count; i++) {
        final Exporter exporter = (Exporter) this.bundleContext.getService(serviceRefs[i]);

        if (aName.equals(exporter.getName())) {
          return exporter;
        }
      }

      return null;
    } catch (InvalidSyntaxException exception) {
      throw new RuntimeException("getExporter failed!", exception);
    }
  }

  /**
   * Returns the names of all current available exporters.
   *
   * @return an array of exporter names, never <code>null</code>, but can be empty.
   */
  public String[] getExporterNames() {
    try {
      final ServiceReference[] serviceRefs =
          this.bundleContext.getAllServiceReferences(Exporter.class.getName(), null);
      final int count = serviceRefs == null ? 0 : serviceRefs.length;

      final String[] result = new String[count];

      for (int i = 0; i < count; i++) {
        final Exporter exporter = (Exporter) this.bundleContext.getService(serviceRefs[i]);

        result[i] = exporter.getName();
        this.bundleContext.ungetService(serviceRefs[i]);
      }

      return result;
    } catch (InvalidSyntaxException exception) {
      throw new RuntimeException("getAllExporterNames failed!", exception);
    }
  }

  /**
   * Returns the current project's filename.
   *
   * @return a project filename, as file object, can be <code>null</code>.
   */
  public File getProjectFilename() {
    return this.projectManager.getCurrentProject().getFilename();
  }

  /**
   * Returns all current tools known to the OSGi framework.
   *
   * @return a collection of tools, never <code>null</code>.
   */
  public final Collection<Tool> getTools() {
    final List<Tool> tools = new ArrayList<Tool>();
    synchronized (this.bundleContext) {
      try {
        final ServiceReference[] serviceRefs =
            this.bundleContext.getAllServiceReferences(Tool.class.getName(), null);
        for (ServiceReference serviceRef : serviceRefs) {
          tools.add((Tool) this.bundleContext.getService(serviceRef));
        }
      } catch (InvalidSyntaxException exception) {
        throw new RuntimeException(exception);
      }
    }
    return tools;
  }

  /**
   * Goes to the current cursor position of the cursor with the given index.
   *
   * @param aCursorIdx the index of the cursor to go to, >= 0 && < 10.
   */
  public void gotoCursorPosition(final int aCursorIdx) {
    if ((this.mainFrame != null) && this.dataContainer.isCursorsEnabled()) {
      final Long cursorPosition = this.dataContainer.getCursorPosition(aCursorIdx);
      if (cursorPosition != null) {
        this.mainFrame.gotoPosition(cursorPosition.longValue());
      }
    }
  }

  /** Goes to the current cursor position of the first available cursor. */
  public void gotoFirstAvailableCursor() {
    if ((this.mainFrame != null) && this.dataContainer.isCursorsEnabled()) {
      for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
        if (this.dataContainer.isCursorPositionSet(c)) {
          final Long cursorPosition = this.dataContainer.getCursorPosition(c);
          if (cursorPosition != null) {
            this.mainFrame.gotoPosition(cursorPosition.longValue());
          }
          break;
        }
      }
    }
  }

  /** Goes to the current cursor position of the last available cursor. */
  public void gotoLastAvailableCursor() {
    if ((this.mainFrame != null) && this.dataContainer.isCursorsEnabled()) {
      for (int c = CapturedData.MAX_CURSORS - 1; c >= 0; c--) {
        if (this.dataContainer.isCursorPositionSet(c)) {
          final Long cursorPosition = this.dataContainer.getCursorPosition(c);
          if (cursorPosition != null) {
            this.mainFrame.gotoPosition(cursorPosition.longValue());
          }
          break;
        }
      }
    }
  }

  /** Goes to the position of the trigger. */
  public void gotoTriggerPosition() {
    if ((this.mainFrame != null) && this.dataContainer.hasTriggerData()) {
      final long position = this.dataContainer.getTriggerPosition();
      this.mainFrame.gotoPosition(position);
    }
  }

  /**
   * Returns whether there is a device selected or not.
   *
   * @return <code>true</code> if there is a device selected, <code>false</code> if no device is
   *     selected.
   */
  public synchronized boolean isDeviceSelected() {
    return this.currentDevCtrl != null;
  }

  /**
   * Returns whether the current device is setup at least once.
   *
   * @return <code>true</code> if the current device is setup, <code>false</code> otherwise.
   * @see #isDeviceSelected()
   */
  public synchronized boolean isDeviceSetup() {
    return (this.currentDevCtrl != null) && this.currentDevCtrl.isSetup();
  }

  /**
   * Returns whether or not the current project is changed.
   *
   * @return <code>true</code> if the current project is changed, <code>false</code> if the current
   *     project is not changed.
   */
  public boolean isProjectChanged() {
    return this.projectManager.getCurrentProject().isChanged();
  }

  /**
   * Loads an OLS data file from the given file.
   *
   * @param aFile the file to load as OLS data, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void openDataFile(final File aFile) throws IOException {
    final FileReader reader = new FileReader(aFile);

    try {
      final Project tempProject = this.projectManager.createTemporaryProject();
      OlsDataHelper.read(tempProject, reader);

      setChannelLabels(tempProject.getChannelLabels());
      setCapturedData(tempProject.getCapturedData());
      setCursorData(tempProject.getCursorPositions(), tempProject.isCursorsEnabled());
    } finally {
      reader.close();

      zoomToFit();

      updateActions();
    }
  }

  /**
   * Opens the project denoted by the given file.
   *
   * @param aFile the project file to open, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void openProjectFile(final File aFile) throws IOException {
    FileInputStream fis = new FileInputStream(aFile);

    this.projectManager.loadProject(fis);

    final Project project = this.projectManager.getCurrentProject();
    project.setFilename(aFile);

    zoomToFit();
  }

  /**
   * Removes the cursor denoted by the given cursor index.
   *
   * @param aCursorIdx the index of the cursor to remove, >= 0 && < 10.
   */
  public void removeCursor(final int aCursorIdx) {
    if (this.mainFrame != null) {
      this.dataContainer.setCursorPosition(aCursorIdx, null);
      fireCursorChangedEvent(aCursorIdx, -1); // removed...
    }

    updateActions();
  }

  /**
   * Removes a cursor change listener.
   *
   * @param aListener the listener to remove, cannot be <code>null</code>.
   */
  public void removeCursorChangeListener(final DiagramCursorChangeListener aListener) {
    this.evenListeners.remove(DiagramCursorChangeListener.class, aListener);
  }

  /**
   * Removes the given device from the list of devices.
   *
   * @param aDeviceController the device to remove, cannot be <code>null</code>.
   */
  public void removeDevice(final DeviceController aDeviceController) {
    if (this.currentDevCtrl == aDeviceController) {
      this.currentDevCtrl = null;
    }

    if (this.mainFrame != null) {
      this.mainFrame.removeDeviceMenuItem(aDeviceController.getName());
    }

    updateActions();
  }

  /**
   * Removes the given exporter from the list of exporters.
   *
   * @param aExporter the exporter to remove, cannot be <code>null</code>.
   */
  public void removeExporter(final Exporter aExporter) {
    if (this.mainFrame != null) {
      this.mainFrame.removeExportMenuItem(aExporter.getName());
    }

    updateActions();
  }

  /**
   * Removes the given tool from the list of tools.
   *
   * @param aTool the tool to remove, cannot be <code>null</code>.
   */
  public void removeTool(final Tool aTool) {
    if (this.mainFrame != null) {
      this.mainFrame.removeToolMenuItem(aTool.getName());
    }

    updateActions();
  }

  /**
   * Repeats the capture with the current settings.
   *
   * @param aParent the parent window to use, can be <code>null</code>.
   */
  public boolean repeatCaptureData(final Window aParent) {
    final DeviceController devCtrl = getDeviceController();
    if (devCtrl == null) {
      return false;
    }

    try {
      setStatus(
          "Capture from {0} started at {1,date,medium} {1,time,medium} ...",
          devCtrl.getName(), new Date());

      devCtrl.captureData(this);

      return true;
    } catch (IOException exception) {
      captureAborted("I/O problem: " + exception.getMessage());

      exception.printStackTrace();

      // Make sure to handle IO-interrupted exceptions properly!
      HostUtils.handleInterruptedException(exception);

      return false;
    } finally {
      updateActions();
    }
  }

  /**
   * Runs the tool denoted by the given name.
   *
   * @param aToolName the name of the tool to run, cannot be <code>null</code>;
   * @param aParent the parent window to use, can be <code>null</code>.
   */
  public void runTool(final String aToolName, final Window aParent) {
    if (LOG.isLoggable(Level.INFO)) {
      LOG.log(Level.INFO, "Running tool: \"{0}\" ...", aToolName);
    }

    final Tool tool = findToolByName(aToolName);
    if (tool == null) {
      JOptionPane.showMessageDialog(
          aParent, "No such tool found: " + aToolName, "Error ...", JOptionPane.ERROR_MESSAGE);
    } else {
      final ToolContext context = createToolContext();
      tool.process(aParent, this.dataContainer, context, this);
    }

    updateActions();
  }

  /** @see nl.lxtreme.ols.api.devices.CaptureCallback#samplesCaptured(java.util.List) */
  @Override
  public void samplesCaptured(final List<Sample> aSamples) {
    if (this.mainFrame != null) {
      this.mainFrame.sampleCaptured(aSamples);
    }
    updateActions();
  }

  /**
   * Saves an OLS data file to the given file.
   *
   * @param aFile the file to save the OLS data to, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void saveDataFile(final File aFile) throws IOException {
    final FileWriter writer = new FileWriter(aFile);
    try {
      final Project tempProject = this.projectManager.createTemporaryProject();
      tempProject.setCapturedData(this.dataContainer);

      OlsDataHelper.write(tempProject, writer);
    } finally {
      writer.flush();
      writer.close();
    }
  }

  /**
   * Saves the current project to the given file.
   *
   * @param aFile the file to save the project information to, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void saveProjectFile(final String aName, final File aFile) throws IOException {
    FileOutputStream out = null;
    try {
      final Project project = this.projectManager.getCurrentProject();
      project.setFilename(aFile);
      project.setName(aName);

      out = new FileOutputStream(aFile);
      this.projectManager.saveProject(out);
    } finally {
      HostUtils.closeResource(out);
    }
  }

  /**
   * Sets whether or not cursors are enabled.
   *
   * @param aState <code>true</code> if the cursors should be enabled, <code>false</code> otherwise.
   */
  public void setCursorMode(final boolean aState) {
    this.dataContainer.setCursorEnabled(aState);
    // Reflect the change directly on the diagram...
    repaintMainFrame();

    updateActions();
  }

  /**
   * Sets the cursor position of the cursor with the given index.
   *
   * @param aCursorIdx the index of the cursor to set, >= 0 && < 10;
   * @param aLocation the mouse location on screen where the cursor should become, cannot be <code>
   *     null</code>.
   */
  public void setCursorPosition(final int aCursorIdx, final Point aLocation) {
    // Implicitly enable cursor mode, the user already had made its
    // intensions clear that he want to have this by opening up the
    // context menu anyway...
    setCursorMode(true);

    if (this.mainFrame != null) {
      // Convert the mouse-position to a sample index...
      final long sampleIdx = this.mainFrame.convertMousePositionToSampleIndex(aLocation);

      this.dataContainer.setCursorPosition(aCursorIdx, Long.valueOf(sampleIdx));

      fireCursorChangedEvent(aCursorIdx, aLocation.x);
    }

    updateActions();
  }

  /**
   * Sets the current device controller to the given value.
   *
   * @param aDeviceName the name of the device controller to set, cannot be <code>null</code>.
   */
  public synchronized void setDeviceController(final String aDeviceName) {
    if (LOG.isLoggable(Level.INFO)) {
      final String name = (aDeviceName == null) ? "no device" : aDeviceName;
      LOG.log(Level.INFO, "Setting current device controller to: \"{0}\" ...", name);
    }

    final Collection<DeviceController> devices = getDevices();
    for (DeviceController device : devices) {
      if (aDeviceName.equals(device.getName())) {
        this.currentDevCtrl = device;
      }
    }

    updateActions();
  }

  /** @param aMainFrame the mainFrame to set */
  public void setMainFrame(final MainFrame aMainFrame) {
    if (this.mainFrame != null) {
      this.projectManager.removePropertyChangeListener(this.mainFrame);
    }
    if (aMainFrame != null) {
      this.projectManager.addPropertyChangeListener(aMainFrame);
    }

    this.mainFrame = aMainFrame;
  }

  /**
   * Sets a status message.
   *
   * @param aMessage the message to set;
   * @param aMessageArgs the (optional) message arguments.
   */
  public final void setStatus(final String aMessage, final Object... aMessageArgs) {
    if (this.mainFrame != null) {
      this.mainFrame.setStatus(aMessage, aMessageArgs);
    }
  }

  /** Shows the "about OLS" dialog on screen. the parent window to use, can be <code>null</code>. */
  public void showAboutBox() {
    MainFrame.showAboutBox(this.host.getVersion());
  }

  /**
   * Shows a dialog with all running OSGi bundles.
   *
   * @param aOwner the owning window to use, can be <code>null</code>.
   */
  public void showBundlesDialog(final Window aOwner) {
    BundlesDialog dialog = new BundlesDialog(aOwner, this.bundleContext);
    if (dialog.showDialog()) {
      dialog.dispose();
      dialog = null;
    }
  }

  /**
   * Shows the label-editor dialog on screen.
   *
   * <p>Display the diagram labels dialog. Will block until the dialog is closed again.
   *
   * @param aParent the parent window to use, can be <code>null</code>.
   */
  public void showLabelsDialog(final Window aParent) {
    if (this.mainFrame != null) {
      DiagramLabelsDialog dialog =
          new DiagramLabelsDialog(aParent, this.dataContainer.getChannelLabels());
      if (dialog.showDialog()) {
        final String[] channelLabels = dialog.getChannelLabels();
        setChannelLabels(channelLabels);
      }

      dialog.dispose();
      dialog = null;
    }
  }

  /**
   * Shows the settings-editor dialog on screen.
   *
   * <p>Display the diagram settings dialog. Will block until the dialog is closed again.
   *
   * @param aParent the parent window to use, can be <code>null</code>.
   */
  public void showModeSettingsDialog(final Window aParent) {
    if (this.mainFrame != null) {
      ModeSettingsDialog dialog = new ModeSettingsDialog(aParent, getDiagramSettings());
      if (dialog.showDialog()) {
        updateDiagramSettings(dialog.getDiagramSettings());
      }

      dialog.dispose();
      dialog = null;
    }
  }

  /** @param aOwner */
  public void showPreferencesDialog(final Window aParent) {
    GeneralSettingsDialog dialog = new GeneralSettingsDialog(aParent, getDiagramSettings());
    if (dialog.showDialog()) {
      updateDiagramSettings(dialog.getDiagramSettings());
    }

    dialog.dispose();
    dialog = null;
  }

  /** @see nl.lxtreme.ols.api.ProgressCallback#updateProgress(int) */
  @Override
  public void updateProgress(final int aPercentage) {
    if (this.mainFrame != null) {
      this.mainFrame.setProgress(aPercentage);
    }
  }

  /** Zooms in to the maximum zoom level. */
  public void zoomDefault() {
    if (this.mainFrame != null) {
      this.mainFrame.zoomDefault();
    }

    updateActions();
  }

  /** Zooms in with a factor of 2.0. */
  public void zoomIn() {
    if (this.mainFrame != null) {
      this.mainFrame.zoomIn();
    }

    updateActions();
  }

  /** Zooms out with a factor of 2.0. */
  public void zoomOut() {
    if (this.mainFrame != null) {
      this.mainFrame.zoomOut();
    }

    updateActions();
  }

  /** Zooms to fit the diagram to the current window dimensions. */
  public void zoomToFit() {
    if (this.mainFrame != null) {
      this.mainFrame.zoomToFit();
    }

    updateActions();
  }

  /**
   * Returns the current main frame.
   *
   * @return the main frame, can be <code>null</code>.
   */
  final MainFrame getMainFrame() {
    return this.mainFrame;
  }

  /**
   * Creates the tool context denoting the range of samples that should be analysed by a tool.
   *
   * @return a tool context, never <code>null</code>.
   */
  private ToolContext createToolContext() {
    int startOfDecode = -1;
    int endOfDecode = -1;

    final int dataLength = this.dataContainer.getValues().length;
    if (this.dataContainer.isCursorsEnabled()) {
      if (this.dataContainer.isCursorPositionSet(0)) {
        final Long cursor1 = this.dataContainer.getCursorPosition(0);
        startOfDecode = this.dataContainer.getSampleIndex(cursor1.longValue()) - 1;
      }
      if (this.dataContainer.isCursorPositionSet(1)) {
        final Long cursor2 = this.dataContainer.getCursorPosition(1);
        endOfDecode = this.dataContainer.getSampleIndex(cursor2.longValue()) + 1;
      }
    } else {
      startOfDecode = 0;
      endOfDecode = dataLength;
    }

    startOfDecode = Math.max(0, startOfDecode);
    if ((endOfDecode < 0) || (endOfDecode >= dataLength)) {
      endOfDecode = dataLength - 1;
    }

    return new DefaultToolContext(startOfDecode, endOfDecode);
  }

  /** @param aActionManager */
  private void fillActionManager(final ActionManager aActionManager) {
    aActionManager.add(new NewProjectAction(this));
    aActionManager.add(new OpenProjectAction(this));
    aActionManager.add(new SaveProjectAction(this)).setEnabled(false);
    aActionManager.add(new SaveProjectAsAction(this)).setEnabled(false);
    aActionManager.add(new OpenDataFileAction(this));
    aActionManager.add(new SaveDataFileAction(this)).setEnabled(false);
    aActionManager.add(new ExitAction(this));

    aActionManager.add(new CaptureAction(this));
    aActionManager.add(new CancelCaptureAction(this)).setEnabled(false);
    aActionManager.add(new RepeatCaptureAction(this)).setEnabled(false);

    aActionManager.add(new ZoomInAction(this)).setEnabled(false);
    aActionManager.add(new ZoomOutAction(this)).setEnabled(false);
    aActionManager.add(new ZoomDefaultAction(this)).setEnabled(false);
    aActionManager.add(new ZoomFitAction(this)).setEnabled(false);

    aActionManager.add(new GotoTriggerAction(this)).setEnabled(false);
    for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
      aActionManager.add(new GotoNthCursorAction(this, c)).setEnabled(false);
    }
    aActionManager.add(new GotoFirstCursorAction(this)).setEnabled(false);
    aActionManager.add(new GotoLastCursorAction(this)).setEnabled(false);
    aActionManager.add(new ClearCursors(this)).setEnabled(false);
    aActionManager.add(new SetCursorModeAction(this));
    for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
      aActionManager.add(new SetCursorAction(this, c));
    }

    aActionManager.add(new ShowGeneralSettingsAction(this));
    aActionManager.add(new ShowModeSettingsAction(this));
    aActionManager.add(new ShowDiagramLabelsAction(this));

    aActionManager.add(new HelpAboutAction(this));
    aActionManager.add(new ShowBundlesAction(this));
  }

  /**
   * Searches for the tool with the given name.
   *
   * @param aToolName the name of the tool to search for, cannot be <code>null</code>.
   * @return the tool with the given name, can be <code>null</code> if no such tool can be found.
   */
  private Tool findToolByName(final String aToolName) {
    Tool tool = null;

    final Collection<Tool> tools = getTools();
    for (Tool _tool : tools) {
      if (aToolName.equals(_tool.getName())) {
        tool = _tool;
        break;
      }
    }
    return tool;
  }

  /**
   * @param aCursorIdx
   * @param aMouseXpos
   */
  private void fireCursorChangedEvent(final int aCursorIdx, final int aMouseXpos) {
    final DiagramCursorChangeListener[] listeners =
        this.evenListeners.getListeners(DiagramCursorChangeListener.class);
    for (final DiagramCursorChangeListener listener : listeners) {
      if (aMouseXpos >= 0) {
        listener.cursorChanged(aCursorIdx, aMouseXpos);
      } else {
        listener.cursorRemoved(aCursorIdx);
      }
    }
  }

  /**
   * Returns the current diagram settings.
   *
   * @return the current diagram settings, can be <code>null</code> if there is no main frame to
   *     take the settings from.
   */
  private DiagramSettings getDiagramSettings() {
    return this.mainFrame != null ? this.mainFrame.getDiagramSettings() : null;
  }

  /** Dispatches a request to repaint the entire main frame. */
  private void repaintMainFrame() {
    SwingUtilities.invokeLater(
        new Runnable() {
          @Override
          public void run() {
            ClientController.this.mainFrame.repaint();
          }
        });
  }

  /**
   * Sets the captured data and zooms the view to show all the data.
   *
   * @param aCapturedData the new captured data to set, cannot be <code>null</code>.
   */
  private void setCapturedData(final CapturedData aCapturedData) {
    this.dataContainer.setCapturedData(aCapturedData);

    if (this.mainFrame != null) {
      this.mainFrame.zoomToFit();
    }
  }

  /**
   * Set the channel labels.
   *
   * @param aChannelLabels the channel labels to set, cannot be <code>null</code>.
   */
  private void setChannelLabels(final String[] aChannelLabels) {
    if (aChannelLabels != null) {
      this.dataContainer.setChannelLabels(aChannelLabels);
      this.mainFrame.setChannelLabels(aChannelLabels);
    }
  }

  /**
   * @param aCursorData the cursor positions to set, cannot be <code>null</code>;
   * @param aCursorsEnabled <code>true</code> if cursors should be enabled, <code>false</code> if
   *     they should be disabled.
   */
  private void setCursorData(final Long[] aCursorData, final boolean aCursorsEnabled) {
    this.dataContainer.setCursorEnabled(aCursorsEnabled);
    for (int i = 0; i < CapturedData.MAX_CURSORS; i++) {
      this.dataContainer.setCursorPosition(i, aCursorData[i]);
    }
  }

  /** Synchronizes the state of the actions to the current state of this host. */
  private void updateActions() {
    final DeviceController currentDeviceController = getDeviceController();

    final boolean deviceControllerSet = currentDeviceController != null;
    final boolean deviceCapturing = deviceControllerSet && currentDeviceController.isCapturing();
    final boolean deviceSetup =
        deviceControllerSet && !deviceCapturing && currentDeviceController.isSetup();

    getAction(CaptureAction.ID).setEnabled(deviceControllerSet);
    getAction(CancelCaptureAction.ID).setEnabled(deviceCapturing);
    getAction(RepeatCaptureAction.ID).setEnabled(deviceSetup);

    final boolean projectChanged = this.projectManager.getCurrentProject().isChanged();
    final boolean projectSavedBefore =
        this.projectManager.getCurrentProject().getFilename() != null;
    final boolean dataAvailable = this.dataContainer.hasCapturedData();

    getAction(SaveProjectAction.ID).setEnabled(projectChanged);
    getAction(SaveProjectAsAction.ID).setEnabled(projectSavedBefore && projectChanged);
    getAction(SaveDataFileAction.ID).setEnabled(dataAvailable);

    getAction(ZoomInAction.ID).setEnabled(dataAvailable);
    getAction(ZoomOutAction.ID).setEnabled(dataAvailable);
    getAction(ZoomDefaultAction.ID).setEnabled(dataAvailable);
    getAction(ZoomFitAction.ID).setEnabled(dataAvailable);

    final boolean triggerEnable = dataAvailable && this.dataContainer.hasTriggerData();
    getAction(GotoTriggerAction.ID).setEnabled(triggerEnable);

    // Update the cursor actions accordingly...
    final boolean enableCursors = dataAvailable && this.dataContainer.isCursorsEnabled();

    for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
      final boolean enabled = enableCursors && this.dataContainer.isCursorPositionSet(c);
      getAction(GotoNthCursorAction.getID(c)).setEnabled(enabled);
    }

    getAction(GotoFirstCursorAction.ID).setEnabled(enableCursors);
    getAction(GotoLastCursorAction.ID).setEnabled(enableCursors);

    getAction(SetCursorModeAction.ID).setEnabled(dataAvailable);
    getAction(SetCursorModeAction.ID)
        .putValue(Action.SELECTED_KEY, Boolean.valueOf(this.dataContainer.isCursorsEnabled()));

    boolean anyCursorSet = false;
    for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
      final boolean cursorPositionSet = this.dataContainer.isCursorPositionSet(c);
      anyCursorSet |= cursorPositionSet;

      final Action action = getAction(SetCursorAction.getCursorId(c));
      action.setEnabled(dataAvailable);
      action.putValue(Action.SELECTED_KEY, Boolean.valueOf(cursorPositionSet));
    }

    getAction(ClearCursors.ID).setEnabled(enableCursors && anyCursorSet);
  }

  /**
   * Should be called after the diagram settings are changed. This method will cause the settings to
   * be set on the main frame and writes them to the preference store.
   *
   * @param aSettings the (new/changed) diagram settings to set, cannot be <code>null</code>.
   */
  private void updateDiagramSettings(final DiagramSettings aSettings) {
    if (this.mainFrame != null) {
      this.mainFrame.setDiagramSettings(aSettings);
      repaintMainFrame();
    }
  }
}
/**
 * @author Emil Ivov
 * @author Lyubomir Marinov
 */
public class ConfigurationActivator implements BundleActivator {
  /** The <tt>Logger</tt> used by the <tt>ConfigurationActivator</tt> class for logging output. */
  private static final Logger logger = Logger.getLogger(ConfigurationActivator.class);

  /** The currently registered {@link ConfigurationService} instance. */
  private ConfigurationService cs;

  /**
   * Starts the configuration service
   *
   * @param bundleContext the <tt>BundleContext</tt> as provided by the OSGi framework.
   * @throws Exception if anything goes wrong
   */
  public void start(BundleContext bundleContext) throws Exception {
    FileAccessService fas = ServiceUtils.getService(bundleContext, FileAccessService.class);

    if (fas != null) {
      File usePropFileConfig;
      try {
        usePropFileConfig =
            fas.getPrivatePersistentFile(".usepropfileconfig", FileCategory.PROFILE);
      } catch (Exception ise) {
        // There is somewhat of a chicken-and-egg dependency between
        // FileConfigurationServiceImpl and ConfigurationServiceImpl:
        // FileConfigurationServiceImpl throws IllegalStateException if
        // certain System properties are not set,
        // ConfigurationServiceImpl will make sure that these properties
        // are set but it will do that later.
        // A SecurityException is thrown when the destination
        // is not writable or we do not have access to that folder
        usePropFileConfig = null;
      }

      if (usePropFileConfig != null && usePropFileConfig.exists()) {
        logger.info("Using properties file configuration store.");
        this.cs = LibJitsi.getConfigurationService();
      }
    }

    if (this.cs == null) {
      this.cs = new JdbcConfigService(fas);
    }

    bundleContext.registerService(ConfigurationService.class.getName(), this.cs, null);

    fixPermissions(this.cs);
  }

  /**
   * Causes the configuration service to store the properties object and unregisters the
   * configuration service.
   *
   * @param bundleContext <tt>BundleContext</tt>
   * @throws Exception if anything goes wrong while storing the properties managed by the
   *     <tt>ConfigurationService</tt> implementation provided by this bundle and while
   *     unregistering the service in question
   */
  public void stop(BundleContext bundleContext) throws Exception {
    this.cs.storeConfiguration();
    this.cs = null;
  }

  /**
   * Makes home folder and the configuration file readable and writable only to the owner.
   *
   * @param cs the <tt>ConfigurationService</tt> instance to check for home folder and configuration
   *     file.
   */
  private static void fixPermissions(ConfigurationService cs) {
    if (!OSUtils.IS_LINUX && !OSUtils.IS_MAC) return;

    try {
      // let's check config file and config folder
      File homeFolder = new File(cs.getScHomeDirLocation(), cs.getScHomeDirName());
      Set<PosixFilePermission> perms =
          new HashSet<PosixFilePermission>() {
            {
              add(PosixFilePermission.OWNER_READ);
              add(PosixFilePermission.OWNER_WRITE);
              add(PosixFilePermission.OWNER_EXECUTE);
            }
          };
      Files.setPosixFilePermissions(Paths.get(homeFolder.getAbsolutePath()), perms);

      String fileName = cs.getConfigurationFilename();
      if (fileName != null) {
        File cf = new File(homeFolder, fileName);
        if (cf.exists()) {
          perms =
              new HashSet<PosixFilePermission>() {
                {
                  add(PosixFilePermission.OWNER_READ);
                  add(PosixFilePermission.OWNER_WRITE);
                }
              };
          Files.setPosixFilePermissions(Paths.get(cf.getAbsolutePath()), perms);
        }
      }
    } catch (Throwable t) {
      logger.error("Error creating c lib instance for fixing file permissions", t);

      if (t instanceof InterruptedException) Thread.currentThread().interrupt();
      else if (t instanceof ThreadDeath) throw (ThreadDeath) t;
    }
  }
}
/**
 * The ProtocolProviderFactory is what actually creates instances of a ProtocolProviderService
 * implementation. A provider factory would register, persistently store, and remove when necessary,
 * ProtocolProviders. The way things are in the SIP Communicator, a user account is represented (in
 * a 1:1 relationship) by an AccountID and a ProtocolProvider. In other words - one would have as
 * many protocol providers installed in a given moment as they would user account registered through
 * the various services.
 *
 * @author Emil Ivov
 * @author Lubomir Marinov
 */
public abstract class ProtocolProviderFactory {
  /**
   * The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class and its instances for
   * logging output.
   */
  private static final Logger logger = Logger.getLogger(ProtocolProviderFactory.class);

  /** Then name of a property which represents a password. */
  public static final String PASSWORD = "******";

  /**
   * The name of a property representing the name of the protocol for an ProtocolProviderFactory.
   */
  public static final String PROTOCOL = "PROTOCOL_NAME";

  /** The name of a property representing the path to protocol icons. */
  public static final String PROTOCOL_ICON_PATH = "PROTOCOL_ICON_PATH";

  /**
   * The name of a property representing the path to the account icon to be used in the user
   * interface, when the protocol provider service is not available.
   */
  public static final String ACCOUNT_ICON_PATH = "ACCOUNT_ICON_PATH";

  /**
   * The name of a property which represents the AccountID of a ProtocolProvider and that, together
   * with a password is used to login on the protocol network..
   */
  public static final String USER_ID = "USER_ID";

  /** The name that should be displayed to others when we are calling or writing them. */
  public static final String DISPLAY_NAME = "DISPLAY_NAME";

  /** The name that should be displayed to the user on call via and chat via lists. */
  public static final String ACCOUNT_DISPLAY_NAME = "ACCOUNT_DISPLAY_NAME";

  /** The name of the property under which we store protocol AccountID-s. */
  public static final String ACCOUNT_UID = "ACCOUNT_UID";

  /**
   * The name of the property under which we store protocol the address of a protocol centric entity
   * (any protocol server).
   */
  public static final String SERVER_ADDRESS = "SERVER_ADDRESS";

  /**
   * The name of the property under which we store the number of the port where the server stored
   * against the SERVER_ADDRESS property is expecting connections to be made via this protocol.
   */
  public static final String SERVER_PORT = "SERVER_PORT";

  /**
   * The name of the property under which we store the name of the transport protocol that needs to
   * be used to access the server.
   */
  public static final String SERVER_TRANSPORT = "SERVER_TRANSPORT";

  /** The name of the property under which we store protocol the address of a protocol proxy. */
  public static final String PROXY_ADDRESS = "PROXY_ADDRESS";

  /**
   * The name of the property under which we store the number of the port where the proxy stored
   * against the PROXY_ADDRESS property is expecting connections to be made via this protocol.
   */
  public static final String PROXY_PORT = "PROXY_PORT";

  /**
   * The name of the property which defines whether proxy is auto configured by the protocol by
   * using known methods such as specific DNS queries.
   */
  public static final String PROXY_AUTO_CONFIG = "PROXY_AUTO_CONFIG";

  /** The property indicating the preferred UDP and TCP port to bind to for clear communications. */
  public static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME =
      "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT";

  /** The property indicating the preferred TLS (TCP) port to bind to for secure communications. */
  public static final String PREFERRED_SECURE_PORT_PROPERTY_NAME =
      "net.java.sip.communicator.SIP_PREFERRED_SECURE_PORT";

  /**
   * The name of the property under which we store the the type of the proxy stored against the
   * PROXY_ADDRESS property. Exact type values depend on protocols and among them are socks4,
   * socks5, http and possibly others.
   */
  public static final String PROXY_TYPE = "PROXY_TYPE";

  /**
   * The name of the property under which we store the the username for the proxy stored against the
   * PROXY_ADDRESS property.
   */
  public static final String PROXY_USERNAME = "******";

  /**
   * The name of the property under which we store the the authorization name for the proxy stored
   * against the PROXY_ADDRESS property.
   */
  public static final String AUTHORIZATION_NAME = "AUTHORIZATION_NAME";

  /**
   * The name of the property under which we store the password for the proxy stored against the
   * PROXY_ADDRESS property.
   */
  public static final String PROXY_PASSWORD = "******";

  /**
   * The name of the property under which we store the name of the transport protocol that needs to
   * be used to access the proxy.
   */
  public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT";

  /**
   * The name of the property that indicates whether loose routing should be forced for all traffic
   * in an account, rather than routing through an outbound proxy which is the default for Jitsi.
   */
  public static final String FORCE_PROXY_BYPASS = "******";

  /**
   * The name of the property under which we store the user preference for a transport protocol to
   * use (i.e. tcp or udp).
   */
  public static final String PREFERRED_TRANSPORT = "PREFERRED_TRANSPORT";

  /**
   * The name of the property under which we store whether we generate resource values or we just
   * use the stored one.
   */
  public static final String AUTO_GENERATE_RESOURCE = "AUTO_GENERATE_RESOURCE";

  /**
   * The name of the property under which we store resources such as the jabber resource property.
   */
  public static final String RESOURCE = "RESOURCE";

  /** The name of the property under which we store resource priority. */
  public static final String RESOURCE_PRIORITY = "RESOURCE_PRIORITY";

  /** The name of the property which defines that the call is encrypted by default */
  public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION";

  /** The name of the property that indicates the encryption protocols for this account. */
  public static final String ENCRYPTION_PROTOCOL = "ENCRYPTION_PROTOCOL";

  /**
   * The name of the property that indicates the status (enabed or disabled) encryption protocols
   * for this account.
   */
  public static final String ENCRYPTION_PROTOCOL_STATUS = "ENCRYPTION_PROTOCOL_STATUS";

  /** The name of the property which defines if to include the ZRTP attribute to SIP/SDP */
  public static final String DEFAULT_SIPZRTP_ATTRIBUTE = "DEFAULT_SIPZRTP_ATTRIBUTE";

  /**
   * The name of the property which defines the ID of the client TLS certificate configuration
   * entry.
   */
  public static final String CLIENT_TLS_CERTIFICATE = "CLIENT_TLS_CERTIFICATE";

  /**
   * The name of the property under which we store the boolean value indicating if the user name
   * should be automatically changed if the specified name already exists. This property is meant to
   * be used by IRC implementations.
   */
  public static final String AUTO_CHANGE_USER_NAME = "AUTO_CHANGE_USER_NAME";

  /**
   * The name of the property under which we store the boolean value indicating if a password is
   * required. Initially this property is meant to be used by IRC implementations.
   */
  public static final String NO_PASSWORD_REQUIRED = "NO_PASSWORD_REQUIRED";

  /** The name of the property under which we store if the presence is enabled. */
  public static final String IS_PRESENCE_ENABLED = "IS_PRESENCE_ENABLED";

  /** The name of the property under which we store if the p2p mode for SIMPLE should be forced. */
  public static final String FORCE_P2P_MODE = "FORCE_P2P_MODE";

  /**
   * The name of the property under which we store the offline contact polling period for SIMPLE.
   */
  public static final String POLLING_PERIOD = "POLLING_PERIOD";

  /**
   * The name of the property under which we store the chosen default subscription expiration value
   * for SIMPLE.
   */
  public static final String SUBSCRIPTION_EXPIRATION = "SUBSCRIPTION_EXPIRATION";

  /** Indicates if the server address has been validated. */
  public static final String SERVER_ADDRESS_VALIDATED = "SERVER_ADDRESS_VALIDATED";

  /** Indicates if the server settings are over */
  public static final String IS_SERVER_OVERRIDDEN = "IS_SERVER_OVERRIDDEN";
  /** Indicates if the proxy address has been validated. */
  public static final String PROXY_ADDRESS_VALIDATED = "PROXY_ADDRESS_VALIDATED";

  /** Indicates the search strategy chosen for the DICT protocole. */
  public static final String STRATEGY = "STRATEGY";

  /** Indicates a protocol that would not be shown in the user interface as an account. */
  public static final String IS_PROTOCOL_HIDDEN = "IS_PROTOCOL_HIDDEN";

  /** Indicates if the given account is the preferred account. */
  public static final String IS_PREFERRED_PROTOCOL = "IS_PREFERRED_PROTOCOL";

  /**
   * The name of the property that would indicate if a given account is currently enabled or
   * disabled.
   */
  public static final String IS_ACCOUNT_DISABLED = "IS_ACCOUNT_DISABLED";

  /** Indicates if ICE should be used. */
  public static final String IS_USE_ICE = "ICE_ENABLED";

  /** Indicates if Google ICE should be used. */
  public static final String IS_USE_GOOGLE_ICE = "GTALK_ICE_ENABLED";

  /** Indicates if STUN server should be automatically discovered. */
  public static final String AUTO_DISCOVER_STUN = "AUTO_DISCOVER_STUN";

  /** Indicates if default STUN server would be used if no other STUN/TURN server are available. */
  public static final String USE_DEFAULT_STUN_SERVER = "USE_DEFAULT_STUN_SERVER";

  /**
   * The name of the boolean account property which indicates whether Jitsi VideoBridge is to be
   * used, if available and supported, for conference calls.
   */
  public static final String USE_JITSI_VIDEO_BRIDGE = "USE_JITSI_VIDEO_BRIDGE";

  /**
   * The property name prefix for all stun server properties. We generally use this prefix in
   * conjunction with an index which is how we store multiple servers.
   */
  public static final String STUN_PREFIX = "STUN";

  /** The base property name for address of additional STUN servers specified. */
  public static final String STUN_ADDRESS = "ADDRESS";

  /** The base property name for port of additional STUN servers specified. */
  public static final String STUN_PORT = "PORT";

  /** The base property name for username of additional STUN servers specified. */
  public static final String STUN_USERNAME = "******";

  /** The base property name for password of additional STUN servers specified. */
  public static final String STUN_PASSWORD = "******";

  /**
   * The base property name for the turn supported property of additional STUN servers specified.
   */
  public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED";

  /** Indicates if JingleNodes should be used with ICE. */
  public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED";

  /** Indicates if JingleNodes should be used with ICE. */
  public static final String AUTO_DISCOVER_JINGLE_NODES = "AUTO_DISCOVER_JINGLE_NODES";

  /** Indicates if JingleNodes should use buddies to search for nodes. */
  public static final String JINGLE_NODES_SEARCH_BUDDIES = "JINGLE_NODES_SEARCH_BUDDIES";

  /** Indicates if UPnP should be used with ICE. */
  public static final String IS_USE_UPNP = "UPNP_ENABLED";

  /** Indicates if we allow non-TLS connection. */
  public static final String IS_ALLOW_NON_SECURE = "ALLOW_NON_SECURE";

  /** Enable notifications for new voicemail messages. */
  public static final String VOICEMAIL_ENABLED = "VOICEMAIL_ENABLED";

  /**
   * Address used to reach voicemail box, by services able to subscribe for voicemail new messages
   * notifications.
   */
  public static final String VOICEMAIL_URI = "VOICEMAIL_URI";

  /** Address used to call to hear your messages stored on the server for your voicemail. */
  public static final String VOICEMAIL_CHECK_URI = "VOICEMAIL_CHECK_URI";

  /** Indicates if calling is disabled for a certain account. */
  public static final String IS_CALLING_DISABLED_FOR_ACCOUNT = "CALLING_DISABLED";

  /** Indicates if desktop streaming/sharing is disabled for a certain account. */
  public static final String IS_DESKTOP_STREAMING_DISABLED = "DESKTOP_STREAMING_DISABLED";

  /** The sms default server address. */
  public static final String SMS_SERVER_ADDRESS = "SMS_SERVER_ADDRESS";

  /** Keep-alive method used by the protocol. */
  public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD";

  /** The interval for keep-alives if any. */
  public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";

  /** The minimal DTMF tone duration. */
  public static final String DTMF_MINIMAL_TONE_DURATION = "DTMF_MINIMAL_TONE_DURATION";

  /** Paranoia mode when turned on requires all calls to be secure and indicated as such. */
  public static final String MODE_PARANOIA = "MODE_PARANOIA";

  /** The name of the "override encodings" property */
  public static final String OVERRIDE_ENCODINGS = "OVERRIDE_ENCODINGS";

  /** The prefix used to store account encoding properties */
  public static final String ENCODING_PROP_PREFIX = "Encodings";

  /**
   * The <code>BundleContext</code> containing (or to contain) the service registration of this
   * factory.
   */
  private final BundleContext bundleContext;

  /**
   * The name of the protocol this factory registers its <code>ProtocolProviderService</code>s with
   * and to be placed in the properties of the accounts created by this factory.
   */
  private final String protocolName;

  /**
   * The table that we store our accounts in.
   *
   * <p>TODO Synchronize the access to the field which may in turn be better achieved by also hiding
   * it from protected into private access.
   */
  protected final Hashtable<AccountID, ServiceRegistration> registeredAccounts =
      new Hashtable<AccountID, ServiceRegistration>();

  /**
   * The name of the property that indicates the AVP type.
   *
   * <ul>
   *   <li>{@link #SAVP_OFF}
   *   <li>{@link #SAVP_MANDATORY}
   *   <li>{@link #SAVP_OPTIONAL}
   * </ul>
   */
  public static final String SAVP_OPTION = "SAVP_OPTION";

  /** Always use RTP/AVP */
  public static final int SAVP_OFF = 0;

  /** Always use RTP/SAVP */
  public static final int SAVP_MANDATORY = 1;

  /** Sends two media description, with RTP/SAVP being first. */
  public static final int SAVP_OPTIONAL = 2;

  /**
   * The name of the property that defines the enabled SDES cipher suites. Enabled suites are listed
   * as CSV by their RFC name.
   */
  public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES";

  /**
   * Creates a new <tt>ProtocolProviderFactory</tt>.
   *
   * @param bundleContext the bundle context reference of the service
   * @param protocolName the name of the protocol
   */
  protected ProtocolProviderFactory(BundleContext bundleContext, String protocolName) {
    this.bundleContext = bundleContext;
    this.protocolName = protocolName;
  }

  /**
   * Gets the <code>BundleContext</code> containing (or to contain) the service registration of this
   * factory.
   *
   * @return the <code>BundleContext</code> containing (or to contain) the service registration of
   *     this factory
   */
  public BundleContext getBundleContext() {
    return bundleContext;
  }

  /**
   * Initializes and creates an account corresponding to the specified accountProperties and
   * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter. Note
   * that account registration is persistent and accounts that are registered during a particular
   * sip-communicator session would be automatically reloaded during all following sessions until
   * they are removed through the removeAccount method.
   *
   * @param userID the user identifier uniquely representing the newly created account within the
   *     protocol namespace.
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @return the AccountID of the newly created account.
   * @throws java.lang.IllegalArgumentException if userID does not correspond to an identifier in
   *     the context of the underlying protocol or if accountProperties does not contain a complete
   *     set of account installation properties.
   * @throws java.lang.IllegalStateException if the account has already been installed.
   * @throws java.lang.NullPointerException if any of the arguments is null.
   */
  public abstract AccountID installAccount(String userID, Map<String, String> accountProperties)
      throws IllegalArgumentException, IllegalStateException, NullPointerException;

  /**
   * Modifies the account corresponding to the specified accountID. This method is meant to be used
   * to change properties of already existing accounts. Note that if the given accountID doesn't
   * correspond to any registered account this method would do nothing.
   *
   * @param protocolProvider the protocol provider service corresponding to the modified account.
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @throws java.lang.NullPointerException if any of the arguments is null.
   */
  public abstract void modifyAccount(
      ProtocolProviderService protocolProvider, Map<String, String> accountProperties)
      throws NullPointerException;

  /**
   * Returns a copy of the list containing the <tt>AccountID</tt>s of all accounts currently
   * registered in this protocol provider.
   *
   * @return a copy of the list containing the <tt>AccountID</tt>s of all accounts currently
   *     registered in this protocol provider.
   */
  public ArrayList<AccountID> getRegisteredAccounts() {
    synchronized (registeredAccounts) {
      return new ArrayList<AccountID>(registeredAccounts.keySet());
    }
  }

  /**
   * Returns the ServiceReference for the protocol provider corresponding to the specified accountID
   * or null if the accountID is unknown.
   *
   * @param accountID the accountID of the protocol provider we'd like to get
   * @return a ServiceReference object to the protocol provider with the specified account id and
   *     null if the account id is unknown to the provider factory.
   */
  public ServiceReference getProviderForAccount(AccountID accountID) {
    ServiceRegistration registration;

    synchronized (registeredAccounts) {
      registration = registeredAccounts.get(accountID);
    }
    return (registration == null) ? null : registration.getReference();
  }

  /**
   * Removes the specified account from the list of accounts that this provider factory is handling.
   * If the specified accountID is unknown to the ProtocolProviderFactory, the call has no effect
   * and false is returned. This method is persistent in nature and once called the account
   * corresponding to the specified ID will not be loaded during future runs of the project.
   *
   * @param accountID the ID of the account to remove.
   * @return true if an account with the specified ID existed and was removed and false otherwise.
   */
  public boolean uninstallAccount(AccountID accountID) {
    // Unregister the protocol provider.
    ServiceReference serRef = getProviderForAccount(accountID);

    boolean wasAccountExisting = false;

    // If the protocol provider service is registered, first unregister the
    // service.
    if (serRef != null) {
      BundleContext bundleContext = getBundleContext();
      ProtocolProviderService protocolProvider =
          (ProtocolProviderService) bundleContext.getService(serRef);

      try {
        protocolProvider.unregister();
      } catch (OperationFailedException ex) {
        logger.error(
            "Failed to unregister protocol provider for account: "
                + accountID
                + " caused by: "
                + ex);
      }
    }

    ServiceRegistration registration;

    synchronized (registeredAccounts) {
      registration = registeredAccounts.remove(accountID);
    }

    // first remove the stored account so when PP is unregistered we can
    // distinguish between deleted or just disabled account
    wasAccountExisting = removeStoredAccount(accountID);

    if (registration != null) {
      // Kill the service.
      registration.unregister();
    }

    return wasAccountExisting;
  }

  /**
   * The method stores the specified account in the configuration service under the package name of
   * the source factory. The restore and remove account methods are to be used to obtain access to
   * and control the stored accounts.
   *
   * <p>In order to store all account properties, the method would create an entry in the
   * configuration service corresponding (beginning with) the <tt>sourceFactory</tt>'s package name
   * and add to it a unique identifier (e.g. the current miliseconds.)
   *
   * @param accountID the AccountID corresponding to the account that we would like to store.
   */
  protected void storeAccount(AccountID accountID) {
    this.storeAccount(accountID, true);
  }

  /**
   * The method stores the specified account in the configuration service under the package name of
   * the source factory. The restore and remove account methods are to be used to obtain access to
   * and control the stored accounts.
   *
   * <p>In order to store all account properties, the method would create an entry in the
   * configuration service corresponding (beginning with) the <tt>sourceFactory</tt>'s package name
   * and add to it a unique identifier (e.g. the current miliseconds.)
   *
   * @param accountID the AccountID corresponding to the account that we would like to store.
   * @param isModification if <tt>false</tt> there must be no such already loaded account, it
   *     <tt>true</tt> ist modification of an existing account. Usually we use this method with
   *     <tt>false</tt> in method installAccount and with <tt>true</tt> or the overridden method in
   *     method modifyAccount.
   */
  protected void storeAccount(AccountID accountID, boolean isModification) {
    if (!isModification && getAccountManager().getStoredAccounts().contains(accountID)) {
      throw new IllegalStateException(
          "An account for id " + accountID.getUserID() + " was already loaded!");
    }

    try {
      getAccountManager().storeAccount(this, accountID);
    } catch (OperationFailedException ofex) {
      throw new UndeclaredThrowableException(ofex);
    }
  }

  /**
   * Saves the password for the specified account after scrambling it a bit so that it is not
   * visible from first sight. (The method remains highly insecure).
   *
   * @param accountID the AccountID for the account whose password we're storing
   * @param password the password itself
   * @throws IllegalArgumentException if no account corresponding to <code>accountID</code> has been
   *     previously stored
   */
  public void storePassword(AccountID accountID, String password) throws IllegalArgumentException {
    try {
      storePassword(getBundleContext(), accountID, password);
    } catch (OperationFailedException ofex) {
      throw new UndeclaredThrowableException(ofex);
    }
  }

  /**
   * Saves the password for the specified account after scrambling it a bit so that it is not
   * visible from first sight (Method remains highly insecure).
   *
   * <p>TODO Delegate the implementation to {@link AccountManager} because it knows the format in
   * which the password (among the other account properties) is to be saved.
   *
   * @param bundleContext a currently valid bundle context.
   * @param accountID the <tt>AccountID</tt> of the account whose password is to be stored
   * @param password the password to be stored
   * @throws IllegalArgumentException if no account corresponding to <tt>accountID</tt> has been
   *     previously stored.
   * @throws OperationFailedException if anything goes wrong while storing the specified
   *     <tt>password</tt>
   */
  protected void storePassword(BundleContext bundleContext, AccountID accountID, String password)
      throws IllegalArgumentException, OperationFailedException {
    String accountPrefix = findAccountPrefix(bundleContext, accountID, getFactoryImplPackageName());

    if (accountPrefix == null) {
      throw new IllegalArgumentException(
          "No previous records found for account ID: "
              + accountID.getAccountUniqueID()
              + " in package"
              + getFactoryImplPackageName());
    }

    CredentialsStorageService credentialsStorage =
        ServiceUtils.getService(bundleContext, CredentialsStorageService.class);

    if (!credentialsStorage.storePassword(accountPrefix, password)) {
      throw new OperationFailedException(
          "CredentialsStorageService failed to storePassword",
          OperationFailedException.GENERAL_ERROR);
    }
  }

  /**
   * Returns the password last saved for the specified account.
   *
   * @param accountID the AccountID for the account whose password we're looking for
   * @return a String containing the password for the specified accountID
   */
  public String loadPassword(AccountID accountID) {
    return loadPassword(getBundleContext(), accountID);
  }

  /**
   * Returns the password last saved for the specified account.
   *
   * <p>TODO Delegate the implementation to {@link AccountManager} because it knows the format in
   * which the password (among the other account properties) was saved.
   *
   * @param bundleContext a currently valid bundle context.
   * @param accountID the AccountID for the account whose password we're looking for..
   * @return a String containing the password for the specified accountID.
   */
  protected String loadPassword(BundleContext bundleContext, AccountID accountID) {
    String accountPrefix = findAccountPrefix(bundleContext, accountID, getFactoryImplPackageName());

    if (accountPrefix == null) return null;

    CredentialsStorageService credentialsStorage =
        ServiceUtils.getService(bundleContext, CredentialsStorageService.class);

    return credentialsStorage.loadPassword(accountPrefix);
  }

  /**
   * Initializes and creates an account corresponding to the specified accountProperties and
   * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter. This
   * method has a persistent effect. Once created the resulting account will remain installed until
   * removed through the uninstallAccount method.
   *
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @return the AccountID of the newly loaded account
   */
  public AccountID loadAccount(Map<String, String> accountProperties) {
    AccountID accountID = createAccount(accountProperties);

    loadAccount(accountID);

    return accountID;
  }

  /**
   * Creates a protocol provider for the given <tt>accountID</tt> and registers it in the bundle
   * context. This method has a persistent effect. Once created the resulting account will remain
   * installed until removed through the uninstallAccount method.
   *
   * @param accountID the account identifier
   * @return <tt>true</tt> if the account with the given <tt>accountID</tt> is successfully loaded,
   *     otherwise returns <tt>false</tt>
   */
  public boolean loadAccount(AccountID accountID) {
    // Need to obtain the original user id property, instead of calling
    // accountID.getUserID(), because this method could return a modified
    // version of the user id property.
    String userID = accountID.getAccountPropertyString(ProtocolProviderFactory.USER_ID);

    ProtocolProviderService service = createService(userID, accountID);

    Dictionary<String, String> properties = new Hashtable<String, String>();
    properties.put(PROTOCOL, protocolName);
    properties.put(USER_ID, userID);

    ServiceRegistration serviceRegistration =
        bundleContext.registerService(ProtocolProviderService.class.getName(), service, properties);

    if (serviceRegistration == null) return false;
    else {
      synchronized (registeredAccounts) {
        registeredAccounts.put(accountID, serviceRegistration);
      }
      return true;
    }
  }

  /**
   * Unloads the account corresponding to the given <tt>accountID</tt>. Unregisters the
   * corresponding protocol provider, but keeps the account in contrast to the uninstallAccount
   * method.
   *
   * @param accountID the account identifier
   * @return true if an account with the specified ID existed and was unloaded and false otherwise.
   */
  public boolean unloadAccount(AccountID accountID) {
    // Unregister the protocol provider.
    ServiceReference serRef = getProviderForAccount(accountID);

    if (serRef == null) {
      return false;
    }

    BundleContext bundleContext = getBundleContext();
    ProtocolProviderService protocolProvider =
        (ProtocolProviderService) bundleContext.getService(serRef);

    try {
      protocolProvider.unregister();
    } catch (OperationFailedException ex) {
      logger.error(
          "Failed to unregister protocol provider for account : "
              + accountID
              + " caused by: "
              + ex);
    }

    ServiceRegistration registration;

    synchronized (registeredAccounts) {
      registration = registeredAccounts.remove(accountID);
    }
    if (registration == null) {
      return false;
    }

    // Kill the service.
    registration.unregister();

    return true;
  }

  /**
   * Initializes and creates an account corresponding to the specified accountProperties.
   *
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @return the AccountID of the newly created account
   */
  public AccountID createAccount(Map<String, String> accountProperties) {
    BundleContext bundleContext = getBundleContext();
    if (bundleContext == null)
      throw new NullPointerException("The specified BundleContext was null");

    if (accountProperties == null)
      throw new NullPointerException("The specified property map was null");

    String userID = accountProperties.get(USER_ID);
    if (userID == null)
      throw new NullPointerException("The account properties contained no user id.");

    String protocolName = getProtocolName();
    if (!accountProperties.containsKey(PROTOCOL)) accountProperties.put(PROTOCOL, protocolName);

    return createAccountID(userID, accountProperties);
  }

  /**
   * Creates a new <code>AccountID</code> instance with a specific user ID to represent a given set
   * of account properties.
   *
   * <p>The method is a pure factory allowing implementers to specify the runtime type of the
   * created <code>AccountID</code> and customize the instance. The returned <code>AccountID</code>
   * will later be associated with a <code>ProtocolProviderService</code> by the caller (e.g. using
   * {@link #createService(String, AccountID)}).
   *
   * @param userID the user ID of the new instance
   * @param accountProperties the set of properties to be represented by the new instance
   * @return a new <code>AccountID</code> instance with the specified user ID representing the given
   *     set of account properties
   */
  protected abstract AccountID createAccountID(
      String userID, Map<String, String> accountProperties);

  /**
   * Gets the name of the protocol this factory registers its <code>ProtocolProviderService</code>s
   * with and to be placed in the properties of the accounts created by this factory.
   *
   * @return the name of the protocol this factory registers its <code>ProtocolProviderService
   *     </code>s with and to be placed in the properties of the accounts created by this factory
   */
  public String getProtocolName() {
    return protocolName;
  }

  /**
   * Initializes a new <code>ProtocolProviderService</code> instance with a specific user ID to
   * represent a specific <code>AccountID</code>.
   *
   * <p>The method is a pure factory allowing implementers to specify the runtime type of the
   * created <code>ProtocolProviderService</code> and customize the instance. The caller will later
   * register the returned service with the <code>BundleContext</code> of this factory.
   *
   * @param userID the user ID to initialize the new instance with
   * @param accountID the <code>AccountID</code> to be represented by the new instance
   * @return a new <code>ProtocolProviderService</code> instance with the specific user ID
   *     representing the specified <code>AccountID</code>
   */
  protected abstract ProtocolProviderService createService(String userID, AccountID accountID);

  /**
   * Removes the account with <tt>accountID</tt> from the set of accounts that are persistently
   * stored inside the configuration service.
   *
   * @param accountID the AccountID of the account to remove.
   * @return true if an account has been removed and false otherwise.
   */
  protected boolean removeStoredAccount(AccountID accountID) {
    return getAccountManager().removeStoredAccount(this, accountID);
  }

  /**
   * Returns the prefix for all persistently stored properties of the account with the specified id.
   *
   * @param bundleContext a currently valid bundle context.
   * @param accountID the AccountID of the account whose properties we're looking for.
   * @param sourcePackageName a String containing the package name of the concrete factory class
   *     that extends us.
   * @return a String indicating the ConfigurationService property name prefix under which all
   *     account properties are stored or null if no account corresponding to the specified id was
   *     found.
   */
  public static String findAccountPrefix(
      BundleContext bundleContext, AccountID accountID, String sourcePackageName) {
    ServiceReference confReference =
        bundleContext.getServiceReference(ConfigurationService.class.getName());
    ConfigurationService configurationService =
        (ConfigurationService) bundleContext.getService(confReference);

    // first retrieve all accounts that we've registered
    List<String> storedAccounts =
        configurationService.getPropertyNamesByPrefix(sourcePackageName, true);

    // find an account with the corresponding id.
    for (String accountRootPropertyName : storedAccounts) {
      // unregister the account in the configuration service.
      // all the properties must have been registered in the following
      // hierarchy:
      // net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
      String accountUID =
          configurationService.getString(
              accountRootPropertyName // node id
                  + "."
                  + ACCOUNT_UID); // propname

      if (accountID.getAccountUniqueID().equals(accountUID)) {
        return accountRootPropertyName;
      }
    }
    return null;
  }

  /**
   * Returns the name of the package that we're currently running in (i.e. the name of the package
   * containing the proto factory that extends us).
   *
   * @return a String containing the package name of the concrete factory class that extends us.
   */
  private String getFactoryImplPackageName() {
    String className = getClass().getName();

    return className.substring(0, className.lastIndexOf('.'));
  }

  /** Prepares the factory for bundle shutdown. */
  public void stop() {
    if (logger.isTraceEnabled()) logger.trace("Preparing to stop all protocol providers of" + this);

    synchronized (registeredAccounts) {
      for (Enumeration<ServiceRegistration> registrations = registeredAccounts.elements();
          registrations.hasMoreElements(); ) {
        ServiceRegistration reg = registrations.nextElement();

        stop(reg);

        reg.unregister();
      }

      registeredAccounts.clear();
    }
  }

  /**
   * Shuts down the <code>ProtocolProviderService</code> representing an account registered with
   * this factory.
   *
   * @param registeredAccount the <code>ServiceRegistration</code> of the <code>
   *     ProtocolProviderService</code> representing an account registered with this factory
   */
  protected void stop(ServiceRegistration registeredAccount) {
    ProtocolProviderService protocolProviderService =
        (ProtocolProviderService) getBundleContext().getService(registeredAccount.getReference());

    protocolProviderService.shutdown();
  }

  /**
   * Get the <tt>AccountManager</tt> of the protocol.
   *
   * @return <tt>AccountManager</tt> of the protocol
   */
  private AccountManager getAccountManager() {
    BundleContext bundleContext = getBundleContext();
    ServiceReference serviceReference =
        bundleContext.getServiceReference(AccountManager.class.getName());

    return (AccountManager) bundleContext.getService(serviceReference);
  }
}
Exemple #23
0
/**
 * A representation of the LogicSniffer device.
 *
 * @author J.W. Janssen
 */
public class LogicSnifferDevice implements Device {
  // CONSTANTS

  private static final String NAME = "OpenBench LogicSniffer";

  private static final Logger LOG = Logger.getLogger(LogicSnifferDevice.class.getName());

  // VARIABLES

  private LogicSnifferConfig config;

  private volatile DependencyManager dependencyManager;
  private volatile ManagedServiceFactory deviceProfileManagerServiceFactory;
  private volatile ConnectorService connectorService;
  private volatile StreamConnection connection;
  private volatile LogicSnifferConfigDialog configDialog;

  // METHODS

  public LogicSnifferConfig getConfig() {
    return config;
  }

  /** {@inheritDoc} */
  @Override
  public void close() throws IOException {
    if (this.connection != null) {
      this.connection.close();
      this.connection = null;
    }
  }

  /** {@inheritDoc} */
  @Override
  public AcquisitionTask createAcquisitionTask(final AcquisitionProgressListener aProgressListener)
      throws IOException {
    return new LogicSnifferAcquisitionTask(
        this.config, getStreamConnection(), getDeviceProfileManager(), aProgressListener);
  }

  /** {@inheritDoc} */
  @Override
  public CancelTask createCancelTask() throws IOException {
    if (this.config.isRleEnabled()) {
      return new LogicSnifferCancelTask(getStreamConnection());
    }
    // Simply use the default behaviour...
    return null;
  }

  /** @see nl.lxtreme.ols.api.devices.Device#getName() */
  public String getName() {
    return NAME;
  }

  /** @see nl.lxtreme.ols.api.devices.Device#isSetup() */
  @Override
  public boolean isSetup() {
    return this.config != null;
  }

  /**
   * Displays the device controller dialog with enabled configuration portion and waits for user
   * input.
   *
   * @see nl.lxtreme.ols.api.devices.Device#setupCapture()
   */
  @Override
  public boolean setupCapture(final Window aOwner) {
    // Just to be sure...
    disposeConfigDialog();

    this.configDialog = new LogicSnifferConfigDialog(aOwner, this);

    try {
      boolean configConfirmed = this.configDialog.showDialog();
      if (configConfirmed) {
        this.config = this.configDialog.getConfiguration();
      }
      return configConfirmed;
    } finally {
      this.configDialog.dispose();
      this.configDialog = null;
    }
  }

  /**
   * @param uri
   * @return
   * @throws IOException
   */
  final StreamConnection createStreamConnection(final String uri) throws IOException {
    return (StreamConnection)
        this.connectorService.open(uri, ConnectorService.READ_WRITE, true /* timeouts */);
  }

  /**
   * Returns the default device profile.
   *
   * @return a default profile, never <code>null</code>.
   */
  final DeviceProfile getDefaultProfile() {
    return getDeviceProfileManager().getDefaultProfile();
  }

  /**
   * Returns the current device profile manager.
   *
   * @return a device profile manager, never <code>null</code>.
   */
  final DeviceProfileManager getDeviceProfileManager() {
    return (DeviceProfileManager) this.deviceProfileManagerServiceFactory;
  }

  /** Called when this class is unregistered as OSGi service. */
  protected void destroy(final Component aComponent) {
    disposeConfigDialog();
  }

  /**
   * Called when this class is registered as OSGi service.
   *
   * @param aComponent the bundle context to use, cannot be <code>null</code>.
   */
  protected void init(final Component aComponent) {
    final String pmFilter =
        String.format("(%s=%s)", Constants.SERVICE_PID, DeviceProfileManager.SERVICE_PID);

    aComponent //
        .add(
            this.dependencyManager
                .createServiceDependency() //
                .setService(ManagedServiceFactory.class, pmFilter) //
                .setAutoConfig("deviceProfileManagerServiceFactory") //
                .setInstanceBound(true) //
                .setRequired(true)) //
        .add(
            this.dependencyManager
                .createServiceDependency() //
                .setService(ConnectorService.class) //
                .setAutoConfig("connectorService") //
                .setInstanceBound(true) //
                .setRequired(true) //
            );
  }

  /**
   * Disposes the current configuration dialog, if one is still visible on screen. If no
   * configuration dialog is visible, this method does nothing.
   */
  private void disposeConfigDialog() {
    if (this.configDialog != null) {
      SwingComponentUtils.dispose(this.configDialog);
      this.configDialog = null;
    }
  }

  /**
   * Returns the current stream connection that is opened.
   *
   * @return a stream connection, can be a cached one, never <code>null</code>.
   * @throws IOException in case of I/O problems creating the stream connection.
   */
  private StreamConnection getStreamConnection() throws IOException {
    if (this.connection == null) {
      final String uri = this.config.getConnectionURI();

      if (LOG.isLoggable(Level.INFO)) {
        LOG.info("Connecting to " + uri);
      }

      this.connection = createStreamConnection(uri);
    }
    return this.connection;
  }
}