/** * Copyright (c) 2016-present, RxJava Contributors. * * 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.reactivex.disposables; import java.util.*; import io.reactivex.annotations.NonNull; import io.reactivex.exceptions.*; import io.reactivex.internal.disposables.DisposableContainer; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.util.*; /** * A disposable container that can hold onto multiple other disposables and * offers O(1) add and removal complexity. */ public final class CompositeDisposable implements Disposable, DisposableContainer { OpenHashSet<Disposable> resources; volatile boolean disposed; /** * Creates an empty CompositeDisposable. */ public CompositeDisposable() { } /** * Creates a CompositeDisposables with the given array of initial elements. * @param resources the array of Disposables to start with */ public CompositeDisposable(@NonNull Disposable... resources) { ObjectHelper.requireNonNull(resources, "resources is null"); this.resources = new OpenHashSet<Disposable>(resources.length + 1); for (Disposable d : resources) { ObjectHelper.requireNonNull(d, "Disposable item is null"); this.resources.add(d); } } /** * Creates a CompositeDisposables with the given Iterable sequence of initial elements. * @param resources the Iterable sequence of Disposables to start with */ public CompositeDisposable(@NonNull Iterable<? extends Disposable> resources) { ObjectHelper.requireNonNull(resources, "resources is null"); this.resources = new OpenHashSet<Disposable>(); for (Disposable d : resources) { ObjectHelper.requireNonNull(d, "Disposable item is null"); this.resources.add(d); } } @Override public void dispose() { if (disposed) { return; } OpenHashSet<Disposable> set; synchronized (this) { if (disposed) { return; } disposed = true; set = resources; resources = null; } dispose(set); } @Override public boolean isDisposed() { return disposed; } @Override public boolean add(@NonNull Disposable d) { ObjectHelper.requireNonNull(d, "d is null"); if (!disposed) { synchronized (this) { if (!disposed) { OpenHashSet<Disposable> set = resources; if (set == null) { set = new OpenHashSet<Disposable>(); resources = set; } set.add(d); return true; } } } d.dispose(); return false; } /** * Atomically adds the given array of Disposables to the container or * disposes them all if the container has been disposed. * @param ds the array of Disposables * @return true if the operation was successful, false if the container has been disposed */ public boolean addAll(@NonNull Disposable... ds) { ObjectHelper.requireNonNull(ds, "ds is null"); if (!disposed) { synchronized (this) { if (!disposed) { OpenHashSet<Disposable> set = resources; if (set == null) { set = new OpenHashSet<Disposable>(ds.length + 1); resources = set; } for (Disposable d : ds) { ObjectHelper.requireNonNull(d, "d is null"); set.add(d); } return true; } } } for (Disposable d : ds) { d.dispose(); } return false; } @Override public boolean remove(@NonNull Disposable d) { if (delete(d)) { d.dispose(); return true; } return false; } @Override public boolean delete(@NonNull Disposable d) { ObjectHelper.requireNonNull(d, "Disposable item is null"); if (disposed) { return false; } synchronized (this) { if (disposed) { return false; } OpenHashSet<Disposable> set = resources; if (set == null || !set.remove(d)) { return false; } } return true; } /** * Atomically clears the container, then disposes all the previously contained Disposables. */ public void clear() { if (disposed) { return; } OpenHashSet<Disposable> set; synchronized (this) { if (disposed) { return; } set = resources; resources = null; } dispose(set); } /** * Returns the number of currently held Disposables. * @return the number of currently held Disposables */ public int size() { if (disposed) { return 0; } synchronized (this) { if (disposed) { return 0; } OpenHashSet<Disposable> set = resources; return set != null ? set.size() : 0; } } /** * Dispose the contents of the OpenHashSet by suppressing non-fatal * Throwables till the end. * @param set the OpenHashSet to dispose elements of */ void dispose(OpenHashSet<Disposable> set) { if (set == null) { return; } List<Throwable> errors = null; Object[] array = set.keys(); for (Object o : array) { if (o instanceof Disposable) { try { ((Disposable) o).dispose(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); if (errors == null) { errors = new ArrayList<Throwable>(); } errors.add(ex); } } } if (errors != null) { if (errors.size() == 1) { throw ExceptionHelper.wrapOrThrow(errors.get(0)); } throw new CompositeException(errors); } } }