/*
* Copyright 2016 Realm 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 io.realm.internal;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* An ObserverPairList holds a list of ObserverPairs. An {@link ObserverPair} is pair containing an observer and a
* listener. The observer is the object to react to the changes through the listener. The observer is saved as a weak
* reference in the pair to control the life cycle of the listener. When the observer gets GCed, the corresponding pair
* will be removed from the list. So DO NOT keep a strong reference to the observer in the subclass of listener since it
* will cause leaks!
* <p>
* This class is not thread safe and it is not supposed to be.
*
* @param <T> the type of {@link ObserverPair}.
*/
public class ObserverPairList<T extends ObserverPairList.ObserverPair> {
/**
* @param <T> the type of observer.
* @param <S> the type of listener.
*/
public abstract static class ObserverPair<T, S> {
final WeakReference<T> observerRef;
protected final S listener;
// Should only be set by the outer class. To marked it as removed in case it is removed in foreach callback.
boolean removed = false;
ObserverPair(T observer, S listener) {
this.listener = listener;
this.observerRef = new WeakReference<T>(observer);
}
// The two pairs will be treated as the same only when the observers are the same and the listeners are equal.
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof ObserverPair) {
ObserverPair anotherPair = (ObserverPair) obj;
return listener.equals(anotherPair.listener) &&
observerRef.get() == anotherPair.observerRef.get();
}
return false;
}
@Override
public int hashCode() {
T observer = observerRef.get();
int result = 17;
result = 31 * result + ((observer != null) ? observer.hashCode() : 0);
result = 31 * result + ((listener != null) ? listener.hashCode() : 0);
return result;
}
}
/**
* Callback passed to the {@link #foreach(Callback)} call.
*
* @param <T> type of ObserverPair.
*/
public interface Callback<T extends ObserverPair> {
void onCalled(T pair, Object observer);
}
private List<T> pairs = new CopyOnWriteArrayList<T>();
// In case the clear() called during the foreach loop.
private boolean cleared = false;
/**
* Iterate every valid pair in the list and call the callback on it. The pair with GCed observer will be removed and
* callback won't be executed. Before executing the callback, a strong reference to the observer will be kept and
* passed to the callback in case the observer gets GCed before callback returns.
*
* @param callback to be executed on the pair.
*/
public void foreach(Callback<T> callback) {
for (T pair : pairs) {
if (cleared) {
break;
} else {
Object observer = pair.observerRef.get();
if (observer == null) {
pairs.remove(pair);
} else if (!pair.removed) {
callback.onCalled(pair, observer);
}
}
}
}
public boolean isEmpty() {
return pairs.isEmpty();
}
public void clear() {
cleared = true;
pairs.clear();
}
public void add(T pair) {
if (!pairs.contains(pair)) {
pairs.add(pair);
pair.removed = false;
}
if (cleared) {
cleared = false;
}
}
public <S, U> void remove(S observer, U listener) {
for (T pair : pairs) {
if (observer == pair.observerRef.get() && listener.equals(pair.listener)) {
pair.removed = true;
pairs.remove(pair);
break;
}
}
}
void removeByObserver(Object observer) {
for (T pair : pairs) {
Object object = pair.observerRef.get();
if (object == null || object == observer) {
pair.removed = true;
pairs.remove(pair);
}
}
}
public int size() {
return pairs.size();
}
}