All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.firebase.database.core.ZombieEventManager Maven / Gradle / Ivy

/*
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.firebase.database.core;

import com.google.firebase.database.annotations.NotNull;
import com.google.firebase.database.core.view.QuerySpec;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

/**
 * {@link ZombieEventManager} records event registrations made from Query so that when they are
 * unregistered, we immediately "zombie" them so that all further events are suppressed. This stops
 * events that would normally be fired if events were already queued in the {@link Repo} or even in
 * the Android message queue.
 */
public class ZombieEventManager implements EventRegistrationZombieListener {

  private static ZombieEventManager defaultInstance = new ZombieEventManager();
  // This hashmap stores the original eventregistrations sent to the repo.
  // These are the registration instances that will get called with update events.
  // Since EventRegistration overrides equals and hashcode, we create temporary instances
  // to use as lookup keys.
  // Package private for testing purposes only
  final HashMap> globalEventRegistrations =
      new HashMap<>();

  private ZombieEventManager() {}

  @NotNull
  public static ZombieEventManager getInstance() {
    return defaultInstance;
  }

  public void recordEventRegistration(EventRegistration registration) {
    synchronized (globalEventRegistrations) {
      List registrationList = globalEventRegistrations.get(registration);
      if (registrationList == null) {
        registrationList = new ArrayList<>();
        globalEventRegistrations.put(registration, registrationList);
      }
      registrationList.add(registration);
      // We record non default listeners twice, because when the default listener is zombied
      // (removed) the repo will remove that listener on all specific queries as well.
      // We need to match that behavior here and zombie all the relevant registrations.
      if (!registration.getQuerySpec().isDefault()) {
        EventRegistration defaultRegistration =
            registration.clone(QuerySpec.defaultQueryAtPath(registration.getQuerySpec().getPath()));
        registrationList = globalEventRegistrations.get(defaultRegistration);
        if (registrationList == null) {
          registrationList = new ArrayList<>();
          globalEventRegistrations.put(defaultRegistration, registrationList);
        }
        registrationList.add(registration);
      }

      registration.setIsUserInitiated(true);
      registration.setOnZombied(this);
    }
  }

  private void unRecordEventRegistration(EventRegistration zombiedRegistration) {
    synchronized (globalEventRegistrations) {
      boolean found = false;

      List registrationList = globalEventRegistrations.get(zombiedRegistration);
      if (registrationList != null) {
        for (int i = 0; i < registrationList.size(); i++) {
          if (registrationList.get(i) == zombiedRegistration) {
            found = true;
            registrationList.remove(i);
            break;
          }
        }
        if (registrationList.isEmpty()) {
          globalEventRegistrations.remove(zombiedRegistration);
        }
      }
      assert (found || !zombiedRegistration.isUserInitiated());

      // If the registration was recorded twice, we need to remove its second
      // record.
      if (!zombiedRegistration.getQuerySpec().isDefault()) {
        EventRegistration defaultRegistration =
            zombiedRegistration.clone(
                QuerySpec.defaultQueryAtPath(zombiedRegistration.getQuerySpec().getPath()));

        registrationList = globalEventRegistrations.get(defaultRegistration);
        if (registrationList != null) {
          for (int i = 0; i < registrationList.size(); i++) {
            if (registrationList.get(i) == zombiedRegistration) {
              registrationList.remove(i);
              break;
            }
          }
          if (registrationList.isEmpty()) {
            globalEventRegistrations.remove(defaultRegistration);
          }
        }
      }
    }
  }

  public void zombifyForRemove(EventRegistration registration) {
    synchronized (globalEventRegistrations) {
      List registrationList = globalEventRegistrations.get(registration);
      if (registrationList != null && !registrationList.isEmpty()) {
        if (registration.getQuerySpec().isDefault()) {
          // The behavior here has to match the behavior of SyncPoint
          // .removeEventRegistration.
          // If the query is default, it remove a single instance of the registration
          // from each unique query.  So for example, if you had 3 copies registered
          // under default,
          // you would end up with 2 still registered.
          // If you had 1 registration in default and 2 in query a', you'd end up with
          // just
          // a single registration in a'.
          // To implement this, we just store in a hashset queries that we remove so we
          // can still
          // keep a fairly simple structure.
          // Note that we *could* use the same logic for non-default as the list there
          // only has a
          // a single query, but its somewhat wasteful to enumerate the list when we
          // know we will
          // only grab 1.
          HashSet zombiedQueries = new HashSet<>();
          // Walk down the list so that removes do not mess up the enumeration.
          for (int i = registrationList.size() - 1; i >= 0; i--) {
            EventRegistration currentRegistration = registrationList.get(i);
            if (!zombiedQueries.contains(currentRegistration.getQuerySpec())) {
              zombiedQueries.add(currentRegistration.getQuerySpec());
              currentRegistration.zombify();
            }
          }
        } else {
          // Note that this entry cannot already be zombied because we are inside synchronization
          // and any previous calls to zombify would have removed the entry.
          registrationList.get(0).zombify();
        }
      }
    }
  }

  @Override
  public void onZombied(EventRegistration zombiedInstance) {
    unRecordEventRegistration(zombiedInstance);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy