mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Jean-Noel Rouvignac
01.23.2014 5d7be546948d1d019e3d29932b222d69412643dd
opends/src/guitools/org/opends/guitools/controlpanel/browser/BrowserController.java
@@ -89,20 +89,20 @@
  /**
   * The mask used to display the number of ACIs or not.
   */
  public final static int DISPLAY_ACI_COUNT        = 0x01;
  private static final int DISPLAY_ACI_COUNT = 0x01;
  /**
   * The list of attributes that are used to sort the entries (if the sorting
   * option is used).
   */
  public static final String[] SORT_ATTRIBUTES = {"cn", "givenname", "o", "ou",
    "sn", "uid"};
  private static final String[] SORT_ATTRIBUTES =
    { "cn", "givenname", "o", "ou", "sn", "uid" };
  /**
   * This is a key value.  It is used to specify that the attribute that should
   * be used to display the entry is the RDN attribute.
   */
  public static final String RDN_ATTRIBUTE = "rdn attribute";
  private static final String RDN_ATTRIBUTE = "rdn attribute";
  /**
   * The filter used to retrieve all the entries.
@@ -110,12 +110,12 @@
  public static final String ALL_OBJECTS_FILTER =
    "(|(objectClass=*)(objectClass=ldapsubentry))";
  private JTree tree;
  private DefaultTreeModel treeModel;
  private RootNode rootNode;
  private final JTree tree;
  private final DefaultTreeModel treeModel;
  private final RootNode rootNode;
  private int displayFlags;
  private String displayAttribute;
  private boolean showAttributeName;
  private final boolean showAttributeName;
  private InitialLdapContext ctxConfiguration;
  private InitialLdapContext ctxUserData;
  private boolean followReferrals;
@@ -127,12 +127,12 @@
  private NumSubordinateHacker numSubordinateHacker;
  private int queueTotalSize;
  private int maxChildren = 0;
  private Collection<BrowserEventListener> listeners =
  private final Collection<BrowserEventListener> listeners =
    new ArrayList<BrowserEventListener>();
  private LDAPConnectionPool connectionPool;
  private IconPool iconPool;
  private final LDAPConnectionPool connectionPool;
  private final IconPool iconPool;
  private NodeSearcherQueue refreshQueue;
  private final NodeSearcherQueue refreshQueue;
  private String filter;
@@ -159,6 +159,7 @@
    tree.addTreeExpansionListener(this);
    tree.setCellRenderer(new BrowserCellRenderer());
    displayFlags = DISPLAY_ACI_COUNT;
    showAttributeName = false;
    displayAttribute = RDN_ATTRIBUTE;
    followReferrals = false;
    sorted = false;
@@ -260,17 +261,16 @@
  }
  /**
   * Tells wether the given suffix is in the tree or not.
   * Tells whether the given suffix is in the tree or not.
   * @param suffixDn the DN of the suffix to be analyzed.
   * @return <CODE>true</CODE> if the provided String is the DN of a suffix
   * and <CODE>false</CODE> otherwise.
   * @throws IllegalArgumentException if a node with the given dn exists but
   * is not a suffix node.
   */
  public boolean hasSuffix(String suffixDn)
  throws IllegalArgumentException
  public boolean hasSuffix(String suffixDn) throws IllegalArgumentException
  {
    return (findSuffixNode(suffixDn, rootNode) != null);
    return findSuffixNode(suffixDn, rootNode) != null;
  }
  /**
@@ -328,28 +328,6 @@
    return new TreePath(treeModel.getPathToRoot(newNode));
  }
  /**
   * Remove the suffix from this controller.
   * The controller updates the JTree and returns the TreePath
   * of the parent node.
   * @param suffixDn the DN of the suffix to be removed.
   * @return the TreePath of the parent node of the removed node.
   */
  public TreePath removeSuffix(String suffixDn) {
    TreePath result = null;
    BasicNode node = findSuffixNode(suffixDn, rootNode);
    TreeNode parentNode = node.getParent();
    /* If the parent is null... the node is no longer in the tree */
    if (parentNode != null) {
      removeOneNode(node);
      result = new TreePath(treeModel.getPathToRoot(parentNode));
    }
    return result;
  }
  /**
   * Remove all the suffixes.
   * The controller removes all the nodes from the JTree except the root.
@@ -403,18 +381,6 @@
  }
  /**
   * Says wether to show the attribute name or not.
   * This routine collapses the JTree and invokes startRefresh().
   * @param showAttributeName whether to show the attribute name or not.
   */
  public void showAttributeName(boolean showAttributeName) {
    this.showAttributeName = showAttributeName;
    stopRefresh();
    removeAllChildNodes(rootNode, true /* Keep suffixes */);
    startRefresh(null);
  }
  /**
   * Says wether we are showing the attribute name or not.
   * @return <CODE>true</CODE> if we are showing the attribute name and
   * <CODE>false</CODE> otherwise.
@@ -586,16 +552,6 @@
    listeners.add(l);
  }
  /**
   * Remove a BrowserEventListener from this controller.
   * @param l the listener to be removed.
   */
  public void removeBrowserEventListener(BrowserEventListener l) {
    listeners.remove(l);
  }
  /**
   * Notify this controller that an entry has been added.
   * The controller adds a new node in the JTree and starts refreshing this new
@@ -635,19 +591,18 @@
   * @return the tree path associated with the parent of the deleted node.
   */
  public TreePath notifyEntryDeleted(BrowserNodeInfo nodeInfo) {
    TreePath result = null;
    BasicNode node = nodeInfo.getNode();
    if (node == rootNode) {
      throw new IllegalArgumentException("Root node cannot be removed");
    }
    TreeNode parentNode = node.getParent();
    /* If the parent is null... the node is no longer in the tree */
    final TreeNode parentNode = node.getParent();
    if (parentNode != null) {
      removeOneNode(node);
      result = new TreePath(treeModel.getPathToRoot(parentNode));
      return new TreePath(treeModel.getPathToRoot(parentNode));
    }
    return result;
    return null;
  }
@@ -663,50 +618,10 @@
  }
  /**
   * Notify this controller that a child entry has changed.
   * The controller has to refresh the corresponding node and (if necessary)
   * itself.
   * @param nodeInfo the parent of the node that changed.
   * @param dn the DN of the entry that changed.
   */
  public void notifyChildEntryChanged(BrowserNodeInfo nodeInfo, String dn) {
    BasicNode node = nodeInfo.getNode();
    startRefreshNode(node, null, true);
  }
  /**
   * Notify this controller that a child entry has been added.
   * The controller has to refresh the corresponding node and (if necessary)
   * itself.
   * @param nodeInfo the parent of the node that was added.
   * @param dn the DN of the entry that was added.
   */
  public void notifyChildEntryAdded(BrowserNodeInfo nodeInfo, String dn) {
    BasicNode node = nodeInfo.getNode();
    startRefreshNode(node, null, true);
  }
  /**
   * Notify this controller that a child entry has been deleted.
   * The controller has to refresh the corresponding node and (if necessary)
   * itself.
   * @param nodeInfo the parent of the node that was deleted.
   * @param dn the DN of the entry that was deleted.
   */
  public void notifyChildEntryDeleted(BrowserNodeInfo nodeInfo, String dn) {
    BasicNode node = nodeInfo.getNode();
    if (node.getParent() != null) {
      startRefreshNode((BasicNode) node.getParent(), null, true);
    } else {
      startRefreshNode(node, null, true);
    }
  }
  /**
   * Notify this controller that authentication data have changed in the
   * connection pool.
   */
  @Override
  public void notifyAuthDataChanged() {
    notifyAuthDataChanged(null);
  }
@@ -718,7 +633,7 @@
   * url.
   * @param url the URL of the connection that changed.
   */
  public void notifyAuthDataChanged(LDAPURL url) {
  private void notifyAuthDataChanged(LDAPURL url) {
    // TODO: temporary implementation
    //    we should refresh only nodes :
    //    - whose URL matches 'url'
@@ -730,7 +645,7 @@
  /**
   * Start a refresh from the specified node.
   * If some refresh are on-going on descendent nodes, they are stopped.
   * If some refresh are on-going on descendant nodes, they are stopped.
   * If nodeInfo is null, refresh is started from the root.
   * @param nodeInfo the node to be refreshed.
   */
@@ -746,35 +661,15 @@
    startRefreshNode(node, null, true);
  }
  /**
   * Equivalent to startRefresh(null).
   */
  public void startRefresh() {
    startRefresh(null);
  }
  /**
   * Stop the current refreshing.
   * Nodes being expanded are collapsed.
   */
  public void stopRefresh() {
  private void stopRefresh() {
    stopRefreshNode(rootNode);
    // TODO: refresh must be stopped in a clean state.
  }
  /**
   * Shutdown the controller : all the backgroup threads are stopped.
   * After this call, the controller is no longer usable.
   */
  public void shutDown() {
    tree.removeTreeExpansionListener(this);
    refreshQueue.shutdown();
    connectionPool.flush();
  }
  /**
   * Start refreshing the whole tree from the specified node.
   * We queue a refresh which:
@@ -784,7 +679,7 @@
   * @param localEntry the local entry corresponding to the node.
   * @param recursive whether the refresh must be executed recursively or not.
   */
  void startRefreshNode(BasicNode node, SearchResult localEntry,
  private void startRefreshNode(BasicNode node, SearchResult localEntry,
      boolean recursive) {
    if (node == rootNode) {
      // For the root node, readBaseEntry is meaningless.
@@ -803,7 +698,7 @@
      // The task does not *see* suffixes.
      // So we need to propagate the refresh on
      // the sub-suffixes if any.
      if (recursive && (node instanceof SuffixNode)) {
      if (recursive && node instanceof SuffixNode) {
        Enumeration<?> e = node.children();
        while (e.hasMoreElements()) {
          BasicNode child = (BasicNode)e.nextElement();
@@ -824,7 +719,7 @@
   * root node.
   * @param node the node where the refresh must stop.
   */
  void stopRefreshNode(BasicNode node) {
  private void stopRefreshNode(BasicNode node) {
    if (node == rootNode) {
      refreshQueue.cancelAll();
    }
@@ -844,11 +739,11 @@
   * Call startRefreshNode() on each referral node accessible from parentNode.
   * @param parentNode the parent node.
   */
  void startRefreshReferralNodes(BasicNode parentNode) {
  private void startRefreshReferralNodes(BasicNode parentNode) {
    Enumeration<?> e = parentNode.children();
    while (e.hasMoreElements()) {
      BasicNode child = (BasicNode)e.nextElement();
      if ((child.getReferral() != null) || (child.getRemoteUrl() != null)) {
      if (child.getReferral() != null || child.getRemoteUrl() != null) {
        startRefreshNode(child, null, true);
      }
      else {
@@ -866,10 +761,10 @@
   * @param parentNode the parent node.
   * @param keepSuffixes whether the suffixes should be kept or not.
   */
  void removeAllChildNodes(BasicNode parentNode, boolean keepSuffixes) {
  private void removeAllChildNodes(BasicNode parentNode, boolean keepSuffixes) {
    for (int i = parentNode.getChildCount() - 1; i >= 0; i--) {
      BasicNode child = (BasicNode)parentNode.getChildAt(i);
      if ((child instanceof SuffixNode) && keepSuffixes) {
      if (child instanceof SuffixNode && keepSuffixes) {
        removeAllChildNodes(child, true);
        child.setRefreshNeededOnExpansion(true);
      }
@@ -885,6 +780,7 @@
   * if it needs it (to search the children for instance).
   * @param event the tree expansion event.
   */
  @Override
  public void treeExpanded(TreeExpansionEvent event) {
    if (!automaticallyExpandedNode)
    {
@@ -904,6 +800,7 @@
   * tasks on it are canceled.
   * @param event the tree collapse event.
   */
  @Override
  public void treeCollapsed(TreeExpansionEvent event) {
    Object node = event.getPath().getLastPathComponent();
    if (!(node instanceof RootNode)) {
@@ -1054,32 +951,19 @@
   * @throws NamingException if there is an error retrieving the connection.
   * @return the LDAP connection to reading the base entry of a node.
   */
  InitialLdapContext findConnectionForLocalEntry(BasicNode node,
      boolean isConfigurationNode)
  throws NamingException {
    InitialLdapContext result;
  private InitialLdapContext findConnectionForLocalEntry(BasicNode node,
      boolean isConfigurationNode) throws NamingException
  {
    if (node == rootNode) {
      result = ctxConfiguration;
      return ctxConfiguration;
    }
    else  {
      BasicNode parent = (BasicNode)node.getParent();
      if ((parent != null) && (parent != rootNode))
      {
        result = findConnectionForDisplayedEntry(parent, isConfigurationNode);
      }
      else
      {
        if (isConfigurationNode)
        {
          result = ctxConfiguration;
        }
        else
        {
          result = ctxUserData;
        }
      }
    final BasicNode parent = (BasicNode) node.getParent();
    if (parent != null && parent != rootNode)
    {
      return findConnectionForDisplayedEntry(parent, isConfigurationNode);
    }
    return result;
    return isConfigurationNode ? ctxConfiguration : ctxUserData;
  }
  /**
@@ -1090,7 +974,6 @@
   */
  public boolean isConfigurationNode(BasicNode node)
  {
    boolean isConfigurationNode = false;
    if (node instanceof SuffixNode)
    {
      String dn = node.getDN();
@@ -1103,12 +986,12 @@
          Utilities.areDnsEqual(dn, ConfigConstants.DN_BACKUP_ROOT) ||
          Utilities.areDnsEqual(dn, ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT))
      {
        isConfigurationNode = true;
        return true;
      }
    }
    else if (node instanceof RootNode)
    {
      isConfigurationNode = true;
      return true;
    }
    else
    {
@@ -1116,7 +999,7 @@
      return isConfigurationNode(parentNode);
    }
    return isConfigurationNode;
    return false;
  }
  /**
@@ -1140,18 +1023,13 @@
   * @return the LDAP connection to search the displayed entry.
   * @throws NamingException if there is an error retrieving the connection.
   */
  InitialLdapContext findConnectionForDisplayedEntry(BasicNode node,
      boolean isConfigurationNode)
  throws NamingException {
    InitialLdapContext result;
    if (followReferrals && (node.getRemoteUrl() != null))
  private InitialLdapContext findConnectionForDisplayedEntry(BasicNode node,
      boolean isConfigurationNode) throws NamingException {
    if (followReferrals && node.getRemoteUrl() != null)
    {
      result = connectionPool.getConnection(node.getRemoteUrl());
      return connectionPool.getConnection(node.getRemoteUrl());
    }
    else {
      result = findConnectionForLocalEntry(node, isConfigurationNode);
    }
    return result;
    return findConnectionForLocalEntry(node, isConfigurationNode);
  }
@@ -1162,8 +1040,7 @@
   * @param ctx the connection to be released.
   */
  void releaseLDAPConnection(InitialLdapContext ctx) {
    if ((ctx != this.ctxConfiguration) &&
        (ctx != this.ctxUserData))
    if (ctx != this.ctxConfiguration && ctx != this.ctxUserData)
    {
      // Thus it comes from the connection pool
      connectionPool.releaseConnection(ctx);
@@ -1177,20 +1054,16 @@
   * @return the local entry URL for a given node.
   */
  LDAPURL findUrlForLocalEntry(BasicNode node) {
    LDAPURL result;
    if (node == rootNode) {
      result = LDAPConnectionPool.makeLDAPUrl(ctxConfiguration, "");
      return LDAPConnectionPool.makeLDAPUrl(ctxConfiguration, "");
    }
    else {
      BasicNode parent = (BasicNode)node.getParent();
      if (parent != null) {
        LDAPURL parentUrl = findUrlForDisplayedEntry(parent);
        result = LDAPConnectionPool.makeLDAPUrl(parentUrl, node.getDN());
      } else {
        result = LDAPConnectionPool.makeLDAPUrl(ctxConfiguration, node.getDN());
      }
    final BasicNode parent = (BasicNode) node.getParent();
    if (parent != null)
    {
      final LDAPURL parentUrl = findUrlForDisplayedEntry(parent);
      return LDAPConnectionPool.makeLDAPUrl(parentUrl, node.getDN());
    }
    return result;
    return LDAPConnectionPool.makeLDAPUrl(ctxConfiguration, node.getDN());
  }
@@ -1199,15 +1072,12 @@
   * @param node the node.
   * @return the displayed entry URL for a given node.
   */
  LDAPURL findUrlForDisplayedEntry(BasicNode node) {
    LDAPURL result;
    if (followReferrals && (node.getRemoteUrl() != null)) {
      result = node.getRemoteUrl();
  private LDAPURL findUrlForDisplayedEntry(BasicNode node)
  {
    if (followReferrals && node.getRemoteUrl() != null) {
      return node.getRemoteUrl();
    }
    else {
      result = findUrlForLocalEntry(node);
    }
    return result;
    return findUrlForLocalEntry(node);
  }
@@ -1221,15 +1091,10 @@
   * @return the DN to use for searching children of a given node.
   */
  String findBaseDNForChildEntries(BasicNode node) {
    String result;
    if (followReferrals && (node.getRemoteUrl() != null)) {
      result = node.getRemoteUrl().getRawBaseDN();
    if (followReferrals && node.getRemoteUrl() != null) {
      return node.getRemoteUrl().getRawBaseDN();
    }
    else {
      result = node.getDN();
    }
    return result;
    return node.getDN();
  }
@@ -1240,24 +1105,20 @@
   * @return <CODE>true</CODE> if the node displays a remote entry and
   * <CODE>false</CODE> otherwise.
   */
  boolean isDisplayedEntryRemote(BasicNode node) {
    boolean result = false;
  private boolean isDisplayedEntryRemote(BasicNode node) {
    if (followReferrals) {
      if (node == rootNode) {
        result = false;
        return false;
      }
      else if (node.getRemoteUrl() != null) {
        result = true;
      if (node.getRemoteUrl() != null) {
        return true;
      }
      else {
        BasicNode parent = (BasicNode)node.getParent();
        if (parent != null) {
          result = isDisplayedEntryRemote(parent);
        }
      final BasicNode parent = (BasicNode)node.getParent();
      if (parent != null) {
        return isDisplayedEntryRemote(parent);
      }
    }
    return result;
    return false;
  }
@@ -1275,30 +1136,11 @@
    if ((displayFlags & DISPLAY_ACI_COUNT) != 0) {
      v.add("aci");
    }
    if (!displayAttribute.equals(RDN_ATTRIBUTE)) {
    if (!RDN_ATTRIBUTE.equals(displayAttribute)) {
      v.add(displayAttribute);
    }
    String[] result = new String[v.size()];
    v.toArray(result);
    return result;
  }
  /**
   * Returns the list of attributes for the green search.
   * @return the list of attributes for the green search.
   */
  String[] getAttrsForGreenSearch() {
    if (!displayAttribute.equals(RDN_ATTRIBUTE)) {
      return new String[] {
          "aci",
          displayAttribute};
    } else {
      return new String[] {
          "aci"
      };
    }
    return v.toArray(new String[v.size()]);
  }
  /**
@@ -1306,7 +1148,7 @@
   * @return the list of attributes for the black search.
   */
  String[] getAttrsForBlackSearch() {
    if (!displayAttribute.equals(RDN_ATTRIBUTE)) {
    if (!RDN_ATTRIBUTE.equals(displayAttribute)) {
      return new String[] {
          "objectClass",
          "numsubordinates",
@@ -1339,7 +1181,7 @@
   * Returns the request controls to search user data.
   * @return the request controls to search user data.
   */
  Control[] getRequestControls()
  private Control[] getRequestControls()
  {
    Control ctls[];
    if (followReferrals)
@@ -1378,7 +1220,7 @@
   * Returns the request controls to search configuration data.
   * @return the request controls to search configuration data.
   */
  Control[] getConfigurationRequestControls()
  private Control[] getConfigurationRequestControls()
  {
    return getRequestControls();
  }
@@ -1430,8 +1272,8 @@
      // tree.
      // Except when it's due a to referral resolution: we keep the node
      // in order the user can fix the referral.
      if (isNameNotFoundException(task.getException()) &&
          (oldState != NodeRefresher.State.SOLVING_REFERRAL)) {
      if (isNameNotFoundException(task.getException())
          && oldState != NodeRefresher.State.SOLVING_REFERRAL) {
        removeOneNode(node);
      }
      else {
@@ -1460,8 +1302,8 @@
        nodeChanged = updateNodeRendering(node, task.getDisplayedEntry());
      }
    }
    else if ((newState == NodeRefresher.State.CANCELLED) &&
        (newState == NodeRefresher.State.INTERRUPTED)) {
    else if (newState == NodeRefresher.State.CANCELLED
        && newState == NodeRefresher.State.INTERRUPTED) {
      // Let's collapse task.getNode()
      tree.collapsePath(new TreePath(treeModel.getPathToRoot(node)));
@@ -1470,8 +1312,8 @@
    }
    else {
      if ((oldState != NodeRefresher.State.SEARCHING_CHILDREN) &&
          (newState == NodeRefresher.State.SEARCHING_CHILDREN)) {
      if (oldState != NodeRefresher.State.SEARCHING_CHILDREN
          && newState == NodeRefresher.State.SEARCHING_CHILDREN) {
        // The children search is going to start
        if (canDoDifferentialUpdate(task)) {
          Enumeration<?> e = node.children();
@@ -1538,11 +1380,9 @@
        }
      }
      if (newState == NodeRefresher.State.FINISHED) {
        if (node.getError() != null) {
          node.setError(null);
          nodeChanged = updateNodeRendering(node, task.getDisplayedEntry());
        }
      if (newState == NodeRefresher.State.FINISHED && node.getError() != null) {
        node.setError(null);
        nodeChanged = updateNodeRendering(node, task.getDisplayedEntry());
      }
    }
@@ -1551,10 +1391,9 @@
      treeModel.nodeChanged(task.getNode());
    }
    if (node.isLeaf() && (node.getChildCount() >= 1)) {
    if (node.isLeaf() && node.getChildCount() >= 1) {
      throw new RuntimeException("Inconsistent node: " + node.getDN());
    }
  }
@@ -1570,8 +1409,8 @@
      final NodeRefresher.State oldState,
      final NodeRefresher.State newState)
  throws InterruptedException {
    Runnable r = new Runnable() {
      @Override
      public void run() {
        try {
          refreshTaskDidProgress(task, oldState, newState);
@@ -1635,13 +1474,13 @@
        child = new BasicNode(entry.getName());
        parent.insert(child, index);
        updateNodeRendering(child, entry);
        insertIndex.add(new Integer(index));
        insertIndex.add(Integer.valueOf(index));
//      System.out.println("Inserted " + child.getDN() + " at " + index);
      }
      else { // Else we update the existing one
        child = (BasicNode)parent.getChildAt(index);
        if (updateNodeRendering(child, entry)) {
          changedIndex.add(new Integer(index));
          changedIndex.add(Integer.valueOf(index));
        }
        // The node is no longer obsolete
        child.setObsolete(false);
@@ -1649,24 +1488,13 @@
      // NUMSUBORDINATE HACK
      // Let's see if child has subordinates or not.
      // Thanks to slapd, we cannot always trust the
      // numSubOrdinates attribute. If the child entry's DN
      // is found in the hacker's list, then we ignore
      // Thanks to slapd, we cannot always trust the numSubOrdinates attribute.
      // If the child entry's DN is found in the hacker's list, then we ignore
      // the numSubordinate attribute... :((
      boolean hasNoSubOrdinates;
      if (!child.hasSubOrdinates() && dontTrust) {
        LDAPURL childUrl = findUrlForDisplayedEntry(child);
        if (numSubordinateHacker.contains(childUrl)) {
          // The numSubOrdinates we have is unreliable.
          // child may potentially have subordinates.
          hasNoSubOrdinates = false;
//        System.out.println("numSubordinates of " + childUrl +
//        " is not reliable");
        }
        else {
          // We can trust this 0 value
          hasNoSubOrdinates = true;
        }
        hasNoSubOrdinates = !numSubordinateHacker.contains(childUrl);
      }
      else {
        hasNoSubOrdinates = !child.hasSubOrdinates();
@@ -1675,7 +1503,7 @@
      // Propagate the refresh
      // Note: logically we should unconditionaly call:
      // Note: logically we should unconditionally call:
      //  startRefreshNode(child, false, true);
      //
      // However doing that saturates refreshQueue
@@ -1691,9 +1519,9 @@
      // node had children (in the tree).  In this case
      // we force the refresh. See bug 5015115
      //
      if (!hasNoSubOrdinates ||
          (child.getReferral() != null) ||
          (child.getChildCount() > 0)) {
      if (!hasNoSubOrdinates
          || child.getReferral() != null
          || child.getChildCount() > 0) {
        startRefreshNode(child, entry, true);
      }
    }
@@ -1711,16 +1539,14 @@
  /**
   * Tells wheter a differential update can be made in the provided task.
   * Tells whether a differential update can be made in the provided task.
   * @param task the task.
   * @return <CODE>true</CODE> if a differential update can be made and
   * <CODE>false</CODE> otherwise.
   */
  private boolean canDoDifferentialUpdate(NodeRefresher task) {
    return (
        (task.getNode().getChildCount() >= 1) &&
        (task.getNode().getNumSubOrdinates() <= 100)
    );
    return task.getNode().getChildCount() >= 1
        && task.getNode().getNumSubOrdinates() <= 100;
  }
@@ -1736,82 +1562,25 @@
    if (entry != null) {
      // Get the numsubordinates
      node.setNumSubOrdinates(getNumSubOrdinates(entry));
      if (node.getNumSubOrdinates() > 0)
      {
        node.setHasSubOrdinates(true);
      }
      else
      {
        // Calculate based also in the hasSubordinates attribute
        node.setHasSubOrdinates(getHasSubOrdinates(entry));
      }
      node.setHasSubOrdinates(
          node.getNumSubOrdinates() > 0 || getHasSubOrdinates(entry));
      node.setReferral(getReferral(entry));
      Set<String> ocValues = ConnectionUtils.getValues(entry, "objectClass");
      if (ocValues != null) {
        String[] array = new String[ocValues.size()];
        ocValues.toArray(array);
        node.setObjectClassValues(array);
        node.setObjectClassValues(ocValues.toArray(new String[ocValues.size()]));
      }
    }
    // Get the aci count
    int aciCount;
    if (((displayFlags & DISPLAY_ACI_COUNT) != 0) && (entry != null)) {
      Set<String> aciValues = ConnectionUtils.getValues(entry, "aci");
      if (aciValues != null) {
        aciCount = aciValues.size();
      }
      else {
        aciCount = 0;
      }
    }
    else {
      aciCount = 0;
    }
    // Select the icon according the objectClass,...
    int modifiers = 0;
    if (node.isLeaf() && !node.hasSubOrdinates()) {
      modifiers |= IconPool.MODIFIER_LEAF;
    }
    if (node.getReferral() != null) {
      modifiers |= IconPool.MODIFIER_REFERRAL;
    }
    if (node.getError() != null) {
      if (node.getError().getException() != null)
      {
        LOG.log(Level.SEVERE, "node has error: "+node.getError().getException(),
            node.getError().getException());
      }
      modifiers |= IconPool.MODIFIER_ERROR;
    }
    SortedSet<String> objectClasses = new TreeSet<String>();
    if (entry != null) {
      Set<String> ocs = ConnectionUtils.getValues(entry, "objectClass");
      if (ocs != null)
      {
        objectClasses.addAll(ocs);
      }
    }
    Icon newIcon;
    if (node instanceof SuffixNode)
    {
      newIcon = iconPool.getSuffixIcon();
    }
    else
    {
      newIcon = iconPool.getIcon(objectClasses, modifiers);
    }
    int aciCount = getAciCount(entry);
    Icon newIcon = getNewIcon(node, entry);
    // Construct the icon text according the dn, the aci count...
    StringBuilder sb2 = new StringBuilder();
    if (aciCount >= 1) {
      sb2.append(String.valueOf(aciCount));
      if (aciCount == 1) {
        sb2.append(" aci");
      }
      else {
        sb2.append(" acis");
      sb2.append(aciCount);
      sb2.append(" aci");
      if (aciCount != 1) {
        sb2.append("s");
      }
    }
@@ -1822,8 +1591,7 @@
      }
    } else {
      boolean useRdn = true;
      if (!displayAttribute.equals(RDN_ATTRIBUTE) &&
          (entry != null)) {
      if (!RDN_ATTRIBUTE.equals(displayAttribute) && entry != null) {
        String value = ConnectionUtils.getFirstValue(entry,displayAttribute);
        if (value != null) {
          if (showAttributeName) {
@@ -1833,9 +1601,10 @@
          useRdn = false;
        }
      }
      if (useRdn) {
        String rdn;
        if (followReferrals && (node.getRemoteUrl() != null)) {
        if (followReferrals && node.getRemoteUrl() != null) {
          if (showAttributeName) {
            rdn = node.getRemoteRDNWithAttributeName();
          } else {
@@ -1866,21 +1635,66 @@
    }
    // Determine if the rendering needs to be updated
    boolean changed = (
        (node.getIcon() != newIcon) ||
        (node.getDisplayName() != newDisplayName) ||
        (node.getFontStyle() != newStyle)
    );
    boolean changed =
        node.getIcon() != newIcon
        || node.getDisplayName() != newDisplayName
        || node.getFontStyle() != newStyle;
    if (changed) {
      node.setIcon(newIcon);
      node.setDisplayName(newDisplayName);
      node.setFontStyle(newStyle);
    }
    return changed;
  }
  private int getAciCount(SearchResult entry) throws NamingException
  {
    if ((displayFlags & DISPLAY_ACI_COUNT) != 0 && entry != null) {
      Set<String> aciValues = ConnectionUtils.getValues(entry, "aci");
      if (aciValues != null) {
        return aciValues.size();
      }
    }
    return 0;
  }
  private Icon getNewIcon(BasicNode node, SearchResult entry)
      throws NamingException
  {
    // Select the icon according the objectClass,...
    int modifiers = 0;
    if (node.isLeaf() && !node.hasSubOrdinates()) {
      modifiers |= IconPool.MODIFIER_LEAF;
    }
    if (node.getReferral() != null) {
      modifiers |= IconPool.MODIFIER_REFERRAL;
    }
    if (node.getError() != null) {
      final Exception ex = node.getError().getException();
      if (ex != null)
      {
        LOG.log(Level.SEVERE, "node has error: " + ex, ex);
      }
      modifiers |= IconPool.MODIFIER_ERROR;
    }
    SortedSet<String> objectClasses = new TreeSet<String>();
    if (entry != null) {
      Set<String> ocs = ConnectionUtils.getValues(entry, "objectClass");
      if (ocs != null)
      {
        objectClasses.addAll(ocs);
      }
    }
    if (node instanceof SuffixNode)
    {
      return iconPool.getSuffixIcon();
    }
    return iconPool.getIcon(objectClasses, modifiers);
  }
  /**
   * Find a child node matching a given DN.
@@ -1895,14 +1709,13 @@
  public int findChildNode(BasicNode parent, String childDn) {
    int childCount = parent.getChildCount();
    int i = 0;
    while ((i < childCount) &&
        !childDn.equals(((BasicNode)parent.getChildAt(i)).getDN())) {
    while (i < childCount
        && !childDn.equals(((BasicNode)parent.getChildAt(i)).getDN())) {
      i++;
    }
    if (i >= childCount) { // Not found
      i = -(childCount + 1);
    }
    return i;
  }
@@ -1936,10 +1749,10 @@
      newSize = newSize - 1;
    }
    if (newSize != queueTotalSize) {
      if ((queueTotalSize == 0) && (newSize >= 1)) {
      if (queueTotalSize == 0 && newSize >= 1) {
        fireEvent(BrowserEvent.Type.UPDATE_START);
      }
      else if ((queueTotalSize >= 1) && (newSize == 0)) {
      else if (queueTotalSize >= 1 && newSize == 0) {
        fireEvent(BrowserEvent.Type.UPDATE_END);
      }
      queueTotalSize = newSize;
@@ -1985,46 +1798,44 @@
   * @throws IllegalArgumentException if a node with the given dn exists but
   * is not a suffix node.
   */
  SuffixNode findSuffixNode(String suffixDn, SuffixNode suffixNode)
  throws IllegalArgumentException
  private SuffixNode findSuffixNode(String suffixDn, SuffixNode suffixNode)
      throws IllegalArgumentException
  {
    SuffixNode result;
    if (Utilities.areDnsEqual(suffixNode.getDN(), suffixDn)) {
      result = suffixNode;
    }
    else {
      int childCount = suffixNode.getChildCount();
      if (childCount == 0) {
        result = null;
      }
      else {
        BasicNode child;
        int i = 0;
        boolean found = false;
        do {
          child = (BasicNode)suffixNode.getChildAt(i) ;
          if (Utilities.areDnsEqual(child.getDN(), suffixDn)) {
            found = true;
          }
          i++;
        }
        while ((i < childCount) && !found);
        if (!found) {
          result = null;
        }
        else if (child instanceof SuffixNode) {
          result = (SuffixNode)child;
        }
        else {
          // A node matches suffixDn however it's not a suffix node.
          // There's a bug in the caller.
          throw new IllegalArgumentException(suffixDn +" is not a suffix node");
        }
      }
      return suffixNode;
    }
    return result;
    int childCount = suffixNode.getChildCount();
    if (childCount == 0)
    {
      return null;
    }
    BasicNode child;
    int i = 0;
    boolean found = false;
    do
    {
      child = (BasicNode) suffixNode.getChildAt(i);
      if (Utilities.areDnsEqual(child.getDN(), suffixDn))
      {
        found = true;
      }
      i++;
    }
    while (i < childCount && !found);
    if (!found)
    {
      return null;
    }
    if (child instanceof SuffixNode)
    {
      return (SuffixNode) child;
    }
    // A node matches suffixDn however it's not a suffix node.
    // There's a bug in the caller.
    throw new IllegalArgumentException(suffixDn + " is not a suffix node");
  }
@@ -2036,15 +1847,7 @@
   * NameNotFoundException.
   */
  private boolean isNameNotFoundException(Object x) {
    boolean result;
    if ((x != null) && (x instanceof NameNotFoundException))
    {
      result = true;
    }
    else {
      result = false;
    }
    return result;
    return x instanceof NameNotFoundException;
  }
@@ -2054,28 +1857,12 @@
   * If numsubordinates is not present, returns 0.
   * @param entry the entry to analyze.
   * @throws NamingException if an error occurs.
   * @return the value of the numsubordinate attribute.  0 if the attribute
   * @return the value of the numsubordinates attribute.  0 if the attribute
   * could not be found.
   */
  public static int getNumSubOrdinates(SearchResult entry)
  throws NamingException
  private static int getNumSubOrdinates(SearchResult entry) throws NamingException
  {
    int result;
    String v = ConnectionUtils.getFirstValue(entry, "numsubordinates");
    if (v == null) {
      result = 0;
    }
    else {
      try {
        result = Integer.parseInt(v);
      }
      catch(NumberFormatException x) {
        result = 0;
      }
    }
    return result;
    return toInt(ConnectionUtils.getFirstValue(entry, "numsubordinates"));
  }
  /**
@@ -2090,49 +1877,46 @@
  public static boolean getHasSubOrdinates(SearchResult entry)
  throws NamingException
  {
    boolean result;
    String v = ConnectionUtils.getFirstValue(entry, "hassubordinates");
    if (v == null) {
      result = getNumSubOrdinates(entry) > 0;
    if (v != null) {
      return "true".equalsIgnoreCase(v);
    }
    else {
      result = "true".equalsIgnoreCase(v);
    }
    return result;
    return getNumSubOrdinates(entry) > 0;
  }
  /**
   * Get the value of the numsubordinates attribute.
   * If numsubordinates is not present, returns 0.
   * @param entry the entry to analyze.
   * @return the value of the numsubordinate attribute.  0 if the attribute
   * @return the value of the numsubordinates attribute.  0 if the attribute
   * could not be found.
   */
  public static int getNumSubOrdinates(CustomSearchResult entry)
  private static int getNumSubOrdinates(CustomSearchResult entry)
  {
    int result;
    List<Object> vs = entry.getAttributeValues("numsubordinates");
    String v = null;
    if (vs != null && !vs.isEmpty())
    {
      v = vs.get(0).toString();
    }
    if (v == null) {
      result = 0;
    }
    else {
      try {
        result = Integer.parseInt(v);
      }
      catch(NumberFormatException x) {
        result = 0;
      }
    }
    return toInt(v);
  }
    return result;
  private static int toInt(String v)
  {
    if (v == null)
    {
      return 0;
    }
    try
    {
      return Integer.parseInt(v);
    }
    catch (NumberFormatException x)
    {
      return 0;
    }
  }
  /**
@@ -2145,22 +1929,17 @@
   */
  public static boolean getHasSubOrdinates(CustomSearchResult entry)
  {
    boolean result;
    List<Object> vs = entry.getAttributeValues("hassubordinates");
    String v = null;
    if (vs != null && !vs.isEmpty())
    {
      v = vs.get(0).toString();
    }
    if (v == null) {
      result = getNumSubOrdinates(entry) > 0;
    if (v != null)
    {
      return "true".equalsIgnoreCase(v);
    }
    else {
      result = "true".equalsIgnoreCase(v);
    }
    return result;
    return getNumSubOrdinates(entry) > 0;
  }
@@ -2180,7 +1959,7 @@
    {
      for (String value : values)
      {
        boolean isReferral = value.equalsIgnoreCase("referral");
        boolean isReferral = "referral".equalsIgnoreCase(value);
        if (isReferral)
        {
          Set<String> refValues = ConnectionUtils.getValues(entry, "ref");
@@ -2231,7 +2010,7 @@
   * @param v the Collection of Integer objects.
   * @return an array of int from a Collection of Integer objects.
   */
  static int[] intArrayFromCollection(Collection<Integer> v) {
  private static int[] intArrayFromCollection(Collection<Integer> v) {
    int[] result = new int[v.size()];
    int i = 0;
    for (Integer value : v)
@@ -2242,29 +2021,6 @@
    return result;
  }
  /**
   * Returns an array of SearchResult from a Collection of SearchResult objects.
   * @param v the Collection of SearchResult objects.
   * @return an array of SearchResult from a Collection of SearchResult objects.
   */
  static SearchResult[] entryArrayFromCollection(Collection<SearchResult> v) {
    SearchResult[] result = new SearchResult[v.size()];
    v.toArray(result);
    return result;
  }
  /**
   * Returns an array of BasicNode from a Collection of BasicNode objects.
   * @param v the Collection of BasicNode objects.
   * @return an array of BasicNode from a Collection of BasicNode objects.
   */
  static BasicNode[] nodeArrayFromCollection(Collection<BasicNode> v) {
    BasicNode[] result = new BasicNode[v.size()];
    v.toArray(result);
    return result;
  }
  /**
   * For debugging purpose: allows to switch easily
@@ -2273,8 +2029,7 @@
   * @param r the runnable to be invoked.
   * @throws InterruptedException if there is an error invoking SwingUtilities.
   */
  static void swingInvoke(Runnable r)
  throws InterruptedException {
  private static void swingInvoke(Runnable r) throws InterruptedException {
    try {
      SwingUtilities.invokeAndWait(r);
    }
@@ -2289,23 +2044,23 @@
  /**
   * The default implementaion of the BrowserNodeInfo interface.
   * The default implementation of the BrowserNodeInfo interface.
   */
  class BrowserNodeInfoImpl implements BrowserNodeInfo
  private class BrowserNodeInfoImpl implements BrowserNodeInfo
  {
    BasicNode node;
    LDAPURL url;
    boolean isRemote;
    boolean isSuffix;
    boolean isRootNode;
    String[] referral;
    int numSubOrdinates;
    boolean hasSubOrdinates;
    int errorType;
    Exception errorException;
    Object errorArg;
    String[] objectClassValues;
    String toString;
    private BasicNode node;
    private LDAPURL url;
    private boolean isRemote;
    private boolean isSuffix;
    private boolean isRootNode;
    private String[] referral;
    private int numSubOrdinates;
    private boolean hasSubOrdinates;
    private int errorType;
    private Exception errorException;
    private Object errorArg;
    private String[] objectClassValues;
    private String toString;
    /**
     * The constructor of this object.
@@ -2353,6 +2108,7 @@
     * Returns the node associated with this object.
     * @return  the node associated with this object.
     */
    @Override
    public BasicNode getNode() {
      return node;
    }
@@ -2361,6 +2117,7 @@
     * Returns the LDAP URL associated with this object.
     * @return the LDAP URL associated with this object.
     */
    @Override
    public LDAPURL getURL() {
      return url;
    }
@@ -2370,6 +2127,7 @@
     * @return <CODE>true</CODE> if this is a root node and <CODE>false</CODE>
     * otherwise.
     */
    @Override
    public boolean isRootNode() {
      return isRootNode;
    }
@@ -2379,6 +2137,7 @@
     * @return <CODE>true</CODE> if this is a suffix node and <CODE>false</CODE>
     * otherwise.
     */
    @Override
    public boolean isSuffix() {
      return isSuffix;
    }
@@ -2388,6 +2147,7 @@
     * @return <CODE>true</CODE> if this is a remote node and <CODE>false</CODE>
     * otherwise.
     */
    @Override
    public boolean isRemote() {
      return isRemote;
    }
@@ -2396,6 +2156,7 @@
     * Returns the list of referral associated with this node.
     * @return the list of referral associated with this node.
     */
    @Override
    public String[] getReferral() {
      return referral;
    }
@@ -2406,6 +2167,7 @@
     * @return the number of subordinates of the entry associated with this
     * node.
     */
    @Override
    public int getNumSubOrdinates() {
      return numSubOrdinates;
    }
@@ -2415,6 +2177,7 @@
     * @return {@code true} if the entry has subordinates and {@code false}
     * otherwise.
     */
    @Override
    public boolean hasSubOrdinates() {
      return hasSubOrdinates;
    }
@@ -2425,6 +2188,7 @@
     * @return the error type associated we got when refreshing the node.
     * <CODE>null</CODE> if no error was found.
     */
    @Override
    public int getErrorType() {
      return errorType;
    }
@@ -2435,6 +2199,7 @@
     * @return the exception associated we got when refreshing the node.
     * <CODE>null</CODE> if no exception was found.
     */
    @Override
    public Exception getErrorException() {
      return errorException;
    }
@@ -2445,6 +2210,7 @@
     * @return the error argument associated we got when refreshing the node.
     * <CODE>null</CODE> if no error argument was found.
     */
    @Override
    public Object getErrorArg() {
      return errorArg;
    }
@@ -2453,6 +2219,7 @@
     * Return the tree path associated with the node in the tree.
     * @return the tree path associated with the node in the tree.
     */
    @Override
    public TreePath getTreePath() {
      return new TreePath(treeModel.getPathToRoot(node));
    }
@@ -2461,6 +2228,7 @@
     * Returns the object class values of the entry associated with the node.
     * @return the object class values of the entry associated with the node.
     */
    @Override
    public String[] getObjectClassValues() {
      return objectClassValues;
    }
@@ -2469,6 +2237,7 @@
     * Returns a String representation of the object.
     * @return a String representation of the object.
     */
    @Override
    public String toString() {
      return toString;
    }
@@ -2479,12 +2248,12 @@
     * @return <CODE>true</CODE> if the node info represents the same node as
     * this and <CODE>false</CODE> otherwise.
     */
    @Override
    public boolean representsSameNode(BrowserNodeInfo node) {
      boolean representsSameNode = false;
      if (node != null) {
        representsSameNode = node.getNode() == node;
        return node.getNode() == node;
      }
      return representsSameNode;
      return false;
    }
  }