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

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

Go to download

This is the official Firebase Admin Java SDK. Build extraordinary native JVM apps in minutes with Firebase. The Firebase platform can power your app’s backend, user authentication, static hosting, and more.

There is a newer version: 9.3.0
Show newest version
/*
 * 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