/**
   * Register the given connector.
   *
   * <p>The lookup method {@link #getConnector(String)} only returns registered connectors.
   *
   * @param connector The connector to register.
   */
  public void registerConnector(ClientConnector connector) {
    boolean wasUnregistered = unregisteredConnectors.remove(connector);

    String connectorId = connector.getConnectorId();
    ClientConnector previouslyRegistered = connectorIdToConnector.get(connectorId);
    if (previouslyRegistered == null) {
      connectorIdToConnector.put(connectorId, connector);
      uninitializedConnectors.add(connector);
      if (getLogger().isLoggable(Level.FINE)) {
        getLogger()
            .log(
                Level.FINE,
                "Registered {0} ({1})",
                new Object[] {connector.getClass().getSimpleName(), connectorId});
      }
    } else if (previouslyRegistered != connector) {
      throw new RuntimeException("A connector with id " + connectorId + " is already registered!");
    } else if (!wasUnregistered) {
      getLogger()
          .log(
              Level.WARNING,
              "An already registered connector was registered again: {0} ({1})",
              new Object[] {connector.getClass().getSimpleName(), connectorId});
    }
    dirtyConnectors.add(connector);
  }
 /**
  * Returns {@link #getConnectorString(ClientConnector)} for the connector and its parent (if it
  * has a parent).
  *
  * @param connector The connector
  * @return A string describing the connector and its parent
  */
 private String getConnectorAndParentInfo(ClientConnector connector) {
   String message = getConnectorString(connector);
   if (connector.getParent() != null) {
     message += " (parent: " + getConnectorString(connector.getParent()) + ")";
   }
   return message;
 }
  /**
   * Cleans the connector map from all connectors that are no longer attached to the application.
   * This should only be called by the framework.
   */
  public void cleanConnectorMap() {
    if (!unregisteredConnectors.isEmpty()) {
      removeUnregisteredConnectors();
    }

    // Do this expensive check only with assertions enabled
    assert isHierarchyComplete()
        : "The connector hierarchy is corrupted. "
            + "Check for missing calls to super.setParent(), super.attach() and super.detach() "
            + "and that all custom component containers call child.setParent(this) when a child is added and child.setParent(null) when the child is no longer used. "
            + "See previous log messages for details.";

    // remove detached components from paintableIdMap so they
    // can be GC'ed
    Iterator<ClientConnector> iterator = connectorIdToConnector.values().iterator();
    GlobalResourceHandler globalResourceHandler = uI.getSession().getGlobalResourceHandler(false);
    while (iterator.hasNext()) {
      ClientConnector connector = iterator.next();
      assert connector != null;
      if (connector.getUI() != uI) {
        // If connector is no longer part of this uI,
        // remove it from the map. If it is re-attached to the
        // application at some point it will be re-added through
        // registerConnector(connector)

        // This code should never be called as cleanup should take place
        // in detach()

        getLogger()
            .log(
                Level.WARNING,
                "cleanConnectorMap unregistered connector {0}. This should have been done when the connector was detached.",
                getConnectorAndParentInfo(connector));

        if (globalResourceHandler != null) {
          globalResourceHandler.unregisterConnector(connector);
        }
        uninitializedConnectors.remove(connector);
        diffStates.remove(connector);
        iterator.remove();
      } else if (!uninitializedConnectors.contains(connector)
          && !LegacyCommunicationManager.isConnectorVisibleToClient(connector)) {
        uninitializedConnectors.add(connector);
        diffStates.remove(connector);
        if (getLogger().isLoggable(Level.FINE)) {
          getLogger()
              .log(
                  Level.FINE,
                  "cleanConnectorMap removed state for {0} as it is not visible",
                  getConnectorAndParentInfo(connector));
        }
      }
    }

    cleanStreamVariables();
  }
  /**
   * Returns a string with the connector name and id. Useful mostly for debugging and logging.
   *
   * @param connector The connector
   * @return A string that describes the connector
   */
  private String getConnectorString(ClientConnector connector) {
    if (connector == null) {
      return "(null)";
    }

    String connectorId;
    try {
      connectorId = connector.getConnectorId();
    } catch (RuntimeException e) {
      // This happens if the connector is not attached to the application.
      // SHOULD not happen in this case but theoretically can.
      connectorId = "@" + Integer.toHexString(connector.hashCode());
    }
    return connector.getClass().getName() + "(" + connectorId + ")";
  }
  private void removeUnregisteredConnectors() {
    GlobalResourceHandler globalResourceHandler = uI.getSession().getGlobalResourceHandler(false);

    for (ClientConnector connector : unregisteredConnectors) {
      ClientConnector removedConnector = connectorIdToConnector.remove(connector.getConnectorId());
      assert removedConnector == connector;

      if (globalResourceHandler != null) {
        globalResourceHandler.unregisterConnector(connector);
      }
      uninitializedConnectors.remove(connector);
      diffStates.remove(connector);
    }
    unregisteredConnectors.clear();
  }
  private boolean isHierarchyComplete() {
    boolean noErrors = true;

    Set<ClientConnector> danglingConnectors =
        new HashSet<ClientConnector>(connectorIdToConnector.values());

    LinkedList<ClientConnector> stack = new LinkedList<ClientConnector>();
    stack.add(uI);
    while (!stack.isEmpty()) {
      ClientConnector connector = stack.pop();
      danglingConnectors.remove(connector);

      Iterable<ClientConnector> children =
          AbstractClientConnector.getAllChildrenIterable(connector);
      for (ClientConnector child : children) {
        stack.add(child);

        if (child.getParent() != connector) {
          noErrors = false;
          getLogger()
              .log(
                  Level.WARNING,
                  "{0} claims that {1} is its child, but the child claims {2} is its parent.",
                  new Object[] {
                    getConnectorString(connector),
                    getConnectorString(child),
                    getConnectorString(child.getParent())
                  });
        }
      }
    }

    for (ClientConnector dangling : danglingConnectors) {
      noErrors = false;
      getLogger()
          .log(
              Level.WARNING,
              "{0} claims that {1} is its parent, but the parent does not acknowledge the parenthood.",
              new Object[] {
                getConnectorString(dangling), getConnectorString(dangling.getParent())
              });
    }

    return noErrors;
  }
  /**
   * Unregister the given connector.
   *
   * <p>The lookup method {@link #getConnector(String)} only returns registered connectors.
   *
   * @param connector The connector to unregister
   */
  public void unregisterConnector(ClientConnector connector) {
    String connectorId = connector.getConnectorId();
    if (!connectorIdToConnector.containsKey(connectorId)) {
      getLogger()
          .log(
              Level.WARNING,
              "Tried to unregister {0} ({1}) which is not registered",
              new Object[] {connector.getClass().getSimpleName(), connectorId});
      return;
    }
    if (connectorIdToConnector.get(connectorId) != connector) {
      throw new RuntimeException(
          "The given connector with id "
              + connectorId
              + " is not the one that was registered for that id");
    }

    Set<String> unregisteredConnectorIds = syncIdToUnregisteredConnectorIds.get(currentSyncId);
    if (unregisteredConnectorIds == null) {
      unregisteredConnectorIds = new HashSet<String>();
      syncIdToUnregisteredConnectorIds.put(currentSyncId, unregisteredConnectorIds);
    }
    unregisteredConnectorIds.add(connectorId);

    dirtyConnectors.remove(connector);
    if (unregisteredConnectors.add(connector)) {
      if (getLogger().isLoggable(Level.FINE)) {
        getLogger()
            .log(
                Level.FINE,
                "Unregistered {0} ({1})",
                new Object[] {connector.getClass().getSimpleName(), connectorId});
      }
    } else {
      getLogger()
          .log(
              Level.WARNING,
              "Unregistered {0} ({1}) that was already unregistered.",
              new Object[] {connector.getClass().getSimpleName(), connectorId});
    }
  }
 public void setDiffState(ClientConnector connector, JSONObject diffState) {
   assert getConnector(connector.getConnectorId()) == connector;
   diffStates.put(connector, diffState);
 }
 public JSONObject getDiffState(ClientConnector connector) {
   assert getConnector(connector.getConnectorId()) == connector;
   return diffStates.get(connector);
 }
 /**
  * Checks whether the given connector has already been initialized in the browser. The given
  * connector should be registered with this connector tracker.
  *
  * @param connector the client connector to check
  * @return <code>true</code> if the initial state has previously been sent to the browser, <code>
  *     false</code> if the client-side doesn't already know anything about the connector.
  */
 public boolean isClientSideInitialized(ClientConnector connector) {
   assert connectorIdToConnector.get(connector.getConnectorId()) == connector
       : "Connector should be registered with this ConnectorTracker";
   return !uninitializedConnectors.contains(connector);
 }