Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(348)

Unified Diff: third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerState.java

Issue 1162033004: Pull cacheinvalidations code directory into chromium repo. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerState.java
diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerState.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerState.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b0bf52d6dcec455f8722726ac02f33feae74561
--- /dev/null
+++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerState.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2011 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.ipc.invalidation.external.client.contrib;
+
+import com.google.ipc.invalidation.external.client.types.ObjectId;
+import com.google.ipc.invalidation.ticl.ProtoWrapperConverter;
+import com.google.ipc.invalidation.ticl.TiclExponentialBackoffDelayGenerator;
+import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol;
+import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState;
+import com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState;
+import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP;
+import com.google.ipc.invalidation.util.Bytes;
+import com.google.ipc.invalidation.util.Marshallable;
+import com.google.ipc.invalidation.util.TypedUtil;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.Set;
+import java.util.UUID;
+
+
+/**
+ * Encapsulates state to simplify persistence and tracking of changes. Internally maintains an
+ * {@link #isDirty} bit. Call {@link #resetIsDirty} to indicate that changes have been persisted.
+ *
+ * <p>Notes on the {@link #desiredRegistrations} (DR) and {@link #delayGenerators} (DG) collections:
+ * When the client application registers for an object, it is immediately added to DR. Similarly,
+ * an object is removed from DR when the application unregisters. If a registration failure is
+ * reported, the object is removed from DR if it exists and a delay generator is added to DG if one
+ * does not already exist. (In the face of a failure, we assume that the registration is not desired
+ * by the application unless/until the application retries.) When there is a successful
+ * registration, the corresponding DG entry is removed. There are two independent collections rather
+ * than one since we may be applying exponential backoff for an object when it is not in DR, and we
+ * may have no reason to delay operations against an object in DR as well.
+ *
+ * <p>By removing objects from the {@link #desiredRegistrations} collection on failures, we are
+ * essentially assuming that the client application doesn't care about the registration until we're
+ * told otherwise -- by a subsequent call to register or unregister.
+ *
+ */
+final class AndroidListenerState
+ implements Marshallable<AndroidListenerProtocol.AndroidListenerState> {
+
+ /**
+ * Exponential backoff delay generators used to determine delay before registration retries.
+ * There is a delay generator for every failing object.
+ */
+ private final Map<ObjectId, TiclExponentialBackoffDelayGenerator> delayGenerators =
+ new HashMap<ObjectId, TiclExponentialBackoffDelayGenerator>();
+
+ /** The set of registrations for which the client wants to be registered. */
+ private final Set<ObjectId> desiredRegistrations;
+
+ /** Random generator used for all delay generators. */
+ private final Random random = new Random();
+
+ /** Initial maximum retry delay for exponential backoff. */
+ private final int initialMaxDelayMs;
+
+ /** Maximum delay factor for exponential backoff (relative to {@link #initialMaxDelayMs}). */
+ private final int maxDelayFactor;
+
+ /** Sequence number for alarm manager request codes. */
+ private int requestCodeSeqNum;
+
+ /**
+ * Dirty flag. {@code true} whenever changes are made, reset to false when {@link #resetIsDirty}
+ * is called. State initialized from a proto is assumed to be initially clean.
+ */
+ private boolean isDirty;
+
+ /**
+ * The identifier for the current client. The ID is randomly generated and is used to ensure that
+ * messages are not handled by the wrong client instance.
+ */
+ private final Bytes clientId;
+
+ /** Initializes state for a new client. */
+ AndroidListenerState(int initialMaxDelayMs, int maxDelayFactor) {
+ desiredRegistrations = new HashSet<ObjectId>();
+ clientId = createGloballyUniqueClientId();
+ // Assigning a client ID dirties the state because calling the constructor twice produces
+ // different results.
+ isDirty = true;
+ requestCodeSeqNum = 0;
+ this.initialMaxDelayMs = initialMaxDelayMs;
+ this.maxDelayFactor = maxDelayFactor;
+ }
+
+ /** Initializes state from proto. */
+ AndroidListenerState(int initialMaxDelayMs, int maxDelayFactor,
+ AndroidListenerProtocol.AndroidListenerState state) {
+ desiredRegistrations = new HashSet<ObjectId>();
+ for (ObjectIdP objectIdProto : state.getRegistration()) {
+ desiredRegistrations.add(ProtoWrapperConverter.convertFromObjectIdProto(objectIdProto));
+ }
+ for (RetryRegistrationState retryState : state.getRetryRegistrationState()) {
+ ObjectIdP objectIdP = retryState.getNullableObjectId();
+ if (objectIdP == null) {
+ continue;
+ }
+ ObjectId objectId = ProtoWrapperConverter.convertFromObjectIdProto(objectIdP);
+ delayGenerators.put(objectId, new TiclExponentialBackoffDelayGenerator(random,
+ initialMaxDelayMs, maxDelayFactor, retryState.getExponentialBackoffState()));
+ }
+ clientId = state.getClientId();
+ requestCodeSeqNum = state.getRequestCodeSeqNum();
+ isDirty = false;
+ this.initialMaxDelayMs = initialMaxDelayMs;
+ this.maxDelayFactor = maxDelayFactor;
+ }
+
+ /** Increments and returns sequence number for alarm manager request codes. */
+ int getNextRequestCode() {
+ isDirty = true;
+ return ++requestCodeSeqNum;
+ }
+
+ /**
+ * See specs for {@link TiclExponentialBackoffDelayGenerator#getNextDelay}. Gets next delay for
+ * the given {@code objectId}. If a delay generator does not yet exist for the object, one is
+ * created.
+ */
+ int getNextDelay(ObjectId objectId) {
+ TiclExponentialBackoffDelayGenerator delayGenerator =
+ delayGenerators.get(objectId);
+ if (delayGenerator == null) {
+ delayGenerator = new TiclExponentialBackoffDelayGenerator(random, initialMaxDelayMs,
+ maxDelayFactor);
+ delayGenerators.put(objectId, delayGenerator);
+ }
+ // Requesting a delay from a delay generator modifies its internal state.
+ isDirty = true;
+ return delayGenerator.getNextDelay();
+ }
+
+ /** Inform that there has been a successful registration for an object. */
+ void informRegistrationSuccess(ObjectId objectId) {
+ // Since registration was successful, we can remove exponential backoff (if any) for the given
+ // object.
+ resetDelayGeneratorFor(objectId);
+ }
+
+ /**
+ * Inform that there has been a registration failure.
+ *
+ * <p>Remove the object from the desired registrations collection whenever there's a failure. We
+ * don't care if the op that failed was actually an unregister because we never suppress an
+ * unregister request (even if the object is not in the collection). See
+ * {@link AndroidListener#issueRegistration}.
+ */
+ public void informRegistrationFailure(ObjectId objectId, boolean isTransient) {
+ removeDesiredRegistration(objectId);
+ if (!isTransient) {
+ // There should be no retries for the object, so remove any backoff state associated with it.
+ resetDelayGeneratorFor(objectId);
+ }
+ }
+
+ /**
+ * If there is a backoff delay generator for the given object, removes it and sets dirty flag.
+ */
+ private void resetDelayGeneratorFor(ObjectId objectId) {
+ if (TypedUtil.remove(delayGenerators, objectId) != null) {
+ isDirty = true;
+ }
+ }
+
+ /** Adds the given registration. Returns {@code true} if it was not already tracked. */
+ boolean addDesiredRegistration(ObjectId objectId) {
+ if (desiredRegistrations.add(objectId)) {
+ isDirty = true;
+ return true;
+ }
+ return false;
+ }
+
+ /** Removes the given registration. Returns {@code true} if it was actually tracked. */
+ boolean removeDesiredRegistration(ObjectId objectId) {
+ if (desiredRegistrations.remove(objectId)) {
+ isDirty = true;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Resets the {@link #isDirty} flag to {@code false}. Call after marshalling and persisting state.
+ */
+ void resetIsDirty() {
+ isDirty = false;
+ }
+
+ @Override
+ public AndroidListenerProtocol.AndroidListenerState marshal() {
+ return AndroidListenerProtos.newAndroidListenerState(clientId, requestCodeSeqNum,
+ delayGenerators, desiredRegistrations);
+ }
+
+ /**
+ * Gets the identifier for the current client. Used to determine if registrations commands are
+ * relevant to this instance.
+ */
+ Bytes getClientId() {
+ return clientId;
+ }
+
+ /** Returns {@code true} iff registration is desired for the given object. */
+ boolean containsDesiredRegistration(ObjectId objectId) {
+ return TypedUtil.contains(desiredRegistrations, objectId);
+ }
+
+ /**
+ * Returns {@code true} if changes have been made since the last successful call to
+ * {@link #resetIsDirty}.
+ */
+ boolean getIsDirty() {
+ return isDirty;
+ }
+
+ @Override
+ public int hashCode() {
+ // Since the client ID is globally unique, it's sufficient as a hashCode.
+ return clientId.hashCode();
+ }
+
+ /**
+ * Overridden for tests which compare listener states to verify that they have been correctly
+ * (un)marshalled. We implement equals rather than exposing private data.
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+
+ if (!(object instanceof AndroidListenerState)) {
+ return false;
+ }
+
+ AndroidListenerState that = (AndroidListenerState) object;
+
+ return (this.isDirty == that.isDirty)
+ && (this.requestCodeSeqNum == that.requestCodeSeqNum)
+ && (this.desiredRegistrations.size() == that.desiredRegistrations.size())
+ && (this.desiredRegistrations.containsAll(that.desiredRegistrations))
+ && TypedUtil.<Bytes>equals(this.clientId, that.clientId)
+ && equals(this.delayGenerators, that.delayGenerators);
+ }
+
+ /** Compares the contents of two {@link #delayGenerators} maps. */
+ private static boolean equals(Map<ObjectId, TiclExponentialBackoffDelayGenerator> x,
+ Map<ObjectId, TiclExponentialBackoffDelayGenerator> y) {
+ if (x.size() != y.size()) {
+ return false;
+ }
+ for (Entry<ObjectId, TiclExponentialBackoffDelayGenerator> xEntry : x.entrySet()) {
+ TiclExponentialBackoffDelayGenerator yGenerator = y.get(xEntry.getKey());
+ if ((yGenerator == null) || !TypedUtil.<ExponentialBackoffState>equals(
+ xEntry.getValue().marshal(), yGenerator.marshal())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.ROOT, "AndroidListenerState[%s]: isDirty = %b, "
+ + "desiredRegistrations.size() = %d, delayGenerators.size() = %d, requestCodeSeqNum = %d",
+ clientId, isDirty, desiredRegistrations.size(), delayGenerators.size(), requestCodeSeqNum);
+ }
+
+ /**
+ * Constructs a new globally unique ID for the client. Can be used to determine if commands
+ * originated from this instance of the listener.
+ */
+ private static Bytes createGloballyUniqueClientId() {
+ UUID guid = UUID.randomUUID();
+ byte[] bytes = new byte[16];
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ buffer.putLong(guid.getLeastSignificantBits());
+ buffer.putLong(guid.getMostSignificantBits());
+ return new Bytes(bytes);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698