diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58730a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +.idea/ +*.iml +target/ +2017* +.project +.classpath +.settings/ +*.log +bin/ +lib/ +src/main/java/test/ +.vscode/ diff --git a/README.md b/README.md deleted file mode 100644 index 4da6e59..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# java-debug - diff --git a/display_runtime.txt b/display_runtime.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/display_runtime.txt @@ -0,0 +1 @@ +1 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c94c866 --- /dev/null +++ b/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + net.educoder + java-debug + 1.0-SNAPSHOT + + + 17 + 17 + + + + + + org.springframework.boot + spring-boot-starter-web + 2.6.14 + + + + + com.google.code.gson + gson + 2.10 + + + + org.reactivestreams + reactive-streams + 1.0.4 + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + commons-io + commons-io + 2.11.0 + + + + + org + minimao-json + system + 1.0 + ${project.basedir}/lib/minimal-json.jar + + + + org + precompiled + system + 1.0 + ${project.basedir}/lib/precompiled.jar + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.6.14 + + + + repackage + + + net.educoder.DebuggerApplication + + + + + + + diff --git a/src/main/java/com/microsoft/java/debug/core/AsyncJdwpUtils.java b/src/main/java/com/microsoft/java/debug/core/AsyncJdwpUtils.java new file mode 100755 index 0000000..fad2ac2 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/AsyncJdwpUtils.java @@ -0,0 +1,143 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import static java.util.concurrent.CompletableFuture.allOf; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Supplier; + +public class AsyncJdwpUtils { + /** + * Create a the thread pool to process JDWP tasks. + * JDWP tasks are IO-bounded, so use a relatively large thread pool for JDWP tasks. + */ + public static ExecutorService jdwpThreadPool = Executors.newWorkStealingPool(100); + // public static ExecutorService jdwpThreadPool = Executors.newCachedThreadPool(); + + public static CompletableFuture runAsync(List tasks) { + return runAsync(jdwpThreadPool, tasks.toArray(new Runnable[0])); + } + + public static CompletableFuture runAsync(Runnable... tasks) { + return runAsync(jdwpThreadPool, tasks); + } + + public static CompletableFuture runAsync(Executor executor, List tasks) { + return runAsync(executor, tasks.toArray(new Runnable[0])); + } + + public static CompletableFuture runAsync(Executor executor, Runnable... tasks) { + List> promises = new ArrayList<>(); + for (Runnable task : tasks) { + if (task == null) { + continue; + } + + promises.add(CompletableFuture.runAsync(task, executor)); + } + + return CompletableFuture.allOf(promises.toArray(new CompletableFuture[0])); + } + + public static CompletableFuture supplyAsync(Supplier supplier) { + return supplyAsync(jdwpThreadPool, supplier); + } + + public static CompletableFuture supplyAsync(Executor executor, Supplier supplier) { + return CompletableFuture.supplyAsync(supplier, executor); + } + + public static U await(CompletableFuture future) { + try { + return future.join(); + } catch (CompletionException ex) { + if (ex.getCause() instanceof RuntimeException) { + throw (RuntimeException) ex.getCause(); + } + + throw ex; + } + } + + public static List await(CompletableFuture[] futures) { + List results = new ArrayList<>(); + try { + allOf(futures).join(); + for (CompletableFuture future : futures) { + results.add(await(future)); + } + } catch (CompletionException ex) { + if (ex.getCause() instanceof RuntimeException) { + throw (RuntimeException) ex.getCause(); + } + + throw ex; + } + + return results; + } + + public static List await(List> futures) { + return await((CompletableFuture[]) futures.toArray(new CompletableFuture[0])); + } + + public static CompletableFuture> all(CompletableFuture... futures) { + return allOf(futures).thenApply((res) -> { + List results = new ArrayList<>(); + for (CompletableFuture future : futures) { + results.add(future.join()); + } + + return results; + }); + } + + public static CompletableFuture> all(List> futures) { + return allOf(futures.toArray(new CompletableFuture[0])).thenApply((res) -> { + List results = new ArrayList<>(); + for (CompletableFuture future : futures) { + results.add(future.join()); + } + + return results; + }); + } + + public static CompletableFuture> flatAll(CompletableFuture>... futures) { + return allOf(futures).thenApply((res) -> { + List results = new ArrayList<>(); + for (CompletableFuture> future : futures) { + results.addAll(future.join()); + } + + return results; + }); + } + + public static CompletableFuture> flatAll(List>> futures) { + return allOf(futures.toArray(new CompletableFuture[0])).thenApply((res) -> { + List results = new ArrayList<>(); + for (CompletableFuture> future : futures) { + results.addAll(future.join()); + } + + return results; + }); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/Breakpoint.java b/src/main/java/com/microsoft/java/debug/core/Breakpoint.java new file mode 100755 index 0000000..dfdbad4 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/Breakpoint.java @@ -0,0 +1,448 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import com.sun.jdi.AbsentInformationException; +import com.sun.jdi.Location; +import com.sun.jdi.Method; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.event.ClassPrepareEvent; +import com.sun.jdi.request.BreakpointRequest; +import com.sun.jdi.request.ClassPrepareRequest; +import com.sun.jdi.request.EventRequest; + +import io.reactivex.Observable; +import io.reactivex.disposables.Disposable; + +public class Breakpoint implements IBreakpoint { + private VirtualMachine vm = null; + private IEventHub eventHub = null; + private JavaBreakpointLocation sourceLocation = null; + private int hitCount = 0; + private String condition = null; + private String logMessage = null; + private HashMap propertyMap = new HashMap<>(); + + private boolean async = false; + + Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber) { + this(vm, eventHub, className, lineNumber, 0, null); + } + + Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount) { + this(vm, eventHub, className, lineNumber, hitCount, null); + } + + Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, String condition) { + this(vm, eventHub, className, lineNumber, hitCount, condition, null); + } + + Breakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, String condition, String logMessage) { + this.vm = vm; + this.eventHub = eventHub; + String contextClass = className; + String methodName = null; + String methodSignature = null; + if (className != null && className.contains("#")) { + contextClass = className.substring(0, className.indexOf("#")); + String[] methodInfo = className.substring(className.indexOf("#") + 1).split("#"); + methodName = methodInfo[0]; + methodSignature = methodInfo[1]; + } + + this.sourceLocation = new JavaBreakpointLocation(lineNumber, -1); + this.sourceLocation.setClassName(contextClass); + this.sourceLocation.setMethodName(methodName); + this.sourceLocation.setMethodSignature(methodSignature); + this.hitCount = hitCount; + this.condition = condition; + this.logMessage = logMessage; + } + + Breakpoint(VirtualMachine vm, IEventHub eventHub, JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage) { + this.vm = vm; + this.eventHub = eventHub; + this.sourceLocation = sourceLocation; + this.hitCount = hitCount; + this.condition = condition; + this.logMessage = logMessage; + } + + // IDebugResource + private List requests = Collections.synchronizedList(new ArrayList<>()); + private List subscriptions = new ArrayList<>(); + + @Override + public List requests() { + return requests; + } + + @Override + public List subscriptions() { + return subscriptions; + } + + // AutoCloseable + @Override + public void close() throws Exception { + try { + vm.eventRequestManager().deleteEventRequests(requests()); + } catch (VMDisconnectedException ex) { + // ignore since removing breakpoints is meaningless when JVM is terminated. + } + subscriptions().forEach(subscription -> { + subscription.dispose(); + }); + requests.clear(); + subscriptions.clear(); + } + + // IBreakpoint + @Override + public JavaBreakpointLocation sourceLocation() { + return this.sourceLocation; + } + + @Override + public String className() { + return this.sourceLocation.className(); + } + + @Override + public int getLineNumber() { + return this.sourceLocation.lineNumber(); + } + + @Override + public int getColumnNumber() { + return this.sourceLocation.columnNumber(); + } + + @Override + public String getCondition() { + return condition; + } + + @Override + public int hashCode() { + return Objects.hash(sourceLocation); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Breakpoint)) { + return false; + } + Breakpoint other = (Breakpoint) obj; + return Objects.equals(sourceLocation, other.sourceLocation); + } + + @Override + public int getHitCount() { + return hitCount; + } + + @Override + public void setHitCount(int hitCount) { + this.hitCount = hitCount; + + Observable.fromIterable(this.requests()) + .filter(request -> request instanceof BreakpointRequest) + .subscribe(request -> { + request.addCountFilter(hitCount); + request.disable(); + request.enable(); + }); + } + + @Override + public void setCondition(String condition) { + this.condition = condition; + } + + @Override + public void setLogMessage(String logMessage) { + this.logMessage = logMessage; + } + + @Override + public String getLogMessage() { + return this.logMessage; + } + + @Override + public boolean async() { + return this.async; + } + + @Override + public void setAsync(boolean async) { + this.async = async; + } + + @Override + public CompletableFuture install() { + // It's possible that different class loaders create new class with the same name. + // Here to listen to future class prepare events to handle such case. + ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); + classPrepareRequest.addClassFilter(className()); + classPrepareRequest.enable(); + requests.add(classPrepareRequest); + + // Local types also needs to be handled + ClassPrepareRequest localClassPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); + localClassPrepareRequest.addClassFilter(className() + "$*"); + localClassPrepareRequest.enable(); + requests.add(localClassPrepareRequest); + + CompletableFuture future = new CompletableFuture<>(); + + Disposable subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent + && (classPrepareRequest.equals(debugEvent.event.request()) + || localClassPrepareRequest.equals(debugEvent.event.request()))) + .subscribe(debugEvent -> { + ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event; + List newRequests = AsyncJdwpUtils.await( + createBreakpointRequests(event.referenceType(), getLineNumber(), hitCount, false) + ); + requests.addAll(newRequests); + if (!newRequests.isEmpty() && !future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + }); + subscriptions.add(subscription); + + Runnable resolveRequestsFromExistingClasses = () -> { + List refTypes = vm.classesByName(className()); + createBreakpointRequests(refTypes, getLineNumber(), hitCount, true) + .whenComplete((newRequests, ex) -> { + if (ex != null) { + return; + } + + requests.addAll(newRequests); + if (!newRequests.isEmpty() && !future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + }); + }; + + if (async()) { + AsyncJdwpUtils.runAsync(resolveRequestsFromExistingClasses); + } else { + resolveRequestsFromExistingClasses.run(); + } + + return future; + } + + private CompletableFuture> collectLocations(ReferenceType refType, int lineNumber) { + List>> futures = new ArrayList<>(); + Iterator iter = refType.methods().iterator(); + while (iter.hasNext()) { + Method method = iter.next(); + if (async()) { + futures.add(AsyncJdwpUtils.supplyAsync(() -> findLocaitonsOfLine(method, lineNumber))); + } else { + futures.add(CompletableFuture.completedFuture(findLocaitonsOfLine(method, lineNumber))); + } + } + + return AsyncJdwpUtils.flatAll(futures); + } + + private CompletableFuture> collectLocations(List refTypes, int lineNumber, boolean includeNestedTypes) { + List>> futures = new ArrayList<>(); + refTypes.forEach(refType -> { + futures.add(collectLocations(refType, lineNumber, includeNestedTypes)); + }); + + return AsyncJdwpUtils.flatAll(futures); + } + + private CompletableFuture> collectLocations(ReferenceType refType, int lineNumber, boolean includeNestedTypes) { + return collectLocations(refType, lineNumber).thenCompose((newLocations) -> { + if (!newLocations.isEmpty()) { + return CompletableFuture.completedFuture(newLocations); + } else if (includeNestedTypes) { + // ReferenceType.nestedTypes() will invoke vm.allClasses() to list all loaded classes, + // should avoid using nestedTypes for performance. + for (ReferenceType nestedType : refType.nestedTypes()) { + CompletableFuture> nestedLocationsFuture = collectLocations(nestedType, lineNumber); + List nestedLocations = nestedLocationsFuture.join(); + if (!nestedLocations.isEmpty()) { + return CompletableFuture.completedFuture(nestedLocations); + } + } + } + + return CompletableFuture.completedFuture(Collections.emptyList()); + }); + } + + private CompletableFuture> collectLocations(List refTypes, String methodName, String methodSiguature) { + List> futures = new ArrayList<>(); + for (ReferenceType refType : refTypes) { + if (async()) { + futures.add(AsyncJdwpUtils.supplyAsync(() -> findMethodLocaiton(refType, methodName, methodSiguature))); + } else { + futures.add(CompletableFuture.completedFuture(findMethodLocaiton(refType, methodName, methodSiguature))); + } + } + + return AsyncJdwpUtils.all(futures); + } + + private Location findMethodLocaiton(ReferenceType refType, String methodName, String methodSiguature) { + List methods = refType.methods(); + Location location = null; + for (Method method : methods) { + if (!method.isAbstract() && !method.isNative() + && methodName.equals(method.name()) + && (methodSiguature.equals(method.genericSignature()) || methodSiguature.equals(method.signature()))) { + location = method.location(); + break; + } + } + + return location; + } + + private List findLocaitonsOfLine(Method method, int lineNumber) { + try { + return method.locationsOfLine(lineNumber); + } catch (AbsentInformationException e) { + // could be AbsentInformationException or ClassNotPreparedException + // but both are expected so no need to further handle + } + + return Collections.emptyList(); + } + + private CompletableFuture> createBreakpointRequests(ReferenceType refType, int lineNumber, int hitCount, + boolean includeNestedTypes) { + return createBreakpointRequests(Arrays.asList(refType), lineNumber, hitCount, includeNestedTypes); + } + + private CompletableFuture> createBreakpointRequests(List refTypes, int lineNumber, + int hitCount, boolean includeNestedTypes) { + CompletableFuture> locationsFuture; + if (this.sourceLocation.methodName() != null) { + locationsFuture = collectLocations(refTypes, this.sourceLocation.methodName(), this.sourceLocation.methodSignature()); + } else { + locationsFuture = collectLocations(refTypes, lineNumber, includeNestedTypes).thenApply((locations) -> { + if (locations.isEmpty()) { + return locations; + } + + /** + * For a line breakpoint, we default to breaking at the first location + * of the line. If you want to break at other locations on the same line, + * you can add an inline breakpoint based on the locations returned by + * the BreakpointLocation request. + */ + return Arrays.asList(locations.get(0)); + }); + } + + return locationsFuture.thenCompose((locations) -> { + // find out the existing breakpoint locations + List existingLocations = new ArrayList<>(requests.size()); + Observable.fromIterable(requests).filter(request -> request instanceof BreakpointRequest) + .map(request -> ((BreakpointRequest) request).location()).toList().subscribe(list -> { + existingLocations.addAll(list); + }); + + // remove duplicated locations + List newLocations = new ArrayList<>(locations.size()); + Observable.fromIterable(locations).filter(location -> !existingLocations.contains(location)).toList().subscribe(list -> { + newLocations.addAll(list); + }); + + List newRequests = new ArrayList<>(newLocations.size()); + + newLocations.forEach(location -> { + BreakpointRequest request = vm.eventRequestManager().createBreakpointRequest(location); + request.setSuspendPolicy(BreakpointRequest.SUSPEND_EVENT_THREAD); + if (hitCount > 0) { + request.addCountFilter(hitCount); + } + request.putProperty(IBreakpoint.REQUEST_TYPE, computeRequestType()); + newRequests.add(request); + }); + + List> futures = new ArrayList<>(); + for (BreakpointRequest request : newRequests) { + if (async()) { + futures.add(AsyncJdwpUtils.runAsync(() -> { + try { + request.enable(); + } catch (VMDisconnectedException ex) { + // enable breakpoint operation may be executing while JVM is terminating, thus the VMDisconnectedException may be + // possible, in case of VMDisconnectedException, this method will return an empty array which turns out a valid + // response in vscode, causing no error log in trace. + } + })); + } else { + try { + request.enable(); + } catch (VMDisconnectedException ex) { + // enable breakpoint operation may be executing while JVM is terminating, thus the VMDisconnectedException may be + // possible, in case of VMDisconnectedException, this method will return an empty array which turns out a valid + // response in vscode, causing no error log in trace. + } + } + } + + return AsyncJdwpUtils.all(futures).thenApply((res) -> newRequests); + }); + } + + private Object computeRequestType() { + if (this.sourceLocation.methodName() == null) { + return IBreakpoint.REQUEST_TYPE_LINE; + } + + if (this.sourceLocation.methodName().startsWith("lambda$")) { + return IBreakpoint.REQUEST_TYPE_LAMBDA; + } else { + return IBreakpoint.REQUEST_TYPE_METHOD; + } + } + + @Override + public void putProperty(Object key, Object value) { + propertyMap.put(key, value); + } + + @Override + public Object getProperty(Object key) { + return propertyMap.get(key); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/Configuration.java b/src/main/java/com/microsoft/java/debug/core/Configuration.java new file mode 100755 index 0000000..6e6b340 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/Configuration.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core; + +public class Configuration { + public static final String LOGGER_NAME = "java-debug"; + public static final String USAGE_DATA_LOGGER_NAME = "java-debug-usage-data"; + +} diff --git a/src/main/java/com/microsoft/java/debug/core/DebugEvent.java b/src/main/java/com/microsoft/java/debug/core/DebugEvent.java new file mode 100755 index 0000000..859de27 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/DebugEvent.java @@ -0,0 +1,21 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import com.sun.jdi.event.Event; +import com.sun.jdi.event.EventSet; + +public class DebugEvent { + public Event event = null; + public EventSet eventSet = null; + public boolean shouldResume = true; +} diff --git a/src/main/java/com/microsoft/java/debug/core/DebugException.java b/src/main/java/com/microsoft/java/debug/core/DebugException.java new file mode 100755 index 0000000..302b9a4 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/DebugException.java @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +public class DebugException extends Exception { + private static final long serialVersionUID = 1L; + private int errorCode; + + private boolean userError = false; + + public DebugException() { + super(); + } + + public DebugException(String message) { + super(message); + } + + public DebugException(String message, Throwable cause) { + super(message, cause); + } + + public DebugException(Throwable cause) { + super(cause); + } + + public DebugException(String message, int errorCode) { + super(message); + this.errorCode = errorCode; + } + + /** + * Create a debug exception with userError flag. + * @param message the error message + * @param errorCode the error code + * @param userError the boolean value indicating whether this exception is caused by a known user error + */ + public DebugException(String message, int errorCode, boolean userError) { + super(message); + this.errorCode = errorCode; + this.userError = userError; + } + + public DebugException(String message, Throwable cause, int errorCode) { + super(message, cause); + this.errorCode = errorCode; + } + + public DebugException(Throwable cause, int errorCode) { + super(cause); + this.errorCode = errorCode; + } + + public int getErrorCode() { + return this.errorCode; + } + + public void setUserError(boolean userError) { + this.userError = userError; + } + + public boolean isUserError() { + return this.userError; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/DebugSession.java b/src/main/java/com/microsoft/java/debug/core/DebugSession.java new file mode 100755 index 0000000..1c7990f --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/DebugSession.java @@ -0,0 +1,198 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import com.sun.jdi.ObjectCollectedException; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.EventRequestManager; +import com.sun.jdi.request.ExceptionRequest; + +public class DebugSession implements IDebugSession { + private VirtualMachine vm; + private EventHub eventHub = new EventHub(); + + public DebugSession(VirtualMachine virtualMachine) { + vm = virtualMachine; + } + + @Override + public void start() { + boolean supportsVirtualThreads = mayCreateVirtualThreads(); + + // request thread events by default + EventRequest threadStartRequest = vm.eventRequestManager().createThreadStartRequest(); + threadStartRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE); + if (supportsVirtualThreads) { + addPlatformThreadsOnlyFilter(threadStartRequest); + } + threadStartRequest.enable(); + + EventRequest threadDeathRequest = vm.eventRequestManager().createThreadDeathRequest(); + threadDeathRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE); + if (supportsVirtualThreads) { + addPlatformThreadsOnlyFilter(threadDeathRequest); + } + threadDeathRequest.enable(); + + eventHub.start(vm); + } + + private boolean mayCreateVirtualThreads() { + try { + Method method = vm.getClass().getMethod("mayCreateVirtualThreads"); + return (boolean) method.invoke(vm); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + // ignore + } + + return false; + } + + /** + * For thread start and thread death events, restrict the events so they are only sent for platform threads. + */ + private void addPlatformThreadsOnlyFilter(EventRequest threadLifecycleRequest) { + try { + Method method = threadLifecycleRequest.getClass().getMethod("addPlatformThreadsOnlyFilter"); + method.invoke(threadLifecycleRequest); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + // ignore + } + } + + @Override + public void suspend() { + vm.suspend(); + } + + @Override + public void resume() { + /** + * To ensure that all threads are fully resumed when the VM is resumed, make sure the suspend count + * of each thread is no larger than 1. + * Notes: Decrementing the thread' suspend count to 1 is on purpose, because it doesn't break the + * the thread's suspend state, and also make sure the next instruction vm.resume() is able to resume + * all threads fully. + */ + for (ThreadReference tr : DebugUtility.getAllThreadsSafely(this)) { + try { + while (tr.suspendCount() > 1) { + tr.resume(); + } + } catch (ObjectCollectedException ex) { + // Skip it if the thread is garbage collected. + } + } + vm.resume(); + } + + @Override + public void detach() { + vm.dispose(); + } + + @Override + public void terminate() { + if (vm.process() == null || vm.process().isAlive()) { + vm.exit(0); + } + } + + @Override + public IBreakpoint createBreakpoint(JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage) { + return new EvaluatableBreakpoint(vm, this.getEventHub(), sourceLocation, hitCount, condition, logMessage); + } + + @Override + public IBreakpoint createBreakpoint(String className, int lineNumber, int hitCount, String condition, String logMessage) { + return new EvaluatableBreakpoint(vm, this.getEventHub(), className, lineNumber, hitCount, condition, logMessage); + } + + @Override + public IWatchpoint createWatchPoint(String className, String fieldName, String accessType, String condition, int hitCount) { + return new Watchpoint(vm, this.getEventHub(), className, fieldName, accessType, condition, hitCount); + } + + @Override + public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught) { + setExceptionBreakpoints(notifyCaught, notifyUncaught, null, null); + } + + @Override + public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters) { + EventRequestManager manager = vm.eventRequestManager(); + ArrayList legacy = new ArrayList<>(manager.exceptionRequests()); + manager.deleteEventRequests(legacy); + // When no exception breakpoints are requested, no need to create an empty exception request. + if (notifyCaught || notifyUncaught) { + // from: https://www.javatips.net/api/REPLmode-master/src/jm/mode/replmode/REPLRunner.java + // Calling this seems to set something internally to make the + // Eclipse JDI wake up. Without it, an ObjectCollectedException + // is thrown on request.enable(). No idea why this works, + // but at least exception handling has returned. (Suspect that it may + // block until all or at least some threads are available, meaning + // that the app has launched and we have legit objects to talk to). + vm.allThreads(); + // The bug may not have been noticed because the test suite waits for + // a thread to be available, and queries it by calling allThreads(). + // See org.eclipse.debug.jdi.tests.AbstractJDITest for the example. + + // get only the uncaught exceptions + ExceptionRequest request = manager.createExceptionRequest(null, notifyCaught, notifyUncaught); + request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + if (classFilters != null) { + for (String classFilter : classFilters) { + request.addClassFilter(classFilter); + } + } + if (classExclusionFilters != null) { + for (String exclusionFilter : classExclusionFilters) { + request.addClassExclusionFilter(exclusionFilter); + } + } + request.enable(); + } + } + + @Override + public Process process() { + return vm.process(); + } + + @Override + public List getAllThreads() { + return vm.allThreads(); + } + + @Override + public IEventHub getEventHub() { + return eventHub; + } + + @Override + public VirtualMachine getVM() { + return vm; + } + + @Override + public IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, + int hitCount) { + return new MethodBreakpoint(vm, this.getEventHub(), className, functionName, condition, hitCount); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/DebugSettings.java b/src/main/java/com/microsoft/java/debug/core/DebugSettings.java new file mode 100755 index 0000000..f59f100 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/DebugSettings.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2017-2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import com.google.gson.JsonSyntaxException; +import com.google.gson.annotations.SerializedName; +import com.microsoft.java.debug.core.protocol.JsonUtils; +import com.microsoft.java.debug.core.protocol.Requests.ClassFilters; +import com.microsoft.java.debug.core.protocol.Requests.StepFilters; + +public final class DebugSettings { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private static Set listeners = + Collections.newSetFromMap(new ConcurrentHashMap()); + private static DebugSettings current = new DebugSettings(); + + public int maxStringLength = 0; + public int numericPrecision = 0; + public boolean showStaticVariables = false; + public boolean showQualifiedNames = false; + public boolean showHex = false; + public boolean showLogicalStructure = true; + public boolean showToString = true; + public String logLevel; + public String javaHome; + public HotCodeReplace hotCodeReplace = HotCodeReplace.MANUAL; + public StepFilters stepFilters = new StepFilters(); + public ClassFilters exceptionFilters = new ClassFilters(); + public boolean exceptionFiltersUpdated = false; + public int limitOfVariablesPerJdwpRequest = 100; + public int jdwpRequestTimeout = 3000; + public AsyncMode asyncJDWP = AsyncMode.OFF; + + public static DebugSettings getCurrent() { + return current; + } + + /** + * Update current settings with the values in the parameter. + * + * @param jsonSettings + * the new settings represents in json format. + */ + public void updateSettings(String jsonSettings) { + try { + DebugSettings oldSettings = current; + current = JsonUtils.fromJson(jsonSettings, DebugSettings.class); + for (IDebugSettingChangeListener listener : listeners) { + listener.update(oldSettings, current); + } + } catch (JsonSyntaxException ex) { + logger.severe(String.format("Invalid json for debugSettings: %s, %s", jsonSettings, ex.getMessage())); + } + } + + private DebugSettings() { + + } + + public static boolean addDebugSettingChangeListener(IDebugSettingChangeListener listener) { + return listeners.add(listener); + } + + public static boolean removeDebugSettingChangeListener(IDebugSettingChangeListener listener) { + return listeners.remove(listener); + } + + public static enum HotCodeReplace { + @SerializedName("manual") + MANUAL, + @SerializedName("auto") + AUTO, + @SerializedName("never") + NEVER + } + + public static enum AsyncMode { + @SerializedName("auto") + AUTO, + @SerializedName("on") + ON, + @SerializedName("off") + OFF + } + + public static interface IDebugSettingChangeListener { + public void update(DebugSettings oldSettings, DebugSettings newSettings); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/DebugUtility.java b/src/main/java/com/microsoft/java/debug/core/DebugUtility.java new file mode 100755 index 0000000..4a2a49e --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/DebugUtility.java @@ -0,0 +1,796 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectCollectedException; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.VirtualMachineManager; +import com.sun.jdi.connect.AttachingConnector; +import com.sun.jdi.connect.Connector.Argument; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.connect.LaunchingConnector; +import com.sun.jdi.connect.VMStartException; +import com.sun.jdi.event.MethodEntryEvent; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.EventRequestManager; +import com.sun.jdi.request.MethodEntryRequest; +import com.sun.jdi.request.StepRequest; + +public class DebugUtility { + public static final String HOME = "home"; + public static final String OPTIONS = "options"; + public static final String MAIN = "main"; + public static final String SUSPEND = "suspend"; + public static final String QUOTE = "quote"; + public static final String EXEC = "vmexec"; + public static final String CWD = "cwd"; + public static final String ENV = "env"; + public static final String HOSTNAME = "hostname"; + public static final String PORT = "port"; + public static final String TIMEOUT = "timeout"; + + /** + * Launch a debuggee in suspend mode. + * @see #launch(VirtualMachineManager, String, String, String, String, String, String, String[]) + */ + public static IDebugSession launch(VirtualMachineManager vmManager, + String mainClass, + String programArguments, + String vmArguments, + List modulePaths, + List classPaths, + String cwd, + String[] envVars) + throws IOException, IllegalConnectorArgumentsException, VMStartException { + return DebugUtility.launch(vmManager, + mainClass, + programArguments, + vmArguments, + String.join(File.pathSeparator, modulePaths), + String.join(File.pathSeparator, classPaths), + cwd, + envVars); + } + + /** + * Launch a debuggee in suspend mode. + * @see #launch(VirtualMachineManager, String, String, String, String, String, String, String[], String) + */ + public static IDebugSession launch(VirtualMachineManager vmManager, + String mainClass, + String programArguments, + String vmArguments, + List modulePaths, + List classPaths, + String cwd, + String[] envVars, + String javaExec) + throws IOException, IllegalConnectorArgumentsException, VMStartException { + return DebugUtility.launch(vmManager, + mainClass, + programArguments, + vmArguments, + String.join(File.pathSeparator, modulePaths), + String.join(File.pathSeparator, classPaths), + cwd, + envVars, + javaExec); + } + + /** + * Launches a debuggee in suspend mode. + * + * @param vmManager + * the virtual machine manager. + * @param mainClass + * the main class. + * @param programArguments + * the program arguments. + * @param vmArguments + * the vm arguments. + * @param modulePaths + * the module paths. + * @param classPaths + * the class paths. + * @param cwd + * the working directory of the program. + * @param envVars + * array of strings, each element of which has environment variable settings in the format name=value. + * or null if the subprocess should inherit the environment of the current process. + * @return an instance of IDebugSession. + * @throws IOException + * when unable to launch. + * @throws IllegalConnectorArgumentsException + * when one of the arguments is invalid. + * @throws VMStartException + * when the debuggee was successfully launched, but terminated + * with an error before a connection could be established. + */ + public static IDebugSession launch(VirtualMachineManager vmManager, + String mainClass, + String programArguments, + String vmArguments, + String modulePaths, + String classPaths, + String cwd, + String[] envVars) + throws IOException, IllegalConnectorArgumentsException, VMStartException { + return launch(vmManager, mainClass, programArguments, vmArguments, modulePaths, classPaths, cwd, envVars, null); + } + + /** + * Launches a debuggee in suspend mode. + * + * @param vmManager + * the virtual machine manager. + * @param mainClass + * the main class. + * @param programArguments + * the program arguments. + * @param vmArguments + * the vm arguments. + * @param modulePaths + * the module paths. + * @param classPaths + * the class paths. + * @param cwd + * the working directory of the program. + * @param envVars + * array of strings, each element of which has environment variable settings in the format name=value. + * or null if the subprocess should inherit the environment of the current process. + * @param javaExec + * the java executable path. If not defined, then resolve from java home. + * @return an instance of IDebugSession. + * @throws IOException + * when unable to launch. + * @throws IllegalConnectorArgumentsException + * when one of the arguments is invalid. + * @throws VMStartException + * when the debuggee was successfully launched, but terminated + * with an error before a connection could be established. + */ + public static IDebugSession launch(VirtualMachineManager vmManager, + String mainClass, + String programArguments, + String vmArguments, + String modulePaths, + String classPaths, + String cwd, + String[] envVars, + String javaExec) + throws IOException, IllegalConnectorArgumentsException, VMStartException { + List connectors = vmManager.launchingConnectors(); + LaunchingConnector connector = connectors.get(0); + + /** In the sun JDK 10, the first launching connector is com.sun.tools.jdi.RawCommandLineLauncher, which is not the one we want to use. + * Add the logic to filter the right one from LaunchingConnector list. + * This fix is only for the JDI implementation by JDK. Other JDI implementations (such as JDT) doesn't have the impact. + */ + final String SUN_LAUNCHING_CONNECTOR = "com.sun.tools.jdi.SunCommandLineLauncher"; + for (LaunchingConnector con : connectors) { + if (con.getClass().getName().equals(SUN_LAUNCHING_CONNECTOR)) { + connector = con; + break; + } + } + + Map arguments = connector.defaultArguments(); + arguments.get(SUSPEND).setValue("true"); + + String options = ""; + if (StringUtils.isNotBlank(vmArguments)) { + options = vmArguments; + } + if (StringUtils.isNotBlank(modulePaths)) { + options += " --module-path \"" + modulePaths + "\""; + } + if (StringUtils.isNotBlank(classPaths)) { + options += " -cp \"" + classPaths + "\""; + } + arguments.get(OPTIONS).setValue(options); + + // For java 9 project, should specify "-m $MainClass". + String[] mainClasses = mainClass.split("/"); + if (mainClasses.length == 2) { + mainClass = "-m " + mainClass; + } + if (StringUtils.isNotBlank(programArguments)) { + mainClass += " " + programArguments; + } + arguments.get(MAIN).setValue(mainClass); + + if (arguments.get(CWD) != null) { + arguments.get(CWD).setValue(cwd); + } + + if (arguments.get(ENV) != null) { + arguments.get(ENV).setValue(encodeArrayArgument(envVars)); + } + + if (isValidJavaExec(javaExec)) { + String vmExec = new File(javaExec).getName(); + String javaHome = new File(javaExec).getParentFile().getParentFile().getAbsolutePath(); + arguments.get(HOME).setValue(javaHome); + arguments.get(EXEC).setValue(vmExec); + } else if (StringUtils.isNotEmpty(DebugSettings.getCurrent().javaHome)) { + arguments.get(HOME).setValue(DebugSettings.getCurrent().javaHome); + } + + VirtualMachine vm = connector.launch(arguments); + // workaround for JDT bug. + // vm.version() calls org.eclipse.jdi.internal.MirrorImpl#requestVM + // It calls vm.getIDSizes() to read related sizes including ReferenceTypeIdSize, + // which is required to construct requests with null ReferenceType (such as ExceptionRequest) + // Without this line, it throws ObjectCollectedException in ExceptionRequest.enable(). + // See https://github.com/Microsoft/java-debug/issues/23 + vm.version(); + return new DebugSession(vm); + } + + private static boolean isValidJavaExec(String javaExec) { + if (StringUtils.isBlank(javaExec)) { + return false; + } + + File file = new File(javaExec); + if (!file.exists() || !file.isFile()) { + return false; + } + + return Files.isExecutable(file.toPath()) + && Objects.equals(file.getParentFile().getName(), "bin"); + } + + /** + * Attach to an existing debuggee VM. + * @param vmManager + * the virtual machine manager + * @param hostName + * the machine where the debuggee VM is launched on + * @param port + * the debug port that the debuggee VM exposed + * @param attachTimeout + * the timeout when attaching to the debuggee VM + * @return an instance of IDebugSession + * @throws IOException + * when unable to attach. + * @throws IllegalConnectorArgumentsException + * when one of the connector arguments is invalid. + */ + public static IDebugSession attach(VirtualMachineManager vmManager, String hostName, int port, int attachTimeout) + throws IOException, IllegalConnectorArgumentsException { + List connectors = vmManager.attachingConnectors(); + AttachingConnector connector = connectors.get(0); + // in JDK 10, the first AttachingConnector is not the one we want + final String SUN_ATTACH_CONNECTOR = "com.sun.tools.jdi.SocketAttachingConnector"; + for (AttachingConnector con : connectors) { + if (con.getClass().getName().equals(SUN_ATTACH_CONNECTOR)) { + connector = con; + break; + } + } + Map arguments = connector.defaultArguments(); + arguments.get(HOSTNAME).setValue(hostName); + arguments.get(PORT).setValue(String.valueOf(port)); + arguments.get(TIMEOUT).setValue(String.valueOf(attachTimeout)); + return new DebugSession(connector.attach(arguments)); + } + + /** + * Create a step over request on the specified thread. + * @param thread + * the target thread. + * @param stepFilters + * the step filters when stepping. + * @return the new step request. + */ + public static StepRequest createStepOverRequest(ThreadReference thread, String[] stepFilters) { + return createStepOverRequest(thread, null, stepFilters); + } + + /** + * Create a step over request on the specified thread. + * @param thread + * the target thread. + * @param classFilters + * restricts the step event to those matching the given class patterns when stepping. + * @param classExclusionFilters + * restricts the step event to those not matching the given class patterns when stepping. + * @return the new step request. + */ + public static StepRequest createStepOverRequest(ThreadReference thread, String[] classFilters, String[] classExclusionFilters) { + return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER, classFilters, classExclusionFilters); + } + + /** + * Create a step into request on the specified thread. + * @param thread + * the target thread. + * @param stepFilters + * the step filters when stepping. + * @return the new step request. + */ + public static StepRequest createStepIntoRequest(ThreadReference thread, String[] stepFilters) { + return createStepIntoRequest(thread, null, stepFilters); + } + + /** + * Create a step into request on the specified thread. + * @param thread + * the target thread. + * @param classFilters + * restricts the step event to those matching the given class patterns when stepping. + * @param classExclusionFilters + * restricts the step event to those not matching the given class patterns when stepping. + * @return the new step request. + */ + public static StepRequest createStepIntoRequest(ThreadReference thread, String[] classFilters, String[] classExclusionFilters) { + return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO, classFilters, classExclusionFilters); + } + + /** + * Create a step out request on the specified thread. + * @param thread + * the target thread. + * @param stepFilters + * the step filters when stepping. + * @return the new step request. + */ + public static StepRequest createStepOutRequest(ThreadReference thread, String[] stepFilters) { + return createStepOutRequest(thread, null, stepFilters); + } + + /** + * Create a step out request on the specified thread. + * @param thread + * the target thread. + * @param classFilters + * restricts the step event to those matching the given class patterns when stepping. + * @param classExclusionFilters + * restricts the step event to those not matching the given class patterns when stepping. + * @return the new step request. + */ + public static StepRequest createStepOutRequest(ThreadReference thread, String[] classFilters, String[] classExclusionFilters) { + return createStepRequest(thread, StepRequest.STEP_LINE, StepRequest.STEP_OUT, classFilters, classExclusionFilters); + } + + private static StepRequest createStepRequest(ThreadReference thread, int stepSize, int stepDepth, String[] classFilters, String[] classExclusionFilters) { + StepRequest request = thread.virtualMachine().eventRequestManager().createStepRequest(thread, stepSize, stepDepth); + if (classFilters != null) { + for (String classFilter : classFilters) { + request.addClassFilter(classFilter); + } + } + if (classExclusionFilters != null) { + for (String exclusionFilter : classExclusionFilters) { + request.addClassExclusionFilter(exclusionFilter); + } + } + request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + request.addCountFilter(1); + + return request; + } + + /** + * Suspend the main thread when the program enters the main method of the specified main class. + * @param debugSession + * the debug session. + * @param mainClass + * the fully qualified name of the main class. + * @return + * a {@link CompletableFuture} that contains the suspended main thread id. + */ + public static CompletableFuture stopOnEntry(IDebugSession debugSession, String mainClass) { + CompletableFuture future = new CompletableFuture<>(); + + EventRequestManager manager = debugSession.getVM().eventRequestManager(); + MethodEntryRequest request = manager.createMethodEntryRequest(); + request.addClassFilter(mainClass); + request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + + debugSession.getEventHub().events().filter(debugEvent -> { + return debugEvent.event instanceof MethodEntryEvent && request.equals(debugEvent.event.request()); + }).subscribe(debugEvent -> { + Method method = ((MethodEntryEvent) debugEvent.event).method(); + if (method.isPublic() && method.isStatic() && method.name().equals("main") + && method.signature().equals("([Ljava/lang/String;)V")) { + deleteEventRequestSafely(debugSession.getVM().eventRequestManager(), request); + debugEvent.shouldResume = false; + ThreadReference bpThread = ((MethodEntryEvent) debugEvent.event).thread(); + future.complete(bpThread.uniqueID()); + } + }); + request.enable(); + + return future; + } + + /** + * Get the ThreadReference instance by the thread id. + * @param debugSession + * the debug session + * @param threadId + * the thread id + * @return the ThreadReference instance + */ + public static ThreadReference getThread(IDebugSession debugSession, long threadId) { + for (ThreadReference thread : getAllThreadsSafely(debugSession)) { + if (thread.uniqueID() == threadId) { + return thread; + } + } + return null; + } + + /** + * Get the available ThreadReference list in the debug session. + * If the debug session has terminated, return an empty list instead of VMDisconnectedException. + * @param debugSession + * the debug session + * @return the available ThreadReference list + */ + public static List getAllThreadsSafely(IDebugSession debugSession) { + if (debugSession != null) { + try { + return debugSession.getAllThreads(); + } catch (VMDisconnectedException ex) { + // do nothing. + } + } + return new ArrayList<>(); + } + + /** + * Resume the thread the times as it has been suspended. + * + * @param thread + * the thread reference + */ + public static void resumeThread(ThreadReference thread) { + // if thread is not found or is garbage collected, do nothing + if (thread == null) { + return; + } + try { + int suspends = thread.suspendCount(); + for (int i = 0; i < suspends; i++) { + /** + * Invoking this method will decrement the count of pending suspends on this thread. + * If it is decremented to 0, the thread will continue to execute. + */ + thread.resume(); + } + } catch (ObjectCollectedException ex) { + // ObjectCollectionException can be thrown if the thread has already completed (exited) in the VM when calling suspendCount, + // the resume operation to this thread is meanness. + } + } + + public static void resumeThread(ThreadReference thread, int resumeCount) { + if (thread == null) { + return; + } + + try { + for (int i = 0; i < resumeCount; i++) { + /** + * Invoking this method will decrement the count of pending suspends on this thread. + * If it is decremented to 0, the thread will continue to execute. + */ + thread.resume(); + } + } catch (ObjectCollectedException ex) { + // ObjectCollectionException can be thrown if the thread has already completed (exited) in the VM when calling suspendCount, + // the resume operation to this thread is meanness. + } + } + + /** + * Remove the event request from the vm. If the vm has terminated, do nothing. + * @param eventManager + * The event request manager. + * @param request + * The target event request. + */ + public static void deleteEventRequestSafely(EventRequestManager eventManager, EventRequest request) { + try { + eventManager.deleteEventRequest(request); + } catch (VMDisconnectedException ex) { + // ignore. + } + } + + /** + * Remove the event request list from the vm. If the vm has terminated, do nothing. + * @param eventManager + * The event request manager. + * @param requests + * The target event request list. + */ + public static void deleteEventRequestSafely(EventRequestManager eventManager, List requests) { + try { + eventManager.deleteEventRequests(requests); + } catch (VMDisconnectedException ex) { + // ignore. + } + } + + /** + * Encode an string array to a string as the follows. + * + *

source argument: + *

["path=C:\\ProgramFiles\\java\\bin", "JAVA_HOME=C:\\ProgramFiles\\java"]
+ * + *

after encoded: + *

"path%3DC%3A%5CProgramFiles%5Cjava%5Cbin\nJAVA_HOME%3DC%3A%5CProgramFiles%5Cjava"
+ * + * @param argument the string array arguments + * @return the encoded string + */ + public static String encodeArrayArgument(String[] argument) { + if (argument == null) { + return null; + } + + List encodedArgs = new ArrayList<>(); + for (String arg : argument) { + try { + encodedArgs.add(URLEncoder.encode(arg, StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException e) { + // do nothing. + } + } + return String.join("\n", encodedArgs); + } + + /** + * Decode the encoded string to the original string array by the rules defined in encodeArrayArgument. + * + * @param argument the encoded string + * @return the original string array argument + */ + public static String[] decodeArrayArgument(String argument) { + if (argument == null) { + return null; + } + + List result = new ArrayList<>(); + String[] splits = argument.split("\n"); + for (String split : splits) { + try { + result.add(URLDecoder.decode(split, StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException e) { + // do nothing. + } + } + + return result.toArray(new String[0]); + } + + /** + * Parses the given command line into separate arguments that can be passed + * to Runtime.getRuntime().exec(cmdArray). + * + * @param cmdStr command line as a single string. + * @return the individual arguments. + */ + public static List parseArguments(String cmdStr) { + if (cmdStr == null) { + return new ArrayList<>(); + } + + return AdapterUtils.isWindows() ? parseArgumentsWindows(cmdStr) : parseArgumentsNonWindows(cmdStr); + } + + /** + * Parses the given command line into separate arguments for mac/linux platform. + * This piece of code is mainly copied from + * https://github.com/eclipse/eclipse.platform.debug/blob/master/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java#L1374 + * + * @param args + * the command line arguments as a single string. + * @return the individual arguments + */ + private static List parseArgumentsNonWindows(String args) { + // man sh, see topic QUOTING + List result = new ArrayList<>(); + + final int DEFAULT = 0; + final int ARG = 1; + final int IN_DOUBLE_QUOTE = 2; + final int IN_SINGLE_QUOTE = 3; + + int state = DEFAULT; + StringBuilder buf = new StringBuilder(); + int len = args.length(); + for (int i = 0; i < len; i++) { + char ch = args.charAt(i); + if (Character.isWhitespace(ch)) { + if (state == DEFAULT) { + // skip + continue; + } else if (state == ARG) { + state = DEFAULT; + result.add(buf.toString()); + buf.setLength(0); + continue; + } + } + switch (state) { + case DEFAULT: + case ARG: + if (ch == '"') { + state = IN_DOUBLE_QUOTE; + } else if (ch == '\'') { + state = IN_SINGLE_QUOTE; + } else if (ch == '\\' && i + 1 < len) { + state = ARG; + ch = args.charAt(++i); + buf.append(ch); + } else { + state = ARG; + buf.append(ch); + } + break; + + case IN_DOUBLE_QUOTE: + if (ch == '"') { + state = ARG; + } else if (ch == '\\' && i + 1 < len && (args.charAt(i + 1) == '\\' || args.charAt(i + 1) == '"')) { + ch = args.charAt(++i); + buf.append(ch); + } else { + buf.append(ch); + } + break; + + case IN_SINGLE_QUOTE: + if (ch == '\'') { + state = ARG; + } else { + buf.append(ch); + } + break; + + default: + throw new IllegalStateException(); + } + } + if (buf.length() > 0 || state != DEFAULT) { + result.add(buf.toString()); + } + + return result; + } + + /** + * Parses the given command line into separate arguments for windows platform. + * This piece of code is mainly copied from + * https://github.com/eclipse/eclipse.platform.debug/blob/master/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java#L1264 + * + * @param args + * the command line arguments as a single string. + * @return the individual arguments + */ + private static List parseArgumentsWindows(String args) { + // see http://msdn.microsoft.com/en-us/library/a1y7w461.aspx + List result = new ArrayList<>(); + final int DEFAULT = 0; + final int ARG = 1; + final int IN_DOUBLE_QUOTE = 2; + + int state = DEFAULT; + int backslashes = 0; + StringBuilder buf = new StringBuilder(); + int len = args.length(); + for (int i = 0; i < len; i++) { + char ch = args.charAt(i); + if (ch == '\\') { + backslashes++; + continue; + } else if (backslashes != 0) { + if (ch == '"') { + for (; backslashes >= 2; backslashes -= 2) { + buf.append('\\'); + } + if (backslashes == 1) { + if (state == DEFAULT) { + state = ARG; + } + buf.append('"'); + backslashes = 0; + continue; + } // else fall through to switch + } else { + // false alarm, treat passed backslashes literally... + if (state == DEFAULT) { + state = ARG; + } + for (; backslashes > 0; backslashes--) { + buf.append('\\'); + } + // fall through to switch + } + } + if (Character.isWhitespace(ch)) { + if (state == DEFAULT) { + // skip + continue; + } else if (state == ARG) { + state = DEFAULT; + result.add(buf.toString()); + buf.setLength(0); + continue; + } + } + switch (state) { + case DEFAULT: + case ARG: + if (ch == '"') { + state = IN_DOUBLE_QUOTE; + } else { + state = ARG; + buf.append(ch); + } + break; + + case IN_DOUBLE_QUOTE: + if (ch == '"') { + if (i + 1 < len && args.charAt(i + 1) == '"') { + /* Undocumented feature in Windows: + * Two consecutive double quotes inside a double-quoted argument are interpreted as + * a single double quote. + */ + buf.append('"'); + i++; + } else if (buf.length() == 0) { + // empty string on Windows platform. Account for bug in constructor of JDK's java.lang.ProcessImpl. + result.add("\"\""); //$NON-NLS-1$ + state = DEFAULT; + } else { + state = ARG; + } + } else { + buf.append(ch); + } + break; + + default: + throw new IllegalStateException(); + } + } + if (buf.length() > 0 || state != DEFAULT) { + result.add(buf.toString()); + } + return result; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java b/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java new file mode 100755 index 0000000..723e2ca --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/EvaluatableBreakpoint.java @@ -0,0 +1,128 @@ +/******************************************************************************* +* Copyright (c) 2018-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.StringUtils; + +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.event.ThreadDeathEvent; + +import io.reactivex.disposables.Disposable; + +public class EvaluatableBreakpoint extends Breakpoint implements IEvaluatableBreakpoint { + private IEventHub eventHub = null; + private Object compiledConditionalExpression = null; + private Object compiledLogpointExpression = null; + private Map compiledExpressions = new ConcurrentHashMap<>(); + + EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber) { + this(vm, eventHub, className, lineNumber, 0, null); + } + + EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount) { + this(vm, eventHub, className, lineNumber, hitCount, null); + } + + EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, + String condition) { + this(vm, eventHub, className, lineNumber, hitCount, condition, null); + } + + EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, int lineNumber, int hitCount, + String condition, String logMessage) { + super(vm, eventHub, className, lineNumber, hitCount, condition, logMessage); + this.eventHub = eventHub; + } + + EvaluatableBreakpoint(VirtualMachine vm, IEventHub eventHub, JavaBreakpointLocation sourceLocation, int hitCount, + String condition, String logMessage) { + super(vm, eventHub, sourceLocation, hitCount, condition, logMessage); + this.eventHub = eventHub; + } + + @Override + public boolean containsEvaluatableExpression() { + return containsConditionalExpression() || containsLogpointExpression(); + } + + @Override + public boolean containsConditionalExpression() { + return StringUtils.isNotBlank(getCondition()); + } + + @Override + public boolean containsLogpointExpression() { + return StringUtils.isNotBlank(getLogMessage()); + } + + @Override + public void setCompiledConditionalExpression(Object compiledExpression) { + this.compiledConditionalExpression = compiledExpression; + } + + @Override + public Object getCompiledConditionalExpression() { + return compiledConditionalExpression; + } + + @Override + public void setCompiledLogpointExpression(Object compiledExpression) { + this.compiledLogpointExpression = compiledExpression; + } + + @Override + public Object getCompiledLogpointExpression() { + return compiledLogpointExpression; + } + + @Override + public void setCondition(String condition) { + super.setCondition(condition); + setCompiledConditionalExpression(null); + compiledExpressions.clear(); + } + + @Override + public void setLogMessage(String logMessage) { + super.setLogMessage(logMessage); + setCompiledLogpointExpression(null); + compiledExpressions.clear(); + } + + @Override + public Object getCompiledExpression(long threadId) { + return compiledExpressions.get(threadId); + } + + @Override + public void setCompiledExpression(long threadId, Object compiledExpression) { + compiledExpressions.put(threadId, compiledExpression); + } + + @Override + public CompletableFuture install() { + Disposable subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ThreadDeathEvent) + .subscribe(debugEvent -> { + ThreadReference deathThread = ((ThreadDeathEvent) debugEvent.event).thread(); + compiledExpressions.remove(deathThread.uniqueID()); + }); + super.subscriptions().add(subscription); + + return super.install(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/EventHub.java b/src/main/java/com/microsoft/java/debug/core/EventHub.java new file mode 100755 index 0000000..712ed03 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/EventHub.java @@ -0,0 +1,158 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.logging.Logger; + +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.EventQueue; +import com.sun.jdi.event.EventSet; +import com.sun.jdi.event.ExceptionEvent; +import com.sun.jdi.event.StepEvent; +import com.sun.jdi.event.ThreadDeathEvent; +import com.sun.jdi.event.ThreadStartEvent; +import com.sun.jdi.event.VMDeathEvent; +import com.sun.jdi.event.VMDisconnectEvent; +import com.sun.jdi.event.VMStartEvent; + +import io.reactivex.Observable; +import io.reactivex.subjects.PublishSubject; + +public class EventHub implements IEventHub { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private PublishSubject subject = PublishSubject.create(); + + @Override + public Observable events() { + return subject; + } + + private Thread workingThread = null; + private boolean isClosed = false; + + /** + * Starts retrieving events from the event queue of the specified virtual machine. + * + * @param vm + * the target virtual machine. + */ + @Override + public void start(VirtualMachine vm) { + if (isClosed) { + throw new IllegalStateException("This event hub is already closed."); + } + + workingThread = new Thread(() -> { + EventQueue queue = vm.eventQueue(); + while (true) { + try { + if (Thread.interrupted()) { + subject.onComplete(); + return; + } + + EventSet set = queue.remove(); + + boolean shouldResume = true; + for (Event event : set) { + try { + logger.fine("\nJDI Event: " + event + "\n"); + } catch (VMDisconnectedException e) { + // do nothing + } + DebugEvent dbgEvent = new DebugEvent(); + dbgEvent.event = event; + dbgEvent.eventSet = set; + subject.onNext(dbgEvent); + shouldResume &= dbgEvent.shouldResume; + } + + if (shouldResume) { + set.resume(); + } + } catch (InterruptedException e) { + isClosed = true; + subject.onComplete(); + return; + } catch (VMDisconnectedException e) { + isClosed = true; + subject.onError(e); + return; + } + } + }, "Event Hub"); + + workingThread.start(); + } + + @Override + public void close() { + if (isClosed) { + return; + } + + workingThread.interrupt(); + workingThread = null; + isClosed = true; + } + + /** + * Gets the observable object for breakpoint events. + * @return the observable object for breakpoint events + */ + @Override + public Observable breakpointEvents() { + return this.events().filter(debugEvent -> debugEvent.event instanceof BreakpointEvent); + } + + /** + * Gets the observable object for thread events. + * @return the observable object for thread events + */ + @Override + public Observable threadEvents() { + return this.events().filter(debugEvent -> debugEvent.event instanceof ThreadStartEvent + || debugEvent.event instanceof ThreadDeathEvent); + } + + /** + * Gets the observable object for exception events. + * @return the observable object for exception events + */ + @Override + public Observable exceptionEvents() { + return this.events().filter(debugEvent -> debugEvent.event instanceof ExceptionEvent); + } + + /** + * Gets the observable object for step events. + * @return the observable object for step events + */ + @Override + public Observable stepEvents() { + return this.events().filter(debugEvent -> debugEvent.event instanceof StepEvent); + } + + /** + * Gets the observable object for vm events. + * @return the observable object for vm events + */ + @Override + public Observable vmEvents() { + return this.events().filter(debugEvent -> debugEvent.event instanceof VMStartEvent + || debugEvent.event instanceof VMDisconnectEvent + || debugEvent.event instanceof VMDeathEvent); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/IBreakpoint.java b/src/main/java/com/microsoft/java/debug/core/IBreakpoint.java new file mode 100755 index 0000000..40995e9 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/IBreakpoint.java @@ -0,0 +1,58 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.concurrent.CompletableFuture; + +public interface IBreakpoint extends IDebugResource { + + String REQUEST_TYPE = "request_type"; + + int REQUEST_TYPE_LINE = 0; + + int REQUEST_TYPE_METHOD = 1; + + int REQUEST_TYPE_LAMBDA = 2; + + JavaBreakpointLocation sourceLocation(); + + String className(); + + int getLineNumber(); + + int getColumnNumber(); + + int getHitCount(); + + void setHitCount(int hitCount); + + CompletableFuture install(); + + void putProperty(Object key, Object value); + + Object getProperty(Object key); + + String getCondition(); + + void setCondition(String condition); + + String getLogMessage(); + + void setLogMessage(String logMessage); + + default void setAsync(boolean async) { + } + + default boolean async() { + return false; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/IDebugResource.java b/src/main/java/com/microsoft/java/debug/core/IDebugResource.java new file mode 100755 index 0000000..71c1e03 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/IDebugResource.java @@ -0,0 +1,24 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.List; + +import com.sun.jdi.request.EventRequest; + +import io.reactivex.disposables.Disposable; + +public interface IDebugResource extends AutoCloseable { + List requests(); + + List subscriptions(); +} diff --git a/src/main/java/com/microsoft/java/debug/core/IDebugSession.java b/src/main/java/com/microsoft/java/debug/core/IDebugSession.java new file mode 100755 index 0000000..4e4078c --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/IDebugSession.java @@ -0,0 +1,50 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.List; + +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VirtualMachine; + +public interface IDebugSession { + void start(); + + void suspend(); + + void resume(); + + void detach(); + + void terminate(); + + // breakpoints + IBreakpoint createBreakpoint(String className, int lineNumber, int hitCount, String condition, String logMessage); + + IBreakpoint createBreakpoint(JavaBreakpointLocation sourceLocation, int hitCount, String condition, String logMessage); + + IWatchpoint createWatchPoint(String className, String fieldName, String accessType, String condition, int hitCount); + + void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught); + + void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught, String[] classFilters, String[] classExclusionFilters); + + IMethodBreakpoint createFunctionBreakpoint(String className, String functionName, String condition, int hitCount); + + Process process(); + + List getAllThreads(); + + IEventHub getEventHub(); + + VirtualMachine getVM(); +} diff --git a/src/main/java/com/microsoft/java/debug/core/IEvaluatableBreakpoint.java b/src/main/java/com/microsoft/java/debug/core/IEvaluatableBreakpoint.java new file mode 100755 index 0000000..16d8904 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/IEvaluatableBreakpoint.java @@ -0,0 +1,69 @@ +/******************************************************************************* +* Copyright (c) 2018 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +public interface IEvaluatableBreakpoint { + boolean containsEvaluatableExpression(); + + boolean containsConditionalExpression(); + + boolean containsLogpointExpression(); + + String getCondition(); + + void setCondition(String condition); + + String getLogMessage(); + + void setLogMessage(String logMessage); + + /** + * please use {@link #setCompiledExpression(long, Object)} instead. + */ + @Deprecated + void setCompiledConditionalExpression(Object compiledExpression); + + /** + * please use {@link #getCompiledExpression(long)} instead. + */ + @Deprecated + Object getCompiledConditionalExpression(); + + /** + * please use {@link #setCompiledExpression(long, Object)} instead. + */ + @Deprecated + void setCompiledLogpointExpression(Object compiledExpression); + + /** + * please use {@link #getCompiledExpression(long)} instead. + */ + @Deprecated + Object getCompiledLogpointExpression(); + + /** + * Sets the compiled expression for a thread. + * + * @param threadId - thread the breakpoint is hit in + * @param compiledExpression - associated compiled expression + */ + void setCompiledExpression(long threadId, Object compiledExpression); + + /** + * Returns existing compiled expression for the given thread or + * null. + * + * @param threadId thread the breakpoint was hit in + * @return compiled expression or null + */ + Object getCompiledExpression(long threadId); +} diff --git a/src/main/java/com/microsoft/java/debug/core/IEventHub.java b/src/main/java/com/microsoft/java/debug/core/IEventHub.java new file mode 100755 index 0000000..9ce4007 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/IEventHub.java @@ -0,0 +1,32 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import com.sun.jdi.VirtualMachine; + +import io.reactivex.Observable; + +public interface IEventHub extends AutoCloseable { + void start(VirtualMachine vm); + + Observable events(); + + Observable breakpointEvents(); + + Observable threadEvents(); + + Observable exceptionEvents(); + + Observable stepEvents(); + + Observable vmEvents(); +} diff --git a/src/main/java/com/microsoft/java/debug/core/IMethodBreakpoint.java b/src/main/java/com/microsoft/java/debug/core/IMethodBreakpoint.java new file mode 100755 index 0000000..68f9539 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/IMethodBreakpoint.java @@ -0,0 +1,40 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Gayan Perera - initial API and implementation +*******************************************************************************/ +package com.microsoft.java.debug.core; + +import java.util.concurrent.CompletableFuture; + +public interface IMethodBreakpoint extends IDebugResource { + String methodName(); + + String className(); + + int getHitCount(); + + String getCondition(); + + void setHitCount(int hitCount); + + void setCondition(String condition); + + CompletableFuture install(); + + Object getProperty(Object key); + + void putProperty(Object key, Object value); + + default void setAsync(boolean async) { + } + + default boolean async() { + return false; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/IWatchpoint.java b/src/main/java/com/microsoft/java/debug/core/IWatchpoint.java new file mode 100755 index 0000000..a3a29a8 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/IWatchpoint.java @@ -0,0 +1,36 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.concurrent.CompletableFuture; + +public interface IWatchpoint extends IDebugResource { + String className(); + + String fieldName(); + + String accessType(); + + CompletableFuture install(); + + void putProperty(Object key, Object value); + + Object getProperty(Object key); + + int getHitCount(); + + void setHitCount(int hitCount); + + String getCondition(); + + void setCondition(String condition); +} diff --git a/src/main/java/com/microsoft/java/debug/core/JavaBreakpointLocation.java b/src/main/java/com/microsoft/java/debug/core/JavaBreakpointLocation.java new file mode 100755 index 0000000..820e80c --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/JavaBreakpointLocation.java @@ -0,0 +1,113 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.Objects; + +import com.microsoft.java.debug.core.protocol.Types; + +public class JavaBreakpointLocation { + /** + * The source line of the breakpoint or logpoint. + */ + private int lineNumber; + /** + * The source column of the breakpoint. + */ + private int columnNumber = -1; + /** + * The declaring class name that encloses the target position. + */ + private String className; + /** + * The method name and signature when the target position + * points to a method declaration. + */ + private String methodName; + private String methodSignature; + /** + * All possible locations for source breakpoints in a given range. + */ + private Types.BreakpointLocation[] availableBreakpointLocations = new Types.BreakpointLocation[0]; + + public JavaBreakpointLocation(int lineNumber, int columnNumber) { + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + + @Override + public int hashCode() { + return Objects.hash(lineNumber, columnNumber, className, methodName, methodSignature); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof JavaBreakpointLocation)) { + return false; + } + JavaBreakpointLocation other = (JavaBreakpointLocation) obj; + return lineNumber == other.lineNumber && columnNumber == other.columnNumber + && Objects.equals(className, other.className) && Objects.equals(methodName, other.methodName) + && Objects.equals(methodSignature, other.methodSignature); + } + + public int lineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public int columnNumber() { + return columnNumber; + } + + public void setColumnNumber(int columnNumber) { + this.columnNumber = columnNumber; + } + + public String className() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String methodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public String methodSignature() { + return methodSignature; + } + + public void setMethodSignature(String methodSignature) { + this.methodSignature = methodSignature; + } + + public Types.BreakpointLocation[] availableBreakpointLocations() { + return availableBreakpointLocations; + } + + public void setAvailableBreakpointLocations(Types.BreakpointLocation[] availableBreakpointLocations) { + this.availableBreakpointLocations = availableBreakpointLocations; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/JdiExceptionReference.java b/src/main/java/com/microsoft/java/debug/core/JdiExceptionReference.java new file mode 100755 index 0000000..0bdd142 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/JdiExceptionReference.java @@ -0,0 +1,24 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import com.sun.jdi.ObjectReference; + +public class JdiExceptionReference { + public ObjectReference exception; + public boolean isUncaught; + + public JdiExceptionReference(ObjectReference exception, boolean isUncaught) { + this.exception = exception; + this.isUncaught = isUncaught; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/JdiMethodResult.java b/src/main/java/com/microsoft/java/debug/core/JdiMethodResult.java new file mode 100755 index 0000000..715e236 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/JdiMethodResult.java @@ -0,0 +1,25 @@ +/******************************************************************************* +* Copyright (c) 2020 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import com.sun.jdi.Method; +import com.sun.jdi.Value; + +public class JdiMethodResult { + public Method method; + public Value value; + + public JdiMethodResult(Method method, Value value) { + this.method = method; + this.value = value; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/LaunchException.java b/src/main/java/com/microsoft/java/debug/core/LaunchException.java new file mode 100755 index 0000000..6c1e1a5 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/LaunchException.java @@ -0,0 +1,51 @@ +/******************************************************************************* +* Copyright (c) 2018-2021 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import com.sun.jdi.connect.VMStartException; + +/** + * Extends {@link VMStartException} to provide more detail about the failed process + * from before it is destroyed. + */ +public class LaunchException extends VMStartException { + + boolean exited; + int exitStatus; + String stdout; + String stderr; + + public LaunchException(String message, Process process, boolean exited, int exitStatus, String stdout, String stderr) { + super(message, process); + this.exited = exited; + this.exitStatus = exitStatus; + this.stdout = stdout; + this.stderr = stderr; + } + + public boolean isExited() { + return exited; + } + + public int getExitStatus() { + return exitStatus; + } + + public String getStdout() { + return stdout; + } + + public String getStderr() { + return stderr; + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java b/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java new file mode 100755 index 0000000..7a6e74c --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/MethodBreakpoint.java @@ -0,0 +1,294 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Gayan Perera - initial API and implementation +*******************************************************************************/ +package com.microsoft.java.debug.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.StringUtils; + +import com.sun.jdi.ReferenceType; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.event.ClassPrepareEvent; +import com.sun.jdi.event.ThreadDeathEvent; +import com.sun.jdi.request.ClassPrepareRequest; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.MethodEntryRequest; + +import io.reactivex.Observable; +import io.reactivex.disposables.Disposable; + +public class MethodBreakpoint implements IMethodBreakpoint, IEvaluatableBreakpoint { + + private VirtualMachine vm; + private IEventHub eventHub; + private String className; + private String functionName; + private String condition; + private int hitCount; + private boolean async = false; + + private HashMap propertyMap = new HashMap<>(); + private Object compiledConditionalExpression = null; + private Map compiledExpressions = new ConcurrentHashMap<>(); + + private List requests = Collections.synchronizedList(new ArrayList<>()); + private List subscriptions = new ArrayList<>(); + + public MethodBreakpoint(VirtualMachine vm, IEventHub eventHub, String className, String functionName, + String condition, int hitCount) { + Objects.requireNonNull(vm); + Objects.requireNonNull(eventHub); + Objects.requireNonNull(className); + Objects.requireNonNull(functionName); + this.vm = vm; + this.eventHub = eventHub; + this.className = className; + this.functionName = functionName; + this.condition = condition; + this.hitCount = hitCount; + } + + @Override + public List requests() { + return requests; + } + + @Override + public List subscriptions() { + return subscriptions; + } + + @Override + public void close() throws Exception { + try { + vm.eventRequestManager().deleteEventRequests(requests()); + } catch (VMDisconnectedException ex) { + // ignore since removing breakpoints is meaningless when JVM is terminated. + } + subscriptions().forEach(Disposable::dispose); + requests.clear(); + subscriptions.clear(); + } + + @Override + public boolean containsEvaluatableExpression() { + return containsConditionalExpression() || containsLogpointExpression(); + } + + @Override + public boolean containsConditionalExpression() { + return StringUtils.isNotBlank(getCondition()); + } + + @Override + public boolean containsLogpointExpression() { + return false; + } + + @Override + public String getCondition() { + return condition; + } + + @Override + public void setCondition(String condition) { + this.condition = condition; + setCompiledConditionalExpression(null); + compiledExpressions.clear(); + } + + @Override + public String getLogMessage() { + return null; + } + + @Override + public void setLogMessage(String logMessage) { + // for future implementation + } + + @Override + public void setCompiledConditionalExpression(Object compiledExpression) { + this.compiledConditionalExpression = compiledExpression; + } + + @Override + public Object getCompiledConditionalExpression() { + return compiledConditionalExpression; + } + + @Override + public void setCompiledLogpointExpression(Object compiledExpression) { + // for future implementation + } + + @Override + public Object getCompiledLogpointExpression() { + return null; + } + + @Override + public void setCompiledExpression(long threadId, Object compiledExpression) { + compiledExpressions.put(threadId, compiledExpression); + } + + @Override + public Object getCompiledExpression(long threadId) { + return compiledExpressions.get(threadId); + } + + @Override + public int getHitCount() { + return hitCount; + } + + @Override + public void setHitCount(int hitCount) { + this.hitCount = hitCount; + Observable.fromIterable(this.requests()) + .filter(request -> request instanceof MethodEntryRequest) + .subscribe(request -> { + request.addCountFilter(hitCount); + request.enable(); + }); + } + + @Override + public boolean async() { + return this.async; + } + + @Override + public void setAsync(boolean async) { + this.async = async; + } + + @Override + public CompletableFuture install() { + Disposable subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ThreadDeathEvent) + .subscribe(debugEvent -> { + ThreadReference deathThread = ((ThreadDeathEvent) debugEvent.event).thread(); + compiledExpressions.remove(deathThread.uniqueID()); + }); + + subscriptions.add(subscription); + + // It's possible that different class loaders create new class with the same + // name. + // Here to listen to future class prepare events to handle such case. + ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); + classPrepareRequest.addClassFilter(className); + classPrepareRequest.enable(); + requests.add(classPrepareRequest); + + CompletableFuture future = new CompletableFuture<>(); + subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent + && (classPrepareRequest.equals(debugEvent.event.request()))) + .subscribe(debugEvent -> { + ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event; + Optional createdRequest = AsyncJdwpUtils.await( + createMethodEntryRequest(event.referenceType()) + ); + if (createdRequest.isPresent()) { + MethodEntryRequest methodEntryRequest = createdRequest.get(); + requests.add(methodEntryRequest); + if (!future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + } + }); + subscriptions.add(subscription); + + Runnable createRequestsFromLoadedClasses = () -> { + List types = vm.classesByName(className); + for (ReferenceType type : types) { + createMethodEntryRequest(type).whenComplete((createdRequest, ex) -> { + if (ex != null) { + return; + } + + if (createdRequest.isPresent()) { + MethodEntryRequest methodEntryRequest = createdRequest.get(); + requests.add(methodEntryRequest); + if (!future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + } + }); + } + }; + + if (async()) { + AsyncJdwpUtils.runAsync(createRequestsFromLoadedClasses); + } else { + createRequestsFromLoadedClasses.run(); + } + + return future; + } + + private CompletableFuture> createMethodEntryRequest(ReferenceType type) { + if (async()) { + return CompletableFuture.supplyAsync(() -> createMethodEntryRequest0(type)); + } else { + return CompletableFuture.completedFuture(createMethodEntryRequest0(type)); + } + } + + private Optional createMethodEntryRequest0(ReferenceType type) { + return type.methodsByName(functionName).stream().findFirst().map(method -> { + MethodEntryRequest request = vm.eventRequestManager().createMethodEntryRequest(); + + request.addClassFilter(type); + request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + if (hitCount > 0) { + request.addCountFilter(hitCount); + } + request.enable(); + return request; + }); + } + + @Override + public Object getProperty(Object key) { + return propertyMap.get(key); + } + + @Override + public void putProperty(Object key, Object value) { + propertyMap.put(key, value); + } + + @Override + public String methodName() { + return functionName; + } + + @Override + public String className() { + return className; + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/StackFrameUtility.java b/src/main/java/com/microsoft/java/debug/core/StackFrameUtility.java new file mode 100755 index 0000000..3195747 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/StackFrameUtility.java @@ -0,0 +1,78 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import com.sun.jdi.AbsentInformationException; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.InvalidStackFrameException; +import com.sun.jdi.NativeMethodException; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; + +public final class StackFrameUtility { + + public static boolean isNative(StackFrame frame) { + return frame.location().method().isNative(); + } + + /** + * Pop a StackFrame from its thread. + * + * @param frame + * the StackFrame will be popped + */ + public static void pop(StackFrame frame) throws DebugException { + try { + frame.thread().popFrames(frame); + } catch (IncompatibleThreadStateException e) { + throw new DebugException(String.format("%s occurred popping stack frame.", e.getMessage()), e); + } catch (InvalidStackFrameException e) { + throw new DebugException("Cannot pop up the top stack farme.", e); + } catch (NativeMethodException e) { + throw new DebugException("Cannot pop up the stack frame because it is not valid for a native method.", e); + } catch (RuntimeException e) { + throw new DebugException(String.format("Runtime exception happened: %s", e.getMessage()), e); + } + } + + public static String getName(StackFrame frame) { + return frame.location().method().name(); + } + + public static String getSignature(StackFrame frame) { + return frame.location().method().signature(); + } + + public static boolean isObsolete(StackFrame frame) { + return frame.location().method().isObsolete(); + } + + /** + * Get the StackFrame associated source file path. + * + * @param frame + * StackFrame for the source path + * @return the source file path + */ + public static String getSourcePath(StackFrame frame) { + try { + return frame.location().sourcePath(); + } catch (AbsentInformationException e) { + // Ignore it + } + return null; + } + + public static ReferenceType getDeclaringType(StackFrame frame) { + return frame.location().method().declaringType(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java b/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java new file mode 100755 index 0000000..b645e28 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java @@ -0,0 +1,228 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.ArrayList; +import java.util.Formatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gson.JsonElement; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.protocol.JsonUtils; +import com.microsoft.java.debug.core.protocol.Messages.Request; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.sun.jdi.event.Event; + +public class UsageDataSession { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private static final Logger usageDataLogger = Logger.getLogger(Configuration.USAGE_DATA_LOGGER_NAME); + private static final long RESPONSE_MAX_DELAY_MS = 1000; + private static final ThreadLocal threadLocal = new InheritableThreadLocal<>(); + private static final boolean TRACE_DAP_PERF = Boolean.getBoolean("debug.dap.perf"); + + private final String sessionGuid = UUID.randomUUID().toString(); + private boolean jdiEventSequenceEnabled = false; + private long startAt = -1; + private long stopAt = -1; + private Map commandCountMap = new HashMap<>(); + private Map breakpointCountMap = new HashMap<>(); + private Map requestEventMap = new HashMap<>(); + private Map userErrorCount = new HashMap<>(); + private Map commandPerfCountMap = new HashMap<>(); + private List eventList = new ArrayList<>(); + private List dapPerf = new ArrayList<>(); + + public static String getSessionGuid() { + return threadLocal.get() == null ? "" : threadLocal.get().sessionGuid; + } + + public UsageDataSession() { + threadLocal.set(this); + } + + class RequestEvent { + Request request; + long timestamp; + + RequestEvent(Request request, long timestamp) { + this.request = request; + this.timestamp = timestamp; + } + } + + public void reportStart() { + startAt = System.currentTimeMillis(); + } + + public void reportStop() { + stopAt = System.currentTimeMillis(); + } + + /** + * Record usage data from request. + */ + public void recordRequest(Request request) { + try { + requestEventMap.put(request.seq, new RequestEvent(request, System.currentTimeMillis())); + + // cmd count + commandCountMap.put(request.command, commandCountMap.getOrDefault(request.command, 0) + 1); + + // bp count + if ("setBreakpoints".equals(request.command)) { + String fileIdentifier = "unknown file"; + JsonElement pathElement = request.arguments.get("source").getAsJsonObject().get("path"); + JsonElement nameElement = request.arguments.get("source").getAsJsonObject().get("name"); + if (pathElement != null) { + fileIdentifier = pathElement.getAsString(); + } else if (nameElement != null) { + fileIdentifier = nameElement.getAsString(); + } + String filenameHash = AdapterUtils.getSHA256HexDigest(fileIdentifier); + int bpCount = request.arguments.get("breakpoints").getAsJsonArray().size(); + breakpointCountMap.put(filenameHash, breakpointCountMap.getOrDefault(filenameHash, 0) + bpCount); + } + } catch (Throwable e) { + // ignore it + } + } + + /** + * Record usage data from response. + */ + public void recordResponse(Response response) { + try { + long responseMillis = System.currentTimeMillis(); + long requestMillis = responseMillis; + String command = null; + + RequestEvent requestEvent = requestEventMap.getOrDefault(response.request_seq, null); + if (requestEvent != null) { + command = requestEvent.request.command; + requestMillis = requestEvent.timestamp; + requestEventMap.remove(response.request_seq); + } + long duration = responseMillis - requestMillis; + commandPerfCountMap.compute(command, (k, v) -> (v == null ? 0 : v.intValue()) + (int) duration); + + if (TRACE_DAP_PERF) { + synchronized (dapPerf) { + dapPerf.add(new String[]{command, String.valueOf(duration)}); + } + } + + if (!response.success || duration > RESPONSE_MAX_DELAY_MS) { + Map props = new HashMap<>(); + props.put("duration", duration); + props.put("command", command); + props.put("success", response.success); + // directly report abnormal response. + usageDataLogger.log(Level.WARNING, "abnormal response", props); + jdiEventSequenceEnabled = true; + } + } catch (Throwable e) { + // ignore it + } + } + + /** + * Submit summary of usage data in current session. + */ + public void submitUsageData() { + Map props = new HashMap<>(); + props.put("sessionStartAt", String.valueOf(startAt)); + props.put("sessionStopAt", String.valueOf(stopAt)); + props.put("commandCount", JsonUtils.toJson(commandCountMap)); + props.put("breakpointCount", JsonUtils.toJson(breakpointCountMap)); + props.put("userErrorCount", JsonUtils.toJson(userErrorCount)); + props.put("commandPerfCount", JsonUtils.toJson(commandPerfCountMap)); + if (jdiEventSequenceEnabled) { + synchronized (eventList) { + props.put("jdiEventSequence", JsonUtils.toJson(eventList)); + } + } + usageDataLogger.log(Level.INFO, "session usage data summary", props); + + if (TRACE_DAP_PERF) { + Formatter fmt = new Formatter(); + fmt.format("\nDAP Performance Metrics:\n"); + fmt.format("%30s %10s(ms)\n", "Request", "Duration"); + synchronized (dapPerf) { + dapPerf.forEach((event) -> { + fmt.format("%30s %14s\n", event[0], event[1]); + }); + } + logger.info(String.valueOf(fmt)); + } + } + + /** + * Record JDI event. + */ + public static void recordEvent(Event event) { + try { + UsageDataSession currentSession = threadLocal.get(); + if (currentSession != null) { + Map eventEntry = new HashMap<>(); + eventEntry.put("timestamp", String.valueOf(System.currentTimeMillis())); + eventEntry.put("event", event.toString()); + synchronized (currentSession.eventList) { + currentSession.eventList.add(JsonUtils.toJson(eventEntry)); + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Exception on recording event: %s.", e.toString()), e); + } + } + + public static void recordInfo(String key, Object value) { + Map map = new HashMap<>(); + map.put(key, value); + usageDataLogger.log(Level.INFO, "session info", map); + } + + public static void recordInfo(String description, Map data) { + usageDataLogger.log(Level.INFO, description, data); + } + + /** + * Record counts for each user errors encountered. + */ + public void recordUserError(ErrorCode errorCode) { + try { + String errorCodeStr = errorCode.name(); + userErrorCount.put(errorCodeStr, userErrorCount.getOrDefault(errorCodeStr, 0) + 1); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Exception on recording user error: %s.", e.toString()), e); + } + } + + /** + * Enable JDI event sequence track in current session. + */ + public static void enableJdiEventSequence() { + try { + UsageDataSession currentSession = threadLocal.get(); + if (currentSession != null) { + currentSession.jdiEventSequenceEnabled = true; + } + } catch (Exception e) { + // ignore it + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java b/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java new file mode 100755 index 0000000..fdc2505 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java @@ -0,0 +1,104 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; + +import com.microsoft.java.debug.core.protocol.JsonUtils; + +public class UsageDataStore { + private ConcurrentLinkedQueue queue; + private static final int QUEUE_MAX_SIZE = 10000; + private static final String DEBUG_SESSION_ID_NAME = "debugSessionId"; + private static final String DESCRIPTION_NAME = "description"; + private static final String ERROR_MESSAGE_NAME = "message"; + private static final String STACKTRACE_NAME = "stackTrace"; + private static final String SCOPE_NAME = "scope"; + private static final String TIMESTAMP_NAME = "timestamp"; + + /** + * Constructor. + */ + private UsageDataStore() { + queue = new ConcurrentLinkedQueue<>(); + } + + private static final class SingletonHolder { + private static final UsageDataStore INSTANCE = new UsageDataStore(); + } + + /** + * Fetch all pending user data records. + * @return List of user data Object. + */ + public synchronized Object[] fetchAll() { + Object[] ret = queue.toArray(); + queue.clear(); + return ret; + } + + public static UsageDataStore getInstance() { + return SingletonHolder.INSTANCE; + } + + /** + * Log user data Object into the queue. + */ + public void logSessionData(String desc, Map props) { + if (queue == null) { + return; + } + Map sessionEntry = new HashMap<>(); + sessionEntry.put(SCOPE_NAME, "session"); + sessionEntry.put(DEBUG_SESSION_ID_NAME, UsageDataSession.getSessionGuid()); + if (desc != null) { + sessionEntry.put(DESCRIPTION_NAME, desc); + } + if (props != null) { + sessionEntry.putAll(props); + } + enqueue(sessionEntry); + } + + /** + * Log Exception details into queue. + */ + public void logErrorData(String desc, Throwable th) { + if (queue == null) { + return; + } + Map errorEntry = new HashMap<>(); + errorEntry.put(SCOPE_NAME, "exception"); + errorEntry.put(DEBUG_SESSION_ID_NAME, UsageDataSession.getSessionGuid()); + if (desc != null) { + errorEntry.put(DESCRIPTION_NAME, desc); + } + if (th != null) { + errorEntry.put(ERROR_MESSAGE_NAME, th.getMessage()); + errorEntry.put(STACKTRACE_NAME, JsonUtils.toJson(th.getStackTrace())); + } + enqueue(errorEntry); + } + + private synchronized void enqueue(Map entry) { + if (queue.size() > QUEUE_MAX_SIZE) { + queue.poll(); + } + if (entry != null) { + entry.put(TIMESTAMP_NAME, Instant.now().toString()); + queue.add(entry); + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/Watchpoint.java b/src/main/java/com/microsoft/java/debug/core/Watchpoint.java new file mode 100755 index 0000000..3de321e --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/Watchpoint.java @@ -0,0 +1,276 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.StringUtils; + +import com.sun.jdi.Field; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.event.ClassPrepareEvent; +import com.sun.jdi.event.ThreadDeathEvent; +import com.sun.jdi.request.ClassPrepareRequest; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.WatchpointRequest; + +import io.reactivex.Observable; +import io.reactivex.disposables.Disposable; + +public class Watchpoint implements IWatchpoint, IEvaluatableBreakpoint { + private final VirtualMachine vm; + private final IEventHub eventHub; + private final String className; + private final String fieldName; + private String accessType = null; + private String condition = null; + private int hitCount; + private HashMap propertyMap = new HashMap<>(); + private Object compiledConditionalExpression = null; + private Map compiledExpressions = new ConcurrentHashMap<>(); + + // IDebugResource + private List requests = new ArrayList<>(); + private List subscriptions = new ArrayList<>(); + + Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName) { + this(vm, eventHub, className, fieldName, "write"); + } + + Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, String accessType) { + this(vm, eventHub, className, fieldName, accessType, null, 0); + } + + Watchpoint(VirtualMachine vm, IEventHub eventHub, String className, String fieldName, String accessType, String condition, int hitCount) { + Objects.requireNonNull(vm); + Objects.requireNonNull(eventHub); + Objects.requireNonNull(className); + Objects.requireNonNull(fieldName); + this.vm = vm; + this.eventHub = eventHub; + this.className = className; + this.fieldName = fieldName; + this.accessType = accessType; + this.condition = condition; + this.hitCount = hitCount; + } + + @Override + public List requests() { + return requests; + } + + @Override + public List subscriptions() { + return subscriptions; + } + + @Override + public void close() throws Exception { + try { + vm.eventRequestManager().deleteEventRequests(requests()); + } catch (VMDisconnectedException ex) { + // ignore since removing breakpoints is meaningless when JVM is terminated. + } + subscriptions().forEach(subscription -> { + subscription.dispose(); + }); + requests.clear(); + subscriptions.clear(); + } + + @Override + public String className() { + return className; + } + + @Override + public String fieldName() { + return fieldName; + } + + @Override + public String accessType() { + return accessType; + } + + @Override + public String getCondition() { + return condition; + } + + @Override + public void setCondition(String condition) { + this.condition = condition; + setCompiledConditionalExpression(null); + compiledExpressions.clear(); + } + + @Override + public int getHitCount() { + return hitCount; + } + + @Override + public void setHitCount(int hitCount) { + this.hitCount = hitCount; + + Observable.fromIterable(this.requests()) + .filter(request -> request instanceof WatchpointRequest) + .subscribe(request -> { + request.addCountFilter(hitCount); + request.enable(); + }); + } + + @Override + public void putProperty(Object key, Object value) { + propertyMap.put(key, value); + } + + @Override + public Object getProperty(Object key) { + return propertyMap.get(key); + } + + @Override + public CompletableFuture install() { + Disposable subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ThreadDeathEvent) + .subscribe(debugEvent -> { + ThreadReference deathThread = ((ThreadDeathEvent) debugEvent.event).thread(); + compiledExpressions.remove(deathThread.uniqueID()); + }); + subscriptions.add(subscription); + + // It's possible that different class loaders create new class with the same name. + // Here to listen to future class prepare events to handle such case. + ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); + classPrepareRequest.addClassFilter(className); + classPrepareRequest.enable(); + requests.add(classPrepareRequest); + + CompletableFuture future = new CompletableFuture<>(); + subscription = eventHub.events() + .filter(debugEvent -> debugEvent.event instanceof ClassPrepareEvent && (classPrepareRequest.equals(debugEvent.event.request()))) + .subscribe(debugEvent -> { + ClassPrepareEvent event = (ClassPrepareEvent) debugEvent.event; + List watchpointRequests = createWatchpointRequests(event.referenceType()); + requests.addAll(watchpointRequests); + if (!watchpointRequests.isEmpty() && !future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + }); + subscriptions.add(subscription); + + List watchpointRequests = new ArrayList<>(); + List types = vm.classesByName(className); + for (ReferenceType type : types) { + watchpointRequests.addAll(createWatchpointRequests(type)); + } + + requests.addAll(watchpointRequests); + if (!watchpointRequests.isEmpty() && !future.isDone()) { + this.putProperty("verified", true); + future.complete(this); + } + + return future; + } + + private List createWatchpointRequests(ReferenceType type) { + List watchpointRequests = new ArrayList<>(); + Field field = type.fieldByName(fieldName); + if (field != null) { + if ("read".equals(accessType)) { + watchpointRequests.add(vm.eventRequestManager().createAccessWatchpointRequest(field)); + } else if ("readWrite".equals(accessType)) { + watchpointRequests.add(vm.eventRequestManager().createAccessWatchpointRequest(field)); + watchpointRequests.add(vm.eventRequestManager().createModificationWatchpointRequest(field)); + } else { + watchpointRequests.add(vm.eventRequestManager().createModificationWatchpointRequest(field)); + } + } + + watchpointRequests.forEach(request -> { + request.setSuspendPolicy(WatchpointRequest.SUSPEND_EVENT_THREAD); + if (hitCount > 0) { + request.addCountFilter(hitCount); + } + request.enable(); + }); + return watchpointRequests; + } + + @Override + public String getLogMessage() { + return null; + } + + @Override + public void setLogMessage(String logMessage) { + throw new UnsupportedOperationException("Log message feature is unsupported for watchpoint."); + } + + @Override + public boolean containsEvaluatableExpression() { + return containsConditionalExpression(); + } + + @Override + public boolean containsConditionalExpression() { + return StringUtils.isNotBlank(getCondition()); + } + + @Override + public boolean containsLogpointExpression() { + return false; + } + + public void setCompiledConditionalExpression(Object compiledExpression) { + this.compiledConditionalExpression = compiledExpression; + } + + public Object getCompiledConditionalExpression() { + return compiledConditionalExpression; + } + + @Override + public void setCompiledLogpointExpression(Object compiledExpression) { + // do nothing + } + + @Override + public Object getCompiledLogpointExpression() { + return null; + } + + @Override + public Object getCompiledExpression(long threadId) { + return compiledExpressions.get(threadId); + } + + @Override + public void setCompiledExpression(long threadId, Object compiledExpression) { + compiledExpressions.put(threadId, compiledExpression); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java b/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java new file mode 100755 index 0000000..9b97233 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java @@ -0,0 +1,313 @@ +/******************************************************************************* +* Copyright (c) 2017-2021 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types; + +public class AdapterUtils { + private static final String OS_NAME = System.getProperty("os.name", "").toLowerCase(); + private static final Pattern ENCLOSING_CLASS_REGEX = Pattern.compile("^([^\\$]*)"); + public static final boolean isWin = isWindows(); + public static final boolean isMac = OS_NAME.contains("mac") || OS_NAME.contains("darwin"); + + /** + * Check if the OS is windows or not. + */ + public static boolean isWindows() { + return OS_NAME.contains("win"); + } + + /** + * Search the absolute path of the java file under the specified source path directory. + * @param sourcePaths + * the project source directories + * @param sourceName + * the java file path + * @return the absolute file path + */ + public static String sourceLookup(String[] sourcePaths, String sourceName) { + if (sourcePaths != null) { + for (String path : sourcePaths) { + Path fullpath = Paths.get(path, sourceName); + if (Files.isRegularFile(fullpath)) { + return fullpath.toString(); + } + } + } + return null; + } + + /** + * Get the enclosing type name of the given fully qualified name. + *
+     * a.b.c        ->   a.b.c
+     * a.b.c$1      ->   a.b.c
+     * a.b.c$1$2    ->   a.b.c
+     * 
+ * @param fullyQualifiedName + * fully qualified name + * @return the enclosing type name + */ + public static String parseEnclosingType(String fullyQualifiedName) { + if (fullyQualifiedName == null) { + return null; + } + Matcher matcher = ENCLOSING_CLASS_REGEX.matcher(fullyQualifiedName); + if (matcher.find()) { + return matcher.group(); + } + return null; + } + + /** + * Convert the source platform's line number to the target platform's line number. + * + * @param line + * the line number from the source platform + * @param sourceLinesStartAt1 + * the source platform's line starts at 1 or not + * @param targetLinesStartAt1 + * the target platform's line starts at 1 or not + * @return the new line number + */ + public static int convertLineNumber(int line, boolean sourceLinesStartAt1, boolean targetLinesStartAt1) { + if (sourceLinesStartAt1) { + return targetLinesStartAt1 ? line : line - 1; + } else { + return targetLinesStartAt1 ? line + 1 : line; + } + } + + /** + * Convert the source platform's column number to the target platform's column + * number. + * + * @param column + * the column number from the source platform + * @param sourceColumnsStartAt1 + * the source platform's column starts at 1 or not + * @param targetColumnStartAt1 + * the target platform's column starts at 1 or not + * @return the new column number + */ + public static int convertColumnNumber(int column, boolean sourceColumnsStartAt1, boolean targetColumnStartAt1) { + if (sourceColumnsStartAt1) { + return targetColumnStartAt1 ? column : column - 1; + } else { + return targetColumnStartAt1 ? column + 1 : column; + } + } + + /** + * Convert the source platform's path format to the target platform's path format. + * + * @param path + * the path value from the source platform + * @param sourceIsUri + * the path format of the source platform is uri or not + * @param targetIsUri + * the path format of the target platform is uri or not + * @return the new path value + */ + public static String convertPath(String path, boolean sourceIsUri, boolean targetIsUri) { + if (path == null) { + return null; + } + + if (sourceIsUri == targetIsUri) { + return path; + } else if (sourceIsUri && !targetIsUri) { + return toPath(path); + } else { + return toUri(path); + } + } + + /** + * Convert a file uri to a file path, or null if this uri does not represent a file in the local file system. + * @param uri + * the uri string + * @return the file path + */ + public static String toPath(String uri) { + try { + return Paths.get(new URI(uri)).toString(); + } catch (URISyntaxException | IllegalArgumentException | FileSystemNotFoundException + | SecurityException e) { + return null; + } + } + + /** + * Convert a file path to an uri string. + * @param path + * the file path + * @return the uri string + */ + public static String toUri(String path) { + try { + return Paths.get(path).toUri().toString(); + } catch (InvalidPathException e) { + return null; + } + } + + /** + * Convert a file path to an url string. + * @param path + * the file path + * @return the url string + * @throws MalformedURLException + * if the file path cannot be constructed to an url because of some errors. + */ + public static String toUrl(String path) throws MalformedURLException { + File file = new File(path); + return file.toURI().toURL().toString(); + } + + /** + * Check a string variable is an uri or not. + */ + public static boolean isUri(String uriString) { + try { + URI uri = new URI(uriString); + return StringUtils.isNotBlank(uri.getScheme()); + } catch (URISyntaxException | IllegalArgumentException | FileSystemNotFoundException + | SecurityException e) { + return false; + } + } + + /** + * Populate the response body with the given error message, and mark the success flag to false. At last return the response object back. + * + * @param response + * the response object + * @param errorCode + * the error code + * @param errorMessage + * the error message + * @return the modified response object. + */ + public static Response setErrorResponse(Response response, ErrorCode errorCode, String errorMessage) { + response.body = new Responses.ErrorResponseBody(new Types.Message(errorCode.getId(), errorMessage)); + response.message = errorMessage; + response.success = false; + return response; + } + + /** + * Populate the response body with the given exception, and mark the success flag to false. At last return the response object back. + * + * @param response + * the response object + * @param errorCode + * the error code + * @param e + * the exception + * @return the modified response object. + */ + public static Response setErrorResponse(Response response, ErrorCode errorCode, Exception e) { + String errorMessage = e.toString(); + response.body = new Responses.ErrorResponseBody(new Types.Message(errorCode.getId(), errorMessage)); + response.message = errorMessage; + response.success = false; + return response; + } + + /** + * Generate a CompletableFuture response with the given error message. + */ + public static CompletableFuture createAsyncErrorResponse(Response response, ErrorCode errorCode, String errorMessage) { + return CompletableFuture.completedFuture(setErrorResponse(response, errorCode, errorMessage)); + } + + /** + * Generate a CompletableFuture response with the given exception. + */ + public static CompletableFuture createAsyncErrorResponse(Response response, ErrorCode errorCode, Exception e) { + return CompletableFuture.completedFuture(setErrorResponse(response, errorCode, e)); + } + + public static CompletionException createCompletionException(String message, ErrorCode errorCode, Throwable cause) { + return new CompletionException(new DebugException(message, cause, errorCode.getId())); + } + + public static CompletionException createCompletionException(String message, ErrorCode errorCode) { + return new CompletionException(new DebugException(message, errorCode.getId())); + } + + public static DebugException createUserErrorDebugException(String message, ErrorCode errorCode) { + return new DebugException(message, errorCode.getId(), true); + } + + /** + * Calculate SHA-256 Digest of given string. + * @param content + * + * string to digest + * @return string of Hex digest + */ + public static String getSHA256HexDigest(String content) { + byte[] hashBytes = null; + try { + hashBytes = MessageDigest.getInstance("SHA-256").digest(content.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + // ignore it. + } + StringBuffer buf = new StringBuffer(); + if (hashBytes != null) { + for (byte b : hashBytes) { + buf.append(Integer.toHexString((b & 0xFF) + 0x100).substring(1)); + } + } + return buf.toString(); + } + + /** + * Decode the uri string. + * @param uri + * the uri string + * @return the decoded uri + */ + public static String decodeURIComponent(String uri) { + try { + return URLDecoder.decode(uri, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + return uri; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java new file mode 100755 index 0000000..eaf1bb5 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java @@ -0,0 +1,271 @@ +/******************************************************************************* +* Copyright (c) 2017-2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IMethodBreakpoint; +import com.microsoft.java.debug.core.IWatchpoint; + +public class BreakpointManager implements IBreakpointManager { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + /** + * A collection of breakpoints registered with this manager. + */ + private List breakpoints; + private Map> sourceToBreakpoints; + private Map watchpoints; + private Map methodBreakpoints; + private AtomicInteger nextBreakpointId = new AtomicInteger(1); + + /** + * Constructor. + */ + public BreakpointManager() { + this.breakpoints = Collections.synchronizedList(new ArrayList<>(5)); + this.sourceToBreakpoints = new HashMap<>(); + this.watchpoints = new HashMap<>(); + this.methodBreakpoints = new HashMap<>(); + } + + @Override + public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints) { + return setBreakpoints(source, breakpoints, false); + } + + @Override + public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, boolean sourceModified) { + List result = new ArrayList<>(); + HashMap breakpointMap = this.sourceToBreakpoints.get(source); + // When source file is modified, delete all previously added breakpoints. + if (sourceModified && breakpointMap != null) { + for (IBreakpoint bp : breakpointMap.values()) { + try { + // Destroy the breakpoint on the debugee VM. + bp.close(); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e); + } + this.breakpoints.remove(bp); + } + this.sourceToBreakpoints.put(source, null); + breakpointMap = null; + } + if (breakpointMap == null) { + breakpointMap = new HashMap<>(); + this.sourceToBreakpoints.put(source, breakpointMap); + } + + // Compute the breakpoints that are newly added. + List toAdd = new ArrayList<>(); + List visitedBreakpoints = new ArrayList<>(); + for (IBreakpoint breakpoint : breakpoints) { + IBreakpoint existed = breakpointMap.get(String.valueOf(breakpoint.hashCode())); + if (existed != null) { + result.add(existed); + visitedBreakpoints.add(existed.hashCode()); + continue; + } else { + result.add(breakpoint); + } + toAdd.add(breakpoint); + } + + // Compute the breakpoints that are no longer listed. + List toRemove = new ArrayList<>(); + for (IBreakpoint breakpoint : breakpointMap.values()) { + if (!visitedBreakpoints.contains(breakpoint.hashCode())) { + toRemove.add(breakpoint); + } + } + + removeBreakpointsInternally(source, toRemove.toArray(new IBreakpoint[0])); + addBreakpointsInternally(source, toAdd.toArray(new IBreakpoint[0])); + + return result.toArray(new IBreakpoint[0]); + } + + private void addBreakpointsInternally(String source, IBreakpoint[] breakpoints) { + Map breakpointMap = this.sourceToBreakpoints.computeIfAbsent(source, k -> new HashMap<>()); + + if (breakpoints != null && breakpoints.length > 0) { + for (IBreakpoint breakpoint : breakpoints) { + breakpoint.putProperty("id", this.nextBreakpointId.getAndIncrement()); + this.breakpoints.add(breakpoint); + breakpointMap.put(String.valueOf(breakpoint.hashCode()), breakpoint); + } + } + } + + /** + * Removes the specified breakpoints from breakpoint manager. + */ + private void removeBreakpointsInternally(String source, IBreakpoint[] breakpoints) { + Map breakpointMap = this.sourceToBreakpoints.get(source); + if (breakpointMap == null || breakpointMap.isEmpty() || breakpoints.length == 0) { + return; + } + + for (IBreakpoint breakpoint : breakpoints) { + if (this.breakpoints.contains(breakpoint)) { + try { + // Destroy the breakpoint on the debugee VM. + breakpoint.close(); + this.breakpoints.remove(breakpoint); + breakpointMap.remove(String.valueOf(breakpoint.hashCode())); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e); + } + } + } + } + + @Override + public IBreakpoint[] getBreakpoints() { + return this.breakpoints.toArray(new IBreakpoint[0]); + } + + @Override + public IBreakpoint[] getBreakpoints(String source) { + HashMap breakpointMap = this.sourceToBreakpoints.get(source); + if (breakpointMap == null) { + return new IBreakpoint[0]; + } + return breakpointMap.values().toArray(new IBreakpoint[0]); + } + + @Override + public IWatchpoint[] setWatchpoints(IWatchpoint[] changedWatchpoints) { + List result = new ArrayList<>(); + List toAdds = new ArrayList<>(); + List toRemoves = new ArrayList<>(); + + Set visitedKeys = new HashSet<>(); + for (IWatchpoint change : changedWatchpoints) { + if (change == null) { + result.add(change); + continue; + } + + String key = getWatchpointKey(change); + IWatchpoint cache = watchpoints.get(key); + if (cache != null && Objects.equals(cache.accessType(), change.accessType())) { + visitedKeys.add(key); + result.add(cache); + } else { + toAdds.add(change); + result.add(change); + } + } + + for (IWatchpoint cache : watchpoints.values()) { + if (!visitedKeys.contains(getWatchpointKey(cache))) { + toRemoves.add(cache); + } + } + + for (IWatchpoint toRemove : toRemoves) { + try { + // Destroy the watch point on the debugee VM. + toRemove.close(); + this.watchpoints.remove(getWatchpointKey(toRemove)); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Remove the watch point exception: %s", e.toString()), e); + } + } + + for (IWatchpoint toAdd : toAdds) { + toAdd.putProperty("id", this.nextBreakpointId.getAndIncrement()); + this.watchpoints.put(getWatchpointKey(toAdd), toAdd); + } + + return result.toArray(new IWatchpoint[0]); + } + + private String getWatchpointKey(IWatchpoint watchpoint) { + return watchpoint.className() + "#" + watchpoint.fieldName(); + } + + @Override + public IWatchpoint[] getWatchpoints() { + return this.watchpoints.values().stream().filter(wp -> wp != null).toArray(IWatchpoint[]::new); + } + + @Override + public IMethodBreakpoint[] getMethodBreakpoints() { + return this.methodBreakpoints.values().stream().filter(Objects::nonNull).toArray(IMethodBreakpoint[]::new); + } + + @Override + public IMethodBreakpoint[] setMethodBreakpoints(IMethodBreakpoint[] breakpoints) { + List result = new ArrayList<>(); + List toAdds = new ArrayList<>(); + List toRemoves = new ArrayList<>(); + + Set visitedKeys = new HashSet<>(); + for (IMethodBreakpoint change : breakpoints) { + if (change == null) { + result.add(change); + continue; + } + + String key = getMethodBreakpointKey(change); + IMethodBreakpoint cache = methodBreakpoints.get(key); + if (cache != null) { + visitedKeys.add(key); + result.add(cache); + } else { + toAdds.add(change); + result.add(change); + } + } + + for (IMethodBreakpoint cache : methodBreakpoints.values()) { + if (!visitedKeys.contains(getMethodBreakpointKey(cache))) { + toRemoves.add(cache); + } + } + + for (IMethodBreakpoint toRemove : toRemoves) { + try { + // Destroy the method breakpoint on the debugee VM. + toRemove.close(); + this.methodBreakpoints.remove(getMethodBreakpointKey(toRemove)); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Remove the method breakpoint exception: %s", e.toString()), e); + } + } + + for (IMethodBreakpoint toAdd : toAdds) { + toAdd.putProperty("id", this.nextBreakpointId.getAndIncrement()); + this.methodBreakpoints.put(getMethodBreakpointKey(toAdd), toAdd); + } + + return result.toArray(new IMethodBreakpoint[0]); + } + + private String getMethodBreakpointKey(IMethodBreakpoint breakpoint) { + return breakpoint.className() + "#" + breakpoint.methodName(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java b/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java new file mode 100755 index 0000000..2e523ab --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java @@ -0,0 +1,18 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +public final class Constants { + public static final String PROJECT_NAME = "projectName"; + public static final String DEBUGGEE_ENCODING = "debuggeeEncoding"; + public static final String MAIN_CLASS = "mainClass"; +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java b/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java new file mode 100755 index 0000000..b853e04 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.adapter.handler.AttachRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.BreakpointLocationsRequestHander; +import com.microsoft.java.debug.core.adapter.handler.CompletionsHandler; +import com.microsoft.java.debug.core.adapter.handler.ConfigurationDoneRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.DataBreakpointInfoRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.DisconnectRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.DisconnectRequestWithoutDebuggingHandler; +import com.microsoft.java.debug.core.adapter.handler.EvaluateRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.ExceptionInfoRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.HotCodeReplaceHandler; +import com.microsoft.java.debug.core.adapter.handler.InitializeRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.InlineValuesRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.LaunchRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.ProcessIdHandler; +import com.microsoft.java.debug.core.adapter.handler.RefreshVariablesHandler; +import com.microsoft.java.debug.core.adapter.handler.RestartFrameHandler; +import com.microsoft.java.debug.core.adapter.handler.ScopesRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.SetBreakpointsRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.SetDataBreakpointsRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.SetExceptionBreakpointsRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.SetFunctionBreakpointsRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.SetVariableRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.SourceRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.StackTraceRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.StepInTargetsRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.StepRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.ThreadsRequestHandler; +import com.microsoft.java.debug.core.adapter.handler.VariablesRequestHandler; +import com.microsoft.java.debug.core.protocol.IProtocolServer; +import com.microsoft.java.debug.core.protocol.JsonUtils; +import com.microsoft.java.debug.core.protocol.Messages; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; + +public class DebugAdapter implements IDebugAdapter { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + private IDebugAdapterContext debugContext = null; + private Map> requestHandlersForDebug = null; + private Map> requestHandlersForNoDebug = null; + + /** + * Constructor. + */ + public DebugAdapter(IProtocolServer server, IProviderContext providerContext) { + this.debugContext = new DebugAdapterContext(server, providerContext); + requestHandlersForDebug = new HashMap<>(); + requestHandlersForNoDebug = new HashMap<>(); + initialize(); + } + + @Override + public CompletableFuture dispatchRequest(Messages.Request request) { + Messages.Response response = new Messages.Response(); + response.request_seq = request.seq; + response.command = request.command; + response.success = true; + + Command command = Command.parse(request.command); + Arguments cmdArgs = JsonUtils.fromJson(request.arguments, command.getArgumentType()); + + if (debugContext.isVmTerminated() && command != Command.DISCONNECT) { + return CompletableFuture.completedFuture(response); + } + List handlers = this.debugContext.getLaunchMode() == LaunchMode.DEBUG + ? requestHandlersForDebug.get(command) : requestHandlersForNoDebug.get(command); + if (handlers != null && !handlers.isEmpty()) { + CompletableFuture future = CompletableFuture.completedFuture(response); + for (IDebugRequestHandler handler : handlers) { + future = future.thenCompose((res) -> { + return handler.handle(command, cmdArgs, res, debugContext); + }); + } + return future; + } else { + final String errorMessage = String.format("Unrecognized request: { _request: %s }", request.command); + logger.log(Level.SEVERE, errorMessage); + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.UNRECOGNIZED_REQUEST_FAILURE, errorMessage); + } + } + + private void initialize() { + // Register request handlers. + // When there are multiple handlers registered for the same request, follow the rule "first register, first execute". + registerHandler(new InitializeRequestHandler()); + registerHandler(new LaunchRequestHandler()); + + // DEBUG mode only + registerHandlerForDebug(new AttachRequestHandler()); + registerHandlerForDebug(new ConfigurationDoneRequestHandler()); + registerHandlerForDebug(new DisconnectRequestHandler()); + registerHandlerForDebug(new SetBreakpointsRequestHandler()); + registerHandlerForDebug(new SetExceptionBreakpointsRequestHandler()); + registerHandlerForDebug(new SourceRequestHandler()); + registerHandlerForDebug(new ThreadsRequestHandler()); + registerHandlerForDebug(new StepRequestHandler()); + registerHandlerForDebug(new StackTraceRequestHandler()); + registerHandlerForDebug(new ScopesRequestHandler()); + registerHandlerForDebug(new VariablesRequestHandler()); + registerHandlerForDebug(new SetVariableRequestHandler()); + registerHandlerForDebug(new EvaluateRequestHandler()); + registerHandlerForDebug(new HotCodeReplaceHandler()); + registerHandlerForDebug(new RestartFrameHandler()); + registerHandlerForDebug(new CompletionsHandler()); + registerHandlerForDebug(new ExceptionInfoRequestHandler()); + registerHandlerForDebug(new DataBreakpointInfoRequestHandler()); + registerHandlerForDebug(new SetDataBreakpointsRequestHandler()); + registerHandlerForDebug(new InlineValuesRequestHandler()); + registerHandlerForDebug(new RefreshVariablesHandler()); + registerHandlerForDebug(new ProcessIdHandler()); + registerHandlerForDebug(new SetFunctionBreakpointsRequestHandler()); + registerHandlerForDebug(new BreakpointLocationsRequestHander()); + registerHandlerForDebug(new StepInTargetsRequestHandler()); + + // NO_DEBUG mode only + registerHandlerForNoDebug(new DisconnectRequestWithoutDebuggingHandler()); + registerHandlerForNoDebug(new ProcessIdHandler()); + } + + private void registerHandlerForDebug(IDebugRequestHandler handler) { + registerHandler(requestHandlersForDebug, handler); + } + + private void registerHandlerForNoDebug(IDebugRequestHandler handler) { + registerHandler(requestHandlersForNoDebug, handler); + } + + private void registerHandler(IDebugRequestHandler handler) { + registerHandler(requestHandlersForDebug, handler); + registerHandler(requestHandlersForNoDebug, handler); + } + + private void registerHandler(Map> requestHandlers, IDebugRequestHandler handler) { + for (Command command : handler.getTargetCommands()) { + List handlerList = requestHandlers.get(command); + if (handlerList == null) { + handlerList = new ArrayList<>(); + requestHandlers.put(command, handlerList); + } + handler.initialize(debugContext); + handlerList.add(handler); + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java b/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java new file mode 100755 index 0000000..f8ac01c --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java @@ -0,0 +1,421 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.DebugSettings.AsyncMode; +import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter; +import com.microsoft.java.debug.core.adapter.variables.VariableFormatterFactory; +import com.microsoft.java.debug.core.protocol.IProtocolServer; +import com.microsoft.java.debug.core.protocol.Requests.StepFilters; + +import org.apache.commons.lang3.ArrayUtils; + +public class DebugAdapterContext implements IDebugAdapterContext { + private static final int MAX_CACHE_ITEMS = 10000; + private final StepFilters defaultFilters = new StepFilters(); + private Map sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS)); + private IProviderContext providerContext; + private IProtocolServer server; + + private IDebugSession debugSession; + private boolean debuggerLinesStartAt1 = true; + // The Java model on debugger uses 0-based column number. + private boolean debuggerColumnStartAt1 = false; + private boolean debuggerPathsAreUri = true; + private boolean clientLinesStartAt1 = true; + private boolean clientColumnsStartAt1 = true; + private boolean clientPathsAreUri = false; + private boolean supportsRunInTerminalRequest; + private boolean isAttached = false; + private String[] sourcePaths; + private Charset debuggeeEncoding; + private transient boolean vmTerminated; + private boolean isVmStopOnEntry = false; + private LaunchMode launchMode = LaunchMode.DEBUG; + private Process debuggeeProcess; + private String mainClass; + private StepFilters stepFilters; + private Path classpathJar = null; + private Path argsfile = null; + private boolean isInitialized = false; + + private long shellProcessId = -1; + private long processId = -1; + + private boolean localDebugging = true; + private long jdwpLatency = 0; + + private IdCollection sourceReferences = new IdCollection<>(); + private RecyclableObjectPool recyclableIdPool = new RecyclableObjectPool<>(); + private IVariableFormatter variableFormatter = VariableFormatterFactory.createVariableFormatter(); + + private IStackFrameManager stackFrameManager = new StackFrameManager(); + private IExceptionManager exceptionManager = new ExceptionManager(); + private IBreakpointManager breakpointManager = new BreakpointManager(); + private IStepResultManager stepResultManager = new StepResultManager(); + private ThreadCache threadCache = new ThreadCache(); + + public DebugAdapterContext(IProtocolServer server, IProviderContext providerContext) { + this.providerContext = providerContext; + this.server = server; + } + + @Override + public IProtocolServer getProtocolServer() { + return server; + } + + @Override + public T getProvider(Class clazz) { + return providerContext.getProvider(clazz); + } + + @Override + public void setDebugSession(IDebugSession session) { + debugSession = session; + } + + @Override + public IDebugSession getDebugSession() { + return debugSession; + } + + @Override + public boolean isDebuggerLinesStartAt1() { + return debuggerLinesStartAt1; + } + + @Override + public void setDebuggerLinesStartAt1(boolean debuggerLinesStartAt1) { + this.debuggerLinesStartAt1 = debuggerLinesStartAt1; + } + + public boolean isDebuggerColumnsStartAt1() { + return debuggerColumnStartAt1; + } + + @Override + public boolean isDebuggerPathsAreUri() { + return debuggerPathsAreUri; + } + + @Override + public void setDebuggerPathsAreUri(boolean debuggerPathsAreUri) { + this.debuggerPathsAreUri = debuggerPathsAreUri; + } + + @Override + public boolean isClientLinesStartAt1() { + return clientLinesStartAt1; + } + + @Override + public void setClientLinesStartAt1(boolean clientLinesStartAt1) { + this.clientLinesStartAt1 = clientLinesStartAt1; + } + + public boolean isClientColumnsStartAt1() { + return clientColumnsStartAt1; + } + + public void setClientColumnsStartAt1(boolean clientColumnsStartAt1) { + this.clientColumnsStartAt1 = clientColumnsStartAt1; + } + + @Override + public boolean isClientPathsAreUri() { + return clientPathsAreUri; + } + + @Override + public void setClientPathsAreUri(boolean clientPathsAreUri) { + this.clientPathsAreUri = clientPathsAreUri; + } + + @Override + public void setSupportsRunInTerminalRequest(boolean supportsRunInTerminalRequest) { + this.supportsRunInTerminalRequest = supportsRunInTerminalRequest; + } + + @Override + public boolean supportsRunInTerminalRequest() { + return supportsRunInTerminalRequest; + } + + @Override + public boolean isAttached() { + return isAttached; + } + + @Override + public void setAttached(boolean attached) { + isAttached = attached; + } + + @Override + public String[] getSourcePaths() { + return sourcePaths; + } + + @Override + public void setSourcePaths(String[] sourcePaths) { + this.sourcePaths = sourcePaths; + } + + @Override + public String getSourceUri(int sourceReference) { + return sourceReferences.get(sourceReference); + } + + @Override + public int createSourceReference(String uri) { + return sourceReferences.create(uri); + } + + @Override + public RecyclableObjectPool getRecyclableIdPool() { + return recyclableIdPool; + } + + @Override + public void setRecyclableIdPool(RecyclableObjectPool idPool) { + recyclableIdPool = idPool; + } + + @Override + public IVariableFormatter getVariableFormatter() { + return variableFormatter; + } + + @Override + public void setVariableFormatter(IVariableFormatter variableFormatter) { + this.variableFormatter = variableFormatter; + } + + @Override + public Map getSourceLookupCache() { + return sourceMappingCache; + } + + @Override + public void setDebuggeeEncoding(Charset encoding) { + debuggeeEncoding = encoding; + } + + @Override + public Charset getDebuggeeEncoding() { + return debuggeeEncoding; + } + + @Override + public void setVmTerminated() { + vmTerminated = true; + } + + @Override + public boolean isVmTerminated() { + return vmTerminated; + } + + @Override + public void setVmStopOnEntry(boolean stopOnEntry) { + isVmStopOnEntry = stopOnEntry; + } + + @Override + public boolean isVmStopOnEntry() { + return isVmStopOnEntry; + } + + @Override + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + @Override + public String getMainClass() { + return this.mainClass; + } + + @Override + public void setStepFilters(StepFilters stepFilters) { + // For backward compatibility, merge the classNameFilters to skipClasses. + if (stepFilters != null && ArrayUtils.isNotEmpty(stepFilters.classNameFilters)) { + Set patterns = new LinkedHashSet<>(); + if (ArrayUtils.isNotEmpty(stepFilters.skipClasses)) { + patterns.addAll(Arrays.asList(stepFilters.skipClasses)); + } + + patterns.addAll(Arrays.asList(stepFilters.classNameFilters)); + stepFilters.skipClasses = patterns.toArray(new String[0]); + } + this.stepFilters = stepFilters; + } + + @Override + public StepFilters getStepFilters() { + if (stepFilters != null) { + return stepFilters; + } else if (DebugSettings.getCurrent().stepFilters != null) { + return DebugSettings.getCurrent().stepFilters; + } + + return defaultFilters; + } + + @Override + public IStackFrameManager getStackFrameManager() { + return stackFrameManager; + } + + @Override + public LaunchMode getLaunchMode() { + return launchMode; + } + + @Override + public void setLaunchMode(LaunchMode launchMode) { + this.launchMode = launchMode; + } + + @Override + public Process getDebuggeeProcess() { + return this.debuggeeProcess; + } + + @Override + public void setDebuggeeProcess(Process debuggeeProcess) { + this.debuggeeProcess = debuggeeProcess; + } + + @Override + public void setClasspathJar(Path classpathJar) { + this.classpathJar = classpathJar; + } + + @Override + public Path getClasspathJar() { + return this.classpathJar; + } + + @Override + public void setArgsfile(Path argsfile) { + this.argsfile = argsfile; + } + + @Override + public Path getArgsfile() { + return this.argsfile; + } + + @Override + public IExceptionManager getExceptionManager() { + return this.exceptionManager; + } + + @Override + public IBreakpointManager getBreakpointManager() { + return breakpointManager; + } + + @Override + public IStepResultManager getStepResultManager() { + return stepResultManager; + } + + @Override + public long getProcessId() { + return this.processId; + } + + @Override + public long getShellProcessId() { + return this.shellProcessId; + } + + @Override + public void setProcessId(long processId) { + this.processId = processId; + } + + @Override + public void setShellProcessId(long shellProcessId) { + this.shellProcessId = shellProcessId; + } + + @Override + public void setThreadCache(ThreadCache cache) { + this.threadCache = cache; + } + + @Override + public ThreadCache getThreadCache() { + return this.threadCache; + } + + @Override + public boolean asyncJDWP() { + /** + * If we take 1 second as the acceptable latency for DAP requests, + * With a single-threaded strategy for handling JDWP requests, + * a latency of about 15ms per JDWP request can ensure the responsiveness + * for most DAPs. It allows sending 66 JDWP requests within 1 seconds, + * which can cover most DAP operations such as breakpoint, threads, + * call stack, step and continue. + */ + return asyncJDWP(15); + } + + @Override + public boolean asyncJDWP(long usableLatency) { + return DebugSettings.getCurrent().asyncJDWP == AsyncMode.ON + || (DebugSettings.getCurrent().asyncJDWP == AsyncMode.AUTO && this.jdwpLatency > usableLatency); + } + + public boolean isLocalDebugging() { + return localDebugging; + } + + public void setLocalDebugging(boolean local) { + this.localDebugging = local; + } + + @Override + public long getJDWPLatency() { + return this.jdwpLatency; + } + + @Override + public void setJDWPLatency(long baseLatency) { + this.jdwpLatency = baseLatency; + } + + @Override + public boolean isInitialized() { + return isInitialized; + } + + @Override + public void setInitialized(boolean isInitialized) { + this.isInitialized = isInitialized; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/ErrorCode.java b/src/main/java/com/microsoft/java/debug/core/adapter/ErrorCode.java new file mode 100755 index 0000000..6cfe523 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/ErrorCode.java @@ -0,0 +1,68 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.Arrays; + +public enum ErrorCode { + UNKNOWN_FAILURE(1000), + UNRECOGNIZED_REQUEST_FAILURE(1001), + LAUNCH_FAILURE(1002), + ATTACH_FAILURE(1003), + ARGUMENT_MISSING(1004), + SET_BREAKPOINT_FAILURE(1005), + SET_EXCEPTIONBREAKPOINT_FAILURE(1006), + GET_STACKTRACE_FAILURE(1007), + GET_VARIABLE_FAILURE(1008), + SET_VARIABLE_FAILURE(1009), + EVALUATE_FAILURE(1010), + EMPTY_DEBUG_SESSION(1011), + INVALID_ENCODING(1012), + VM_TERMINATED(1013), + LAUNCH_IN_TERMINAL_FAILURE(1014), + STEP_FAILURE(1015), + RESTARTFRAME_FAILURE(1016), + COMPLETIONS_FAILURE(1017), + EXCEPTION_INFO_FAILURE(1018), + EVALUATION_COMPILE_ERROR(2001), + EVALUATE_NOT_SUSPENDED_THREAD(2002), + HCR_FAILURE(3001), + INVALID_DAP_HEADER(3002); + + private int id; + + ErrorCode(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + /** + * Get the corresponding ErrorCode type by the error code id. + * If the error code is not defined in the enum type, return ErrorCode.UNKNOWN_FAILURE. + * @param id + * the error code id. + * @return the ErrorCode type. + */ + public static ErrorCode parse(int id) { + ErrorCode[] found = Arrays.stream(ErrorCode.values()).filter(code -> { + return code.getId() == id; + }).toArray(ErrorCode[]::new); + + if (found.length > 0) { + return found[0]; + } + return ErrorCode.UNKNOWN_FAILURE; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/ExceptionManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/ExceptionManager.java new file mode 100755 index 0000000..b12a6b2 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/ExceptionManager.java @@ -0,0 +1,42 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.microsoft.java.debug.core.JdiExceptionReference; + +public class ExceptionManager implements IExceptionManager { + private Map exceptions = Collections.synchronizedMap(new HashMap<>()); + + @Override + public JdiExceptionReference getException(long threadId) { + return exceptions.get(threadId); + } + + @Override + public JdiExceptionReference removeException(long threadId) { + return exceptions.remove(threadId); + } + + @Override + public JdiExceptionReference setException(long threadId, JdiExceptionReference exception) { + return exceptions.put(threadId, exception); + } + + @Override + public void removeAllExceptions() { + exceptions.clear(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/HotCodeReplaceEvent.java b/src/main/java/com/microsoft/java/debug/core/adapter/HotCodeReplaceEvent.java new file mode 100755 index 0000000..7046b0a --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/HotCodeReplaceEvent.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +public class HotCodeReplaceEvent { + + public enum EventType { + ERROR(-1), + + WARNING(-2), + + STARTING(1), + + END(2), + + BUILD_COMPLETE(3); + + private int value; + + private EventType(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + } + + private EventType eventType; + + private String message; + + private Object data; + + public HotCodeReplaceEvent(EventType eventType, String message) { + this.eventType = eventType; + this.message = message; + } + + public HotCodeReplaceEvent(EventType eventType, String message, Object data) { + this(eventType, message); + this.data = data; + } + + public EventType getEventType() { + return eventType; + } + + public String getMessage() { + return message; + } + + public Object getData() { + return data; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java new file mode 100755 index 0000000..196714d --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IBreakpointManager.java @@ -0,0 +1,92 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IMethodBreakpoint; +import com.microsoft.java.debug.core.IWatchpoint; + +public interface IBreakpointManager { + + /** + * Update the breakpoints associated with the source file. + * + * @see #setBreakpoints(String, IBreakpoint[], boolean) + * @param source + * source path of breakpoints + * @param breakpoints + * full list of breakpoints that locates in this source file + * @return the full breakpoint list that locates in the source file + */ + IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints); + + /** + * Update the breakpoints associated with the source file. If the requested breakpoints already registered in the breakpoint manager, + * reuse the cached one. Otherwise register the requested breakpoint as a new breakpoint. Besides, delete those not existed any more. + * + *

If the source file is modified, delete all cached breakpoints associated the file first and re-register the new breakpoints.

+ * + * @param source + * source path of breakpoints + * @param breakpoints + * full list of breakpoints that locates in this source file + * @param sourceModified + * the source file is modified or not. + * @return the full breakpoint list that locates in the source file + */ + IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, boolean sourceModified); + + /** + * Update the watchpoint list. If the requested watchpoint already registered in the breakpoint manager, + * reuse the cached one. Otherwise register the requested watchpoint as a new watchpoint. + * Besides, delete those not existed any more. + * + * @param watchpoints + * the watchpoints requested by client + * @return the full registered watchpoints list + */ + IWatchpoint[] setWatchpoints(IWatchpoint[] watchpoints); + + /** + * Returns all registered breakpoints. + */ + IBreakpoint[] getBreakpoints(); + + /** + * Returns the registered breakpoints at the source file. + */ + IBreakpoint[] getBreakpoints(String source); + + /** + * Returns all registered watchpoints. + */ + IWatchpoint[] getWatchpoints(); + + /** + * Returns all the registered method breakpoints. + */ + IMethodBreakpoint[] getMethodBreakpoints(); + + /** + * Update the method breakpoints list. If the requested method breakpoints + * already registered in the breakpoint + * manager, reuse the cached one. Otherwise register the requested method + * breakpoints as a new method breakpoints. + * Besides, delete those not existed any more. + * + * @param methodBreakpoints + * the method breakpoints requested by client + * @return the full registered method breakpoints list + */ + IMethodBreakpoint[] setMethodBreakpoints(IMethodBreakpoint[] methodBreakpoints); + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/ICompletionsProvider.java b/src/main/java/com/microsoft/java/debug/core/adapter/ICompletionsProvider.java new file mode 100755 index 0000000..6597ff0 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/ICompletionsProvider.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2018 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.List; + +import com.microsoft.java.debug.core.protocol.Types.CompletionItem; +import com.sun.jdi.StackFrame; + +public interface ICompletionsProvider extends IProvider { + + /** + * Complete the code snippet on the target frame. + * + * @param frame + * the target frame that the completions on + * @param snippet + * the code snippet text + * @param line + * the line number of the operation happens inside the snippet + * @param column + * the column number of the operation happens inside the snippet + * @return a list of {@link CompletionItem} + */ + List codeComplete(StackFrame frame, String snippet, int line, int column); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapter.java b/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapter.java new file mode 100755 index 0000000..72405da --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapter.java @@ -0,0 +1,20 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.protocol.Messages; + +public interface IDebugAdapter { + CompletableFuture dispatchRequest(Messages.Request request); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java b/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java new file mode 100755 index 0000000..9a38e85 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2017-2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Map; + +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter; +import com.microsoft.java.debug.core.protocol.IProtocolServer; +import com.microsoft.java.debug.core.protocol.Requests.StepFilters; + +public interface IDebugAdapterContext { + IProtocolServer getProtocolServer(); + + T getProvider(Class clazz); + + /** + * Set the debug session. + * @param session + * the new debug session + */ + void setDebugSession(IDebugSession session); + + /** + * Get the debug session. + * + * @return the debug session. + */ + IDebugSession getDebugSession(); + + boolean isDebuggerLinesStartAt1(); + + void setDebuggerLinesStartAt1(boolean debuggerLinesStartAt1); + + boolean isDebuggerPathsAreUri(); + + void setDebuggerPathsAreUri(boolean debuggerPathsAreUri); + + boolean isClientLinesStartAt1(); + + void setClientLinesStartAt1(boolean clientLinesStartAt1); + + boolean isClientColumnsStartAt1(); + + void setClientColumnsStartAt1(boolean clientColumnsStartAt1); + + boolean isDebuggerColumnsStartAt1(); + + boolean isClientPathsAreUri(); + + void setClientPathsAreUri(boolean clientPathsAreUri); + + void setSupportsRunInTerminalRequest(boolean supportsRunInTerminalRequest); + + boolean supportsRunInTerminalRequest(); + + boolean isAttached(); + + void setAttached(boolean attached); + + String[] getSourcePaths(); + + void setSourcePaths(String[] sourcePaths); + + String getSourceUri(int sourceReference); + + int createSourceReference(String uri); + + RecyclableObjectPool getRecyclableIdPool(); + + void setRecyclableIdPool(RecyclableObjectPool idPool); + + IVariableFormatter getVariableFormatter(); + + void setVariableFormatter(IVariableFormatter variableFormatter); + + Map getSourceLookupCache(); + + void setDebuggeeEncoding(Charset encoding); + + Charset getDebuggeeEncoding(); + + void setVmTerminated(); + + boolean isVmTerminated(); + + void setVmStopOnEntry(boolean stopOnEntry); + + boolean isVmStopOnEntry(); + + void setMainClass(String mainClass); + + String getMainClass(); + + void setStepFilters(StepFilters stepFilters); + + StepFilters getStepFilters(); + + IStackFrameManager getStackFrameManager(); + + LaunchMode getLaunchMode(); + + void setLaunchMode(LaunchMode launchMode); + + Process getDebuggeeProcess(); + + void setDebuggeeProcess(Process debuggeeProcess); + + void setClasspathJar(Path classpathJar); + + Path getClasspathJar(); + + void setArgsfile(Path argsfile); + + Path getArgsfile(); + + IExceptionManager getExceptionManager(); + + IBreakpointManager getBreakpointManager(); + + IStepResultManager getStepResultManager(); + + void setShellProcessId(long shellProcessId); + + long getShellProcessId(); + + void setProcessId(long processId); + + long getProcessId(); + + void setThreadCache(ThreadCache cache); + + ThreadCache getThreadCache(); + + boolean asyncJDWP(); + + boolean asyncJDWP(long usableLatency/**ms*/); + + boolean isLocalDebugging(); + + void setLocalDebugging(boolean local); + + long getJDWPLatency(); + + void setJDWPLatency(long baseLatency); + + boolean isInitialized(); + + void setInitialized(boolean isInitialized); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IDebugRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/IDebugRequestHandler.java new file mode 100755 index 0000000..96066fd --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IDebugRequestHandler.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; + +public interface IDebugRequestHandler { + List getTargetCommands(); + + default void initialize(IDebugAdapterContext context) { + } + + CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context); + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IEvaluationProvider.java b/src/main/java/com/microsoft/java/debug/core/adapter/IEvaluationProvider.java new file mode 100755 index 0000000..9f21c9d --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IEvaluationProvider.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2017-2019 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.IEvaluatableBreakpoint; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Value; + +/** + * An evaluation engine performs an evaluation of a code snippet or expression + * in a specified thread of a debug target. An evaluation engine is associated + * with a specific debug target and Java project on creation. + */ +public interface IEvaluationProvider extends IProvider { + /** + * This method provides the event hub the ability to exclude the breakpoint event raised during evaluation. + * @param thread the thread to be checked against evaluation work. + * @return whether or not the thread is performing evaluation + */ + boolean isInEvaluation(ThreadReference thread); + + /** + * Evaluate the expression in the context of the specified stack frame, return the promise which is to be resolved/rejected when + * the evaluation finishes. + * + * @param expression The expression to be evaluated + * @param thread The suspended thread the evaluation will be executed at + * @param depth The stack frame depth in the suspended thread + * @return the evaluation result future + */ + CompletableFuture evaluate(String expression, ThreadReference thread, int depth); + + /** + * Evaluate the expression in the context of the specified 'this' object, return the promise which is to be resolved/rejected when + * the evaluation finishes. + * @param expression The expression to be evaluated + * @param thisContext The 'this' context for the evaluation + * @param thread The suspended thread which the evaluation will be executed at + * @return the evaluation result future + */ + CompletableFuture evaluate(String expression, ObjectReference thisContext, ThreadReference thread); + + /** + * Evaluate the conditional breakpoint or logpoint at the given thread and return the promise which is to be resolved/rejected when + * the evaluation finishes. + * + * @param breakpoint The evaluatable breakpoint + * @param thread The jdi thread to the expression will be executed at + * @return the evaluation result future + */ + CompletableFuture evaluateForBreakpoint(IEvaluatableBreakpoint breakpoint, ThreadReference thread); + + /** + * Invoke the specified method with the given arguments at this object and the given thread, and return the result. + * The given thread is resumed to perform the method invocation. The thread will suspend in its originallocation when the method invocation is complete. + * @param thisContext The 'this' context for the invocation + * @param methodName The method to be invoked + * @param methodSignature The JNI style signature of the method to be invoked + * @param args The arguments of the method, which can be null or empty if there are none + * @param thread The thread in which to invoke the method + * @param invokeSuper true if the method lookup should begin in thisobject's superclass + * @return The result of invoking the method + */ + CompletableFuture invokeMethod(ObjectReference thisContext, String methodName, String methodSignature, + Value[] args, ThreadReference thread, boolean invokeSuper); + + /** + * Call this method when the thread is to be resumed by user, it will first cancel ongoing evaluation tasks on specified thread and + * ensure the inner states is cleaned. + * + * @param thread the JDI thread reference where the evaluation task is executing at + */ + void clearState(ThreadReference thread); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IExceptionManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/IExceptionManager.java new file mode 100755 index 0000000..eca1c80 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IExceptionManager.java @@ -0,0 +1,38 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import com.microsoft.java.debug.core.JdiExceptionReference; + +public interface IExceptionManager { + /** + * Returns the Exception associated with the thread. + */ + JdiExceptionReference getException(long threadId); + + /** + * Removes the Exception associated with the thread. Returns the previous Exception mapping to the thread, + * null if no mapping exists. + */ + JdiExceptionReference removeException(long threadId); + + /** + * Associates an Exception with the thread. Returns the previous Exception mapping to the thread, + * null if no mapping exists before. + */ + JdiExceptionReference setException(long threadId, JdiExceptionReference exception); + + /** + * Clear all Exceptions. + */ + void removeAllExceptions(); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IHotCodeReplaceProvider.java b/src/main/java/com/microsoft/java/debug/core/adapter/IHotCodeReplaceProvider.java new file mode 100755 index 0000000..0760c43 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IHotCodeReplaceProvider.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import io.reactivex.Observable; + +public interface IHotCodeReplaceProvider extends IProvider { + void onClassRedefined(Consumer> consumer); + + CompletableFuture> redefineClasses(); + + Observable getEventHub(); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IProvider.java b/src/main/java/com/microsoft/java/debug/core/adapter/IProvider.java new file mode 100755 index 0000000..86c3092 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IProvider.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.Map; + +public interface IProvider { + /** + * Initialize this provider. + * + * @param debugContext + * The associated debug context + * @param options + * the options + */ + default void initialize(IDebugAdapterContext debugContext, Map options) { + } + + /** + * Close the provider and free all associated resources. + */ + default void close() { + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IProviderContext.java b/src/main/java/com/microsoft/java/debug/core/adapter/IProviderContext.java new file mode 100755 index 0000000..c631017 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IProviderContext.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +public interface IProviderContext { + + T getProvider(Class clazz); + + void registerProvider(Class clazz, IProvider provider); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java b/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java new file mode 100755 index 0000000..ead10e3 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2017-2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.List; +import java.util.Objects; + +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.JavaBreakpointLocation; +import com.microsoft.java.debug.core.protocol.Types.SourceBreakpoint; + +public interface ISourceLookUpProvider extends IProvider { + boolean supportsRealtimeBreakpointVerification(); + + /** + * Deprecated, please use {@link #getBreakpointLocations(String, SourceBreakpoint[])} instead. + */ + @Deprecated + String[] getFullyQualifiedName(String uri, int[] lines, int[] columns) throws DebugException; + + /** + * Given a set of source breakpoint locations with line and column numbers, + * verify if they are valid breakpoint locations. If it's a valid location, + * resolve its enclosing class name, method name and signature (for method + * breakpoint) and all possible inline breakpoint locations in that line. + * + * @param sourceUri + * the source file uri + * @param sourceBreakpoints + * the source breakpoints with line and column numbers + * @return Locations of Breakpoints containing context class and method information. + */ + JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceBreakpoint[] sourceBreakpoints) throws DebugException; + + /** + * Given a fully qualified class name and source file path, search the associated disk source file. + * + * @param fullyQualifiedName + * the fully qualified class name (e.g. com.microsoft.java.debug.core.adapter.ISourceLookUpProvider). + * @param sourcePath + * the qualified source file path (e.g. com\microsoft\java\debug\core\adapter\ISourceLookupProvider.java). + * @return the associated source file uri. + */ + String getSourceFileURI(String fullyQualifiedName, String sourcePath); + + String getSourceContents(String uri); + + /** + * Returns the Java runtime that the specified project's build path used. + * @param projectName + * the specified project name + * @return the Java runtime version the specified project used. null if projectName is empty or doesn't exist. + */ + default String getJavaRuntimeVersion(String projectName) { + return null; + } + + /** + * Return method invocation found in the statement as the given line number of + * the source file. + * + * @param uri The source file where the invocation must be searched. + * @param line The line number where the invocation must be searched. + * + * @return List of found method invocation or empty if not method invocations + * can be found. + */ + List findMethodInvocations(String uri, int line); + + public static class MethodInvocation { + public String expression; + public String methodName; + public String methodSignature; + public String methodGenericSignature; + public String declaringTypeName; + public int lineStart; + public int lineEnd; + public int columnStart; + public int columnEnd; + + @Override + public int hashCode() { + return Objects.hash(expression, methodName, methodSignature, methodGenericSignature, declaringTypeName, + lineStart, lineEnd, columnStart, columnEnd); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof MethodInvocation)) { + return false; + } + MethodInvocation other = (MethodInvocation) obj; + return Objects.equals(expression, other.expression) && Objects.equals(methodName, other.methodName) + && Objects.equals(methodSignature, other.methodSignature) + && Objects.equals(methodGenericSignature, other.methodGenericSignature) + && Objects.equals(declaringTypeName, other.declaringTypeName) && lineStart == other.lineStart + && lineEnd == other.lineEnd && columnStart == other.columnStart && columnEnd == other.columnEnd; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IStackFrameManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/IStackFrameManager.java new file mode 100755 index 0000000..8d6c744 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IStackFrameManager.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2017-2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.sun.jdi.StackFrame; +import com.sun.jdi.ThreadReference; + +public interface IStackFrameManager { + /** + * Get a jdi stack frame from stack frame reference. + * + * @param ref the stackframe reference + * @return the jdi stackframe + */ + StackFrame getStackFrame(StackFrameReference ref); + + /** + * Refresh all stackframes from jdi thread. + * + * @param thread the jdi thread + * @return all the stackframes in the specified thread + */ + StackFrame[] reloadStackFrames(ThreadReference thread); + + /** + * Refresh all stackframes from jdi thread. + * + * @param thread the jdi thread + * @param force Whether to load the whole frames if the thread's stackframes haven't been cached. + * @return all the stackframes in the specified thread + */ + StackFrame[] reloadStackFrames(ThreadReference thread, boolean force); + + /** + * Refersh the stackframes starting from the specified depth and length. + * + * @param thread the jdi thread + * @param start the index of the first frame to refresh. Index 0 represents the current frame. + * @param length the number of frames to refersh + * @return the refreshed stackframes + */ + StackFrame[] reloadStackFrames(ThreadReference thread, int start, int length); + + /** + * Clear the stackframes cache from the specified thread. + * + * @param thread the jdi thread + */ + void clearStackFrames(ThreadReference thread); + + /** + * Clear the whole stackframes cache. + */ + void clearStackFrames(); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IStepResultManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/IStepResultManager.java new file mode 100755 index 0000000..c19c0ec --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IStepResultManager.java @@ -0,0 +1,24 @@ +/******************************************************************************* +* Copyright (c) 2020 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import com.microsoft.java.debug.core.JdiMethodResult; + +public interface IStepResultManager { + JdiMethodResult setMethodResult(long threadId, JdiMethodResult methodResult); + + JdiMethodResult getMethodResult(long threadId); + + JdiMethodResult removeMethodResult(long threadId); + + void removeAllMethodResults(); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IVirtualMachineManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/IVirtualMachineManager.java new file mode 100755 index 0000000..095c070 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IVirtualMachineManager.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2020 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +public interface IVirtualMachineManager extends com.sun.jdi.VirtualMachineManager { + boolean connectVirtualMachine(com.sun.jdi.VirtualMachine vm); + + boolean disconnectVirtualMachine(com.sun.jdi.VirtualMachine vm); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IVirtualMachineManagerProvider.java b/src/main/java/com/microsoft/java/debug/core/adapter/IVirtualMachineManagerProvider.java new file mode 100755 index 0000000..8f68807 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IVirtualMachineManagerProvider.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +public interface IVirtualMachineManagerProvider extends IProvider { + com.sun.jdi.VirtualMachineManager getVirtualMachineManager(); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/IdCollection.java b/src/main/java/com/microsoft/java/debug/core/adapter/IdCollection.java new file mode 100755 index 0000000..bec9a82 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/IdCollection.java @@ -0,0 +1,79 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class IdCollection { + private int startId; + private AtomicInteger nextId; + private HashMap idMap; + private HashMap reverseMap; + + public IdCollection() { + this(1); + } + + /** + * Constructs a new id generator with the given startId as the start id number. + * @param startId + * the start id number + */ + public IdCollection(int startId) { + this.startId = startId; + this.nextId = new AtomicInteger(startId); + this.idMap = new HashMap<>(); + this.reverseMap = new HashMap<>(); + } + + /** + * Reset the id to the initial start number. + */ + public void reset() { + this.nextId.set(this.startId); + this.idMap.clear(); + this.reverseMap.clear(); + } + + /** + * Create a new id if the id doesn't exist for the given value. + * Otherwise return the existing id. + */ + public int create(T value) { + if (this.reverseMap.containsKey(value)) { + return this.reverseMap.get(value); + } + int id = this.nextId.getAndIncrement(); + this.idMap.put(id, value); + this.reverseMap.put(value, id); + return id; + } + + /** + * Get the original value by the id. + */ + public T get(int id) { + return this.idMap.get(id); + } + + /** + * Remove the id from the id collection. + */ + public T remove(int id) { + T target = this.idMap.remove(id); + if (target != null) { + this.reverseMap.remove(target); + } + return target; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/LRUCache.java b/src/main/java/com/microsoft/java/debug/core/adapter/LRUCache.java new file mode 100755 index 0000000..250a940 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/LRUCache.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class LRUCache extends LinkedHashMap { + private static final long serialVersionUID = -7068164191168103891L; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + private int cacheSize; + + /** + * Create a LUR cache with the max capacity. + * @param cacheSize the max size of elements in this cache. + */ + public LRUCache(int cacheSize) { + super((int) Math.ceil(cacheSize / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_LOAD_FACTOR, true); + if (cacheSize < 0) { + throw new IllegalArgumentException("cacheSize is negative."); + } + this.cacheSize = cacheSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > cacheSize; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/LaunchMode.java b/src/main/java/com/microsoft/java/debug/core/adapter/LaunchMode.java new file mode 100755 index 0000000..0c3b4ee --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/LaunchMode.java @@ -0,0 +1,17 @@ +/******************************************************************************* +* Copyright (c) 2018 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +public enum LaunchMode { + DEBUG, + NO_DEBUG +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java b/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java new file mode 100755 index 0000000..3d823df --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java @@ -0,0 +1,169 @@ +/******************************************************************************* +* Copyright (c) 2017-2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; + +import com.microsoft.java.debug.core.protocol.Events.OutputEvent.Category; + +import io.reactivex.Observable; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; + +public class ProcessConsole { + private InputStreamObservable stdoutStream; + private InputStreamObservable stderrStream; + private Observable observable = null; + + public ProcessConsole(Process process) { + this(process, "Process", StandardCharsets.UTF_8); + } + + /** + * Constructor. + * @param process + * the process + * @param name + * the process name + * @param encoding + * the process encoding format + */ + public ProcessConsole(Process process, String name, Charset encoding) { + this.stdoutStream = new InputStreamObservable(name + " Stdout Handler", process.getInputStream(), encoding); + this.stderrStream = new InputStreamObservable(name + " Stderr Handler", process.getErrorStream(), encoding); + Observable stdout = this.stdoutStream.messages().map((message) -> new ConsoleMessage(message, Category.stdout)); + Observable stderr = this.stderrStream.messages().map((message) -> new ConsoleMessage(message, Category.stderr)); + this.observable = Observable.mergeArrayDelayError(stdout, stderr).observeOn(Schedulers.newThread()); + } + + /** + * Start monitoring the stdout/stderr streams of the target process. + */ + public void start() { + stdoutStream.start(); + stderrStream.start(); + } + + /** + * Stop monitoring the process console. + */ + public void stop() { + stdoutStream.stop(); + stderrStream.stop(); + } + + public Observable messages() { + return observable; + } + + public Observable stdoutMessages() { + return this.messages().filter((message) -> message.category == Category.stdout); + } + + public Observable stderrMessages() { + return this.messages().filter((message) -> message.category == Category.stderr); + } + + /** + * Split the stdio message to lines, and return them as a new Observable. + */ + public Observable lineMessages() { + return this.messages().map((message) -> { + String[] lines = message.output.split("(?<=\n)"); + return Stream.of(lines).map((line) -> new ConsoleMessage(line, message.category)).toArray(ConsoleMessage[]::new); + }).concatMap((lines) -> Observable.fromArray(lines)); + } + + public static class InputStreamObservable { + private PublishSubject rxSubject = PublishSubject.create(); + private String name; + private InputStream inputStream; + private Charset encoding; + private Thread loopingThread; + + /** + * Constructor. + */ + public InputStreamObservable(String name, InputStream inputStream, Charset encoding) { + this.name = name; + this.inputStream = inputStream; + this.encoding = encoding; + } + + /** + * Starts the stream. + */ + public void start() { + loopingThread = new Thread(name) { + public void run() { + monitor(inputStream, rxSubject); + } + }; + loopingThread.setDaemon(true); + loopingThread.start(); + } + + /** + * Stops the stream. + */ + public void stop() { + if (loopingThread != null) { + loopingThread.interrupt(); + loopingThread = null; + } + } + + private void monitor(InputStream input, PublishSubject subject) { + BufferedReader reader = new BufferedReader(encoding == null ? new InputStreamReader(input) : new InputStreamReader(input, encoding)); + final int BUFFERSIZE = 4096; + char[] buffer = new char[BUFFERSIZE]; + while (true) { + try { + if (Thread.interrupted()) { + subject.onComplete(); + return; + } + int read = reader.read(buffer, 0, BUFFERSIZE); + if (read == -1) { + subject.onComplete(); + return; + } + + subject.onNext(new String(buffer, 0, read)); + } catch (IOException e) { + subject.onError(e); + return; + } + } + } + + public Observable messages() { + return rxSubject; + } + } + + public static class ConsoleMessage { + public String output; + public Category category; + + public ConsoleMessage(String message, Category category) { + this.output = message; + this.category = category; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java b/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java new file mode 100755 index 0000000..0526293 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java @@ -0,0 +1,164 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.UsageDataSession; +import com.microsoft.java.debug.core.protocol.AbstractProtocolServer; +import com.microsoft.java.debug.core.protocol.Events.DebugEvent; +import com.microsoft.java.debug.core.protocol.Events.StoppedEvent; +import com.microsoft.java.debug.core.protocol.Messages; +import com.sun.jdi.VMDisconnectedException; + +public class ProtocolServer extends AbstractProtocolServer { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + private IDebugAdapter debugAdapter; + private UsageDataSession usageDataSession = new UsageDataSession(); + + private Object lock = new Object(); + private boolean isDispatchingRequest = false; + private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue<>(); + + /** + * Constructs a protocol server instance based on the given input stream and output stream. + * @param input + * the input stream + * @param output + * the output stream + * @param context + * provider context for a series of provider implementation + */ + public ProtocolServer(InputStream input, OutputStream output, IProviderContext context) { + super(input, output); + debugAdapter = new DebugAdapter(this, context); + } + + /** + * A while-loop to parse input data and send output data constantly. + */ + @Override + public void run() { + usageDataSession.reportStart(); + super.run(); + usageDataSession.reportStop(); + usageDataSession.submitUsageData(); + } + + @Override + public void sendResponse(Messages.Response response) { + usageDataSession.recordResponse(response); + super.sendResponse(response); + } + + @Override + public CompletableFuture sendRequest(Messages.Request request) { + usageDataSession.recordRequest(request); + return super.sendRequest(request); + } + + @Override + public CompletableFuture sendRequest(Messages.Request request, long timeout) { + usageDataSession.recordRequest(request); + return super.sendRequest(request, timeout); + } + + @Override + public void sendEvent(DebugEvent event) { + // See the two bugs https://github.com/Microsoft/java-debug/issues/134 and https://github.com/Microsoft/vscode/issues/58327, + // it requires the java-debug to send the StoppedEvent after ContinueResponse/StepResponse is received by DA. + if (event instanceof StoppedEvent) { + sendEventLater(event); + } else { + super.sendEvent(event); + } + + } + + /** + * If the the dispatcher is idle, then send the event to the DA immediately. + * Else add the new event to an eventQueue first and send them when dispatcher becomes idle again. + */ + private void sendEventLater(DebugEvent event) { + synchronized (lock) { + if (this.isDispatchingRequest) { + this.eventQueue.offer(event); + } else { + super.sendEvent(event); + } + } + } + + @Override + protected void dispatchRequest(Messages.Request request) { + usageDataSession.recordRequest(request); + try { + synchronized (lock) { + this.isDispatchingRequest = true; + } + + debugAdapter.dispatchRequest(request).thenCompose((response) -> { + CompletableFuture future = new CompletableFuture<>(); + if (response != null) { + sendResponse(response); + future.complete(null); + } else { + future.completeExceptionally(new DebugException("The request dispatcher should not return null response.", + ErrorCode.UNKNOWN_FAILURE.getId())); + } + return future; + }).exceptionally((ex) -> { + Messages.Response response = new Messages.Response(request.seq, request.command); + if (ex instanceof CompletionException && ex.getCause() != null) { + ex = ex.getCause(); + } + + if (ex instanceof VMDisconnectedException) { + // mark it success to avoid reporting error on VSCode. + response.success = true; + sendResponse(response); + } else { + String exceptionMessage = ex.getMessage() != null ? ex.getMessage() : ex.toString(); + ErrorCode errorCode = ex instanceof DebugException ? ErrorCode.parse(((DebugException) ex).getErrorCode()) : ErrorCode.UNKNOWN_FAILURE; + boolean isUserError = ex instanceof DebugException && ((DebugException) ex).isUserError(); + if (isUserError) { + usageDataSession.recordUserError(errorCode); + } else { + logger.log(Level.SEVERE, String.format("[error response][%s]: %s", request.command, exceptionMessage), ex); + } + + sendResponse(AdapterUtils.setErrorResponse(response, + errorCode, + exceptionMessage)); + } + return null; + }).join(); + } finally { + synchronized (lock) { + this.isDispatchingRequest = false; + } + + while (this.eventQueue.peek() != null) { + super.sendEvent(this.eventQueue.poll()); + } + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/ProviderContext.java b/src/main/java/com/microsoft/java/debug/core/adapter/ProviderContext.java new file mode 100755 index 0000000..756eb30 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/ProviderContext.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.HashMap; +import java.util.Map; + +public class ProviderContext implements IProviderContext { + + private Map, IProvider> providerMap; + + public ProviderContext() { + providerMap = new HashMap<>(); + } + + /** + * Get the registered provider with the interface type, + * IllegalArgumentException will raise if the provider is absent. + * The returned object is type-safe to be assigned to T since registerProvider + * will check the compatibility, so suppress unchecked rule. + */ + @SuppressWarnings("unchecked") + @Override + public T getProvider(Class clazz) { + if (!providerMap.containsKey(clazz)) { + throw new IllegalArgumentException(String.format("%s has not been registered.", clazz.getName())); + } + return (T) providerMap.get(clazz); + } + + @Override + public void registerProvider(Class clazz, IProvider provider) { + if (clazz == null) { + throw new IllegalArgumentException("Null provider class is illegal."); + } + + if (provider == null) { + throw new IllegalArgumentException("Null provider is illegal."); + } + + if (providerMap.containsKey(clazz)) { + throw new IllegalArgumentException(String.format("%s has already been registered.", clazz.getName())); + } + + if (!clazz.isInstance(provider)) { + throw new IllegalArgumentException(String.format("The provider doesn't implement interface %s.", clazz.getName())); + } + + if (!clazz.isInterface()) { + throw new IllegalArgumentException("The provider class should be an interface"); + } + + providerMap.put(clazz, provider); + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/RecyclableObjectPool.java b/src/main/java/com/microsoft/java/debug/core/adapter/RecyclableObjectPool.java new file mode 100755 index 0000000..0d1bdf1 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/RecyclableObjectPool.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * An utility object pool class with the following ability: + * 1. store an object to get an object id. + * 2. remove an object. + * 3. remove objects which has specified owner. + * 4. remove all objects. + * + *

It is thread-safe, the duplicate object will not be stored, an object can be referenced by multiple owners, it is + * removed only when user explicitly calls removeObjectById or all the owners has been removed.

+ * + * @param the owner class type + * @param the object type + */ +public class RecyclableObjectPool { + private final IdCollection objectCollection = new IdCollection<>(); + private final Map> referenceMap = new HashMap<>(); + private final Map objectIdMap = new HashMap<>(); + + /** + * Add an object into this pool, if the object is already added, the original id will be used, it will also create a + * reference link from the object to its owner. + * + * @param owner the owner of this object + * @param object the object + * @return the inner id of this object + */ + public int addObject(O owner, V object) { + if (owner == null) { + throw new IllegalArgumentException("Owner cannot be null."); + } + if (object == null) { + throw new IllegalArgumentException("Null object cannot be added."); + } + synchronized (this) { + if (!referenceMap.containsKey(object)) { + // the object is new + Set owners = new HashSet<>(1); + owners.add(owner); + referenceMap.put(object, owners); + int id = objectCollection.create(object); + objectIdMap.put(object, id); + return id; + } else { + // the object is already in this pool + referenceMap.get(object).add(owner); + return objectIdMap.get(object); + } + } + } + + /** + * Get the object by object id. + * + * @param id the object id. + * @return the object, null if the object cannot be found. + */ + public V getObjectById(int id) { + synchronized (this) { + return objectCollection.get(id); + } + } + + /** + * Remove the object by object id. + * + * @param id the object id. + * @return true if the object is removed successfully, false if the object cannot be found. + */ + public boolean removeObjectById(int id) { + synchronized (this) { + V object = this.objectCollection.remove(id); + if (object == null) { + return false; + } + referenceMap.remove(object); + objectIdMap.remove(object); + return true; + } + } + + /** + * Remove a group of objects with the owner, the objects which only refers this owner will be removed. + * + * @param owner the owner. + * @return true if any object is removed. + */ + public boolean removeObjectsByOwner(O owner) { + if (owner == null) { + throw new IllegalArgumentException("owner cannot be null."); + } + synchronized (this) { + List recycling = new ArrayList<>(); + referenceMap.forEach((key, value) -> { + if (value.remove(owner)) { + if (value.isEmpty()) { + recycling.add(key); + } + } + }); + for (V recycled : recycling) { + this.objectCollection.remove(objectIdMap.remove(recycled)); + referenceMap.remove(recycled); + } + return !recycling.isEmpty(); + } + } + + /** + * Removes all the objects. + */ + public void removeAllObjects() { + synchronized (this) { + this.objectCollection.reset(); + this.referenceMap.clear(); + this.objectIdMap.clear(); + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/StackFrameManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/StackFrameManager.java new file mode 100755 index 0000000..518cc77 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/StackFrameManager.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2017-2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.HashMap; +import java.util.Map; + +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.StackFrame; +import com.sun.jdi.ThreadReference; + +public class StackFrameManager implements IStackFrameManager { + private Map threadStackFrameMap = new HashMap<>(); + + @Override + public synchronized StackFrame getStackFrame(StackFrameReference ref) { + ThreadReference thread = ref.getThread(); + int depth = ref.getDepth(); + StackFrame[] frames = threadStackFrameMap.get(thread.uniqueID()); + return frames == null || frames.length < depth ? null : frames[depth]; + } + + @Override + public synchronized StackFrame[] reloadStackFrames(ThreadReference thread) { + return reloadStackFrames(thread, true); + } + + @Override + public synchronized StackFrame[] reloadStackFrames(ThreadReference thread, boolean force) { + return threadStackFrameMap.compute(thread.uniqueID(), (key, old) -> { + try { + if (old == null || old.length == 0) { + if (force) { + return thread.frames().toArray(new StackFrame[0]); + } else { + return new StackFrame[0]; + } + } else { + return thread.frames(0, old.length).toArray(new StackFrame[0]); + } + } catch (IncompatibleThreadStateException e) { + return new StackFrame[0]; + } + }); + } + + @Override + public synchronized StackFrame[] reloadStackFrames(ThreadReference thread, int start, int length) { + long threadId = thread.uniqueID(); + StackFrame[] old = threadStackFrameMap.get(threadId); + try { + StackFrame[] newFrames = thread.frames(start, length).toArray(new StackFrame[0]); + if (old == null || (start == 0 && length == old.length)) { + threadStackFrameMap.put(threadId, newFrames); + } else { + int maxLength = Math.max(old.length, start + length); + StackFrame[] totalFrames = new StackFrame[maxLength]; + System.arraycopy(old, 0, totalFrames, 0, old.length); + System.arraycopy(newFrames, 0, totalFrames, start, length); + threadStackFrameMap.put(threadId, totalFrames); + } + + return newFrames; + } catch (IncompatibleThreadStateException | IndexOutOfBoundsException e) { + return new StackFrame[0]; + } + } + + @Override + public synchronized void clearStackFrames(ThreadReference thread) { + threadStackFrameMap.remove(thread.uniqueID()); + } + + @Override + public synchronized void clearStackFrames() { + threadStackFrameMap.clear(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/StepResultManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/StepResultManager.java new file mode 100755 index 0000000..dd39371 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/StepResultManager.java @@ -0,0 +1,42 @@ +/******************************************************************************* +* Copyright (c) 2020 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.microsoft.java.debug.core.JdiMethodResult; + +public class StepResultManager implements IStepResultManager { + private Map methodResults = Collections.synchronizedMap(new HashMap<>()); + + @Override + public JdiMethodResult setMethodResult(long threadId, JdiMethodResult methodResult) { + return this.methodResults.put(threadId, methodResult); + } + + @Override + public JdiMethodResult getMethodResult(long threadId) { + return this.methodResults.get(threadId); + } + + @Override + public JdiMethodResult removeMethodResult(long threadId) { + return this.methodResults.remove(threadId); + } + + @Override + public void removeAllMethodResults() { + this.methodResults.clear(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java b/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java new file mode 100755 index 0000000..6dd37f1 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/ThreadCache.java @@ -0,0 +1,116 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.sun.jdi.ThreadReference; + +public class ThreadCache { + private List allThreads = new ArrayList<>(); + private Map threadNameMap = new ConcurrentHashMap<>(); + private Map deathThreads = Collections.synchronizedMap(new LinkedHashMap<>() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return this.size() > 100; + } + }); + private Map eventThreads = new ConcurrentHashMap<>(); + + public synchronized void resetThreads(List threads) { + allThreads.clear(); + allThreads.addAll(threads); + } + + public synchronized List getThreads() { + return allThreads; + } + + public synchronized ThreadReference getThread(long threadId) { + for (ThreadReference thread : allThreads) { + if (threadId == thread.uniqueID()) { + return thread; + } + } + + for (ThreadReference thread : eventThreads.values()) { + if (threadId == thread.uniqueID()) { + return thread; + } + } + + return null; + } + + public void setThreadName(long threadId, String name) { + threadNameMap.put(threadId, name); + } + + public String getThreadName(long threadId) { + return threadNameMap.get(threadId); + } + + public void addDeathThread(long threadId) { + threadNameMap.remove(threadId); + eventThreads.remove(threadId); + deathThreads.put(threadId, true); + } + + public boolean isDeathThread(long threadId) { + return deathThreads.containsKey(threadId); + } + + public void addEventThread(ThreadReference thread) { + eventThreads.put(thread.uniqueID(), thread); + } + + public void removeEventThread(long threadId) { + eventThreads.remove(threadId); + } + + public void clearEventThread() { + eventThreads.clear(); + } + + /** + * The visible threads includes: + * 1. The currently running threads returned by the JDI API + * VirtualMachine.allThreads(). + * 2. The threads suspended by events such as Breakpoint, Step, Exception etc. + * + * The part 2 is mainly for virtual threads, since VirtualMachine.allThreads() + * does not include virtual threads by default. For those virtual threads + * that are suspended, we need to show their call stacks in CALL STACK view. + */ + public List visibleThreads(IDebugAdapterContext context) { + List visibleThreads = new ArrayList<>(context.getDebugSession().getAllThreads()); + Set idSet = new HashSet<>(); + visibleThreads.forEach(thread -> idSet.add(thread.uniqueID())); + for (ThreadReference thread : eventThreads.values()) { + if (idSet.contains(thread.uniqueID())) { + continue; + } + + idSet.add(thread.uniqueID()); + visibleThreads.add(thread); + } + + return visibleThreads; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ArrayObjectFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ArrayObjectFormatter.java new file mode 100755 index 0000000..a6dee13 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ArrayObjectFormatter.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.ARRAY; + +import java.util.Map; +import java.util.function.BiFunction; + +import com.sun.jdi.ArrayReference; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public class ArrayObjectFormatter extends ObjectFormatter { + public ArrayObjectFormatter(BiFunction, String> typeStringFunction) { + super(typeStringFunction); + } + + @Override + protected String getPrefix(ObjectReference value, Map options) { + String arrayTypeWithLength = String.format("[%s]", + NumericFormatter.formatNumber(arrayLength(value), options)); + return super.getPrefix(value, options).replaceFirst("\\[]", arrayTypeWithLength); + } + + @Override + public boolean acceptType(Type type, Map options) { + return type != null && type.signature().charAt(0) == ARRAY; + } + + private static int arrayLength(Value value) { + return ((ArrayReference) value).length(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/BooleanFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/BooleanFormatter.java new file mode 100755 index 0000000..ac13fb8 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/BooleanFormatter.java @@ -0,0 +1,43 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.BOOLEAN; + +import java.util.Map; + +import com.sun.jdi.Type; +import com.sun.jdi.Value; +import com.sun.jdi.VirtualMachine; + +public class BooleanFormatter implements IValueFormatter { + + @Override + public String toString(Object value, Map options) { + return value == null ? NullObjectFormatter.NULL_STRING : value.toString(); + } + + @Override + public boolean acceptType(Type type, Map options) { + if (type == null) { + return false; + } + char signature0 = type.signature().charAt(0); + return signature0 == BOOLEAN; + } + + @Override + public Value valueOf(String value, Type type, Map options) { + VirtualMachine vm = type.virtualMachine(); + return vm.mirrorOf(Boolean.parseBoolean(value)); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/CharacterFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/CharacterFormatter.java new file mode 100755 index 0000000..a8639ec --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/CharacterFormatter.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CHAR; + +import java.util.Map; + +import com.sun.jdi.Type; +import com.sun.jdi.Value; +import com.sun.jdi.VirtualMachine; + +public class CharacterFormatter implements IValueFormatter { + + @Override + public String toString(Object value, Map options) { + return value == null ? NullObjectFormatter.NULL_STRING : value.toString(); + } + + @Override + public boolean acceptType(Type type, Map options) { + if (type == null) { + return false; + } + char signature0 = type.signature().charAt(0); + return signature0 == CHAR; + } + + @Override + public Value valueOf(String value, Type type, Map options) { + VirtualMachine vm = type.virtualMachine(); + if (value == null) { + return null; + } + if (value.length() == 3 + && value.startsWith("'") + && value.endsWith("'")) { + return type.virtualMachine().mirrorOf(value.charAt(1)); + } + return vm.mirrorOf(value.charAt(0)); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ClassObjectFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ClassObjectFormatter.java new file mode 100755 index 0000000..8049148 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ClassObjectFormatter.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CLASS_OBJECT; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CLASS_SIGNATURE; + +import java.util.Map; +import java.util.function.BiFunction; + +import com.sun.jdi.ClassObjectReference; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.Type; + +public class ClassObjectFormatter extends ObjectFormatter { + public ClassObjectFormatter(BiFunction, String> typeStringFunction) { + super(typeStringFunction); + } + + @Override + protected String getPrefix(ObjectReference value, Map options) { + Type classType = ((ClassObjectReference) value).reflectedType(); + return String.format("%s (%s)", super.getPrefix(value, options), + typeToStringFunction.apply(classType, options)); + } + + @Override + public boolean acceptType(Type type, Map options) { + return super.acceptType(type, options) && (type.signature().charAt(0) == CLASS_OBJECT + || type.signature().equals(CLASS_SIGNATURE)); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/IFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/IFormatter.java new file mode 100755 index 0000000..38a7a4c --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/IFormatter.java @@ -0,0 +1,46 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import java.util.HashMap; +import java.util.Map; + +import com.sun.jdi.Type; + +public interface IFormatter { + + /** + * Get the string representations for an object. + * + * @param value the value + * @param options additional information about expected format. + * @return the string representations. + */ + String toString(Object value, Map options); + + /** + * The conditional function for this formatter. + * + * @param type the JDI type + * @param options additional information about expected format + * @return whether or not this formatter is expected to work on this type. + */ + boolean acceptType(Type type, Map options); + + /** + * Get the default options for this formatter. + * @return the default options + */ + default Map getDefaultOptions() { + return new HashMap<>(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ITypeFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ITypeFormatter.java new file mode 100755 index 0000000..b8a556d --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ITypeFormatter.java @@ -0,0 +1,15 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +public interface ITypeFormatter extends IFormatter { +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/IValueFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/IValueFormatter.java new file mode 100755 index 0000000..e97b217 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/IValueFormatter.java @@ -0,0 +1,30 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import java.util.Map; + +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public interface IValueFormatter extends IFormatter { + /** + * Create the value from string, this method is used in setValue feature + * where converts user-input string to JDI value. + * + * @param value the string text. + * @param type the expected value type. + * @param options additional information about expected format + * @return the JDI value. + */ + Value valueOf(String value, Type type, Map options); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/NullObjectFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/NullObjectFormatter.java new file mode 100755 index 0000000..d84360a --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/NullObjectFormatter.java @@ -0,0 +1,40 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import java.util.Map; + +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public class NullObjectFormatter implements IValueFormatter { + public static final String NULL_STRING = "null"; + + @Override + public String toString(Object value, Map options) { + return NULL_STRING; + } + + @Override + public boolean acceptType(Type type, Map options) { + return type == null; + } + + @Override + public Value valueOf(String value, Type type, Map options) { + if (value == null || NULL_STRING.equals(value)) { + return null; + } + throw new UnsupportedOperationException("Set value is not supported by NullObjectFormatter."); + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/NumericFormatEnum.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/NumericFormatEnum.java new file mode 100755 index 0000000..c9766d6 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/NumericFormatEnum.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +public enum NumericFormatEnum { + HEX, + OCT, + DEC +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/NumericFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/NumericFormatter.java new file mode 100755 index 0000000..b26667b --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/NumericFormatter.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.BYTE; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.DOUBLE; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.FLOAT; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.INT; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.LONG; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.SHORT; + +import java.util.HashMap; +import java.util.Map; + +import com.sun.jdi.Type; +import com.sun.jdi.Value; +import com.sun.jdi.VirtualMachine; + +public class NumericFormatter implements IValueFormatter { + public static final String NUMERIC_FORMAT_OPTION = "numeric_format"; + public static final String NUMERIC_PRECISION_OPTION = "numeric_precision"; + private static final NumericFormatEnum DEFAULT_NUMERIC_FORMAT = NumericFormatEnum.DEC; + private static final int DEFAULT_NUMERIC_PRECISION = 0; + private static final Map enumFormatMap = new HashMap<>(); + + static { + enumFormatMap.put(NumericFormatEnum.DEC, "%d"); + enumFormatMap.put(NumericFormatEnum.HEX, "%#x"); + enumFormatMap.put(NumericFormatEnum.OCT, "%#o"); + } + + /** + * Get the string representations for an object. + * + * @param obj the value object + * @param options extra information for printing + * @return the string representations. + */ + @Override + public String toString(Object obj, Map options) { + Value value = (Value) obj; + char signature0 = value.type().signature().charAt(0); + if (signature0 == LONG + || signature0 == INT + || signature0 == SHORT + || signature0 == BYTE) { + return formatNumber(Long.parseLong(value.toString()), options); + } else if (hasFraction(signature0)) { + return formatFloatDouble(Double.parseDouble(value.toString()), options); + } + + throw new UnsupportedOperationException(String.format("%s is not a numeric type.", value.type().name())); + } + + @Override + public Value valueOf(String value, Type type, Map options) { + VirtualMachine vm = type.virtualMachine(); + char signature0 = type.signature().charAt(0); + if (signature0 == LONG + || signature0 == INT + || signature0 == SHORT + || signature0 == BYTE) { + long number = parseNumber(value); + if (signature0 == LONG) { + return vm.mirrorOf(number); + } else if (signature0 == INT) { + return vm.mirrorOf((int) number); + } else if (signature0 == SHORT) { + return vm.mirrorOf((short) number); + } else if (signature0 == BYTE) { + return vm.mirrorOf((byte) number); + } + } else if (hasFraction(signature0)) { + double doubleNumber = parseFloatDouble(value); + if (signature0 == DOUBLE) { + return vm.mirrorOf(doubleNumber); + } else { + return vm.mirrorOf((float) doubleNumber); + } + } + + throw new UnsupportedOperationException(String.format("%s is not a numeric type.", type.name())); + } + + + /** + * The conditional function for this formatter. + * + * @param type the JDI type + * @return whether or not this formatter is expected to work on this value. + */ + @Override + public boolean acceptType(Type type, Map options) { + if (type == null) { + return false; + } + char signature0 = type.signature().charAt(0); + return signature0 == LONG + || signature0 == INT + || signature0 == SHORT + || signature0 == BYTE + || signature0 == FLOAT + || signature0 == DOUBLE; + } + + @Override + public Map getDefaultOptions() { + Map options = new HashMap<>(); + options.put(NUMERIC_FORMAT_OPTION, DEFAULT_NUMERIC_FORMAT); + options.put(NUMERIC_PRECISION_OPTION, DEFAULT_NUMERIC_PRECISION); + return options; + } + + static String formatNumber(long value, Map options) { + NumericFormatEnum formatEnum = getNumericFormatOption(options); + return String.format(enumFormatMap.get(formatEnum), value); + } + + private static long parseNumber(String number) { + return Long.decode(number); + } + + private static double parseFloatDouble(String number) { + return Double.parseDouble(number); + } + + private static String formatFloatDouble(double value, Map options) { + int precision = getFractionPrecision(options); + return String.format(precision > 0 ? String.format("%%.%df", precision) : "%f", value); + } + + private static NumericFormatEnum getNumericFormatOption(Map options) { + return options.containsKey(NUMERIC_FORMAT_OPTION) + ? (NumericFormatEnum) options.get(NUMERIC_FORMAT_OPTION) : DEFAULT_NUMERIC_FORMAT; + } + + private static boolean hasFraction(char signature0) { + return signature0 == FLOAT + || signature0 == DOUBLE; + } + + private static int getFractionPrecision(Map options) { + return options.containsKey(NUMERIC_PRECISION_OPTION) + ? (int) options.get(NUMERIC_PRECISION_OPTION) : DEFAULT_NUMERIC_PRECISION; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java new file mode 100755 index 0000000..25a44d2 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2017-2019 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.ARRAY; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CLASS_LOADER; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.CLASS_OBJECT; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.OBJECT; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.STRING; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.THREAD; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.THREAD_GROUP; + +import java.util.Map; +import java.util.function.BiFunction; + +import com.sun.jdi.ObjectReference; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public class ObjectFormatter implements IValueFormatter { + + /** + * The format type function for this object. + */ + protected final BiFunction, String> typeToStringFunction; + + public ObjectFormatter(BiFunction, String> typeToStringFunction) { + this.typeToStringFunction = typeToStringFunction; + } + + @Override + public String toString(Object obj, Map options) { + return String.format("%s@%s", getPrefix((ObjectReference) obj, options), + getIdPostfix((ObjectReference) obj, options)); + } + + @Override + public boolean acceptType(Type type, Map options) { + if (type == null) { + return false; + } + char tag = type.signature().charAt(0); + return (tag == OBJECT) || (tag == ARRAY) || (tag == STRING) + || (tag == THREAD) || (tag == THREAD_GROUP) + || (tag == CLASS_LOADER) + || (tag == CLASS_OBJECT); + } + + @Override + public Value valueOf(String value, Type type, Map options) { + if (value == null || NullObjectFormatter.NULL_STRING.equals(value)) { + return null; + } + throw new UnsupportedOperationException(String.format("Set value is not supported yet for type %s.", type.name())); + } + + /** + * The type with additional prefix before id=${id} of this object.(eg: class, array length) + * @param value The object value. + * @param options additional information about expected format + * @return the type name with additional text + */ + protected String getPrefix(ObjectReference value, Map options) { + return typeToStringFunction.apply(value.type(), options); + } + + protected static String getIdPostfix(ObjectReference obj, Map options) { + return NumericFormatter.formatNumber(obj.uniqueID(), options); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/SimpleTypeFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/SimpleTypeFormatter.java new file mode 100755 index 0000000..2073669 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/SimpleTypeFormatter.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import java.util.HashMap; +import java.util.Map; + +import com.sun.jdi.Type; + +public class SimpleTypeFormatter implements ITypeFormatter { + public static final String QUALIFIED_CLASS_NAME_OPTION = "qualified_class_name"; + private static final boolean DEFAULT_QUALIFIED_CLASS_NAME_OPTION = false; + + /** + * Format a JDI type, using the SimpleTypeFormatter.QUALIFIED_FORMAT_OPTION to control whether or not + * to use the fully qualified name. Set QUALIFIED_FORMAT_OPTION to true(java.lang.Boolean) to enable + * fully qualified name, the default option for QUALIFIED_FORMAT_OPTION is false. + * + * @param type the Jdi type + * @param options the format options + * @return the type name + */ + @Override + public String toString(Object type, Map options) { + if (type == null) { + return NullObjectFormatter.NULL_STRING; + } + + String typeName = ((Type) type).name(); + return showQualifiedClassName(options) ? typeName : trimTypeName(typeName); + } + + @Override + public boolean acceptType(Type type, Map options) { + return true; + } + + @Override + public Map getDefaultOptions() { + Map options = new HashMap<>(); + options.put(QUALIFIED_CLASS_NAME_OPTION, DEFAULT_QUALIFIED_CLASS_NAME_OPTION); + return options; + } + + /** + * An utility method for convert fully qualified class name to the simplified class name. + * @param type the fully qualified class name + * @return the simplified class name + */ + public static String trimTypeName(String type) { + if (type.indexOf('.') >= 0) { + type = type.substring(type.lastIndexOf('.') + 1); + } + return type; + } + + private static boolean showQualifiedClassName(Map options) { + return options.containsKey(QUALIFIED_CLASS_NAME_OPTION) + ? (Boolean) options.get(QUALIFIED_CLASS_NAME_OPTION) : DEFAULT_QUALIFIED_CLASS_NAME_OPTION; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java new file mode 100755 index 0000000..299f7dd --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java @@ -0,0 +1,72 @@ +/******************************************************************************* +* Copyright (c) 2017-2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.STRING; +import static com.microsoft.java.debug.core.adapter.formatter.TypeIdentifiers.STRING_SIGNATURE; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import com.sun.jdi.StringReference; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public class StringObjectFormatter extends ObjectFormatter implements IValueFormatter { + public static final String MAX_STRING_LENGTH_OPTION = "max_string_length"; + private static final int DEFAULT_MAX_STRING_LENGTH = 0; + private static final String QUOTE_STRING = "\""; + + public StringObjectFormatter() { + super(null); + } + + @Override + public Map getDefaultOptions() { + Map options = new HashMap<>(); + options.put(MAX_STRING_LENGTH_OPTION, DEFAULT_MAX_STRING_LENGTH); + return options; + } + + @Override + public String toString(Object value, Map options) { + int maxLength = getMaxStringLength(options); + return String.format("\"%s\"", + maxLength > 0 ? StringUtils.abbreviate(((StringReference) value).value(), maxLength) : ((StringReference) value).value()); + } + + @Override + public boolean acceptType(Type type, Map options) { + return type != null && (type.signature().charAt(0) == STRING + || type.signature().equals(STRING_SIGNATURE)); + } + + @Override + public Value valueOf(String value, Type type, Map options) { + if (value == null || NullObjectFormatter.NULL_STRING.equals(value)) { + return null; + } + if (value.length() >= 2 + && value.startsWith(QUOTE_STRING) + && value.endsWith(QUOTE_STRING)) { + return type.virtualMachine().mirrorOf(StringUtils.substring(value, 1, -1)); + } + return type.virtualMachine().mirrorOf(value); + } + + private static int getMaxStringLength(Map options) { + return options.containsKey(MAX_STRING_LENGTH_OPTION) + ? (int) options.get(MAX_STRING_LENGTH_OPTION) : DEFAULT_MAX_STRING_LENGTH; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/formatter/TypeIdentifiers.java b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/TypeIdentifiers.java new file mode 100755 index 0000000..611c9d2 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/formatter/TypeIdentifiers.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.formatter; + +public final class TypeIdentifiers { + public static final char ARRAY = '['; + public static final char BYTE = 'B'; + public static final char CHAR = 'C'; + public static final char OBJECT = 'L'; + public static final char FLOAT = 'F'; + public static final char DOUBLE = 'D'; + public static final char INT = 'I'; + public static final char LONG = 'J'; + public static final char SHORT = 'S'; + public static final char BOOLEAN = 'Z'; + public static final char STRING = 's'; + public static final char THREAD = 't'; + + public static final char THREAD_GROUP = 'g'; + public static final char CLASS_LOADER = 'l'; + public static final char CLASS_OBJECT = 'c'; + + public static final String STRING_SIGNATURE = "Ljava/lang/String;"; + public static final String CLASS_SIGNATURE = "Ljava/lang/Class;"; +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/AbstractDisconnectRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/AbstractDisconnectRequestHandler.java new file mode 100755 index 0000000..fd5c68d --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/AbstractDisconnectRequestHandler.java @@ -0,0 +1,110 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider; +import com.microsoft.java.debug.core.adapter.LaunchMode; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; + +public abstract class AbstractDisconnectRequestHandler implements IDebugRequestHandler { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.DISCONNECT); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + context.setVmTerminated(); + destroyDebugSession(command, arguments, response, context); + destroyResource(context); + return CompletableFuture.completedFuture(response); + } + + /** + * Destroy the resources generated by the debug session. + * + * @param context the debug context + */ + private void destroyResource(IDebugAdapterContext context) { + destroyProviders(context); + if (shouldDestroyLaunchFiles(context)) { + destroyLaunchFiles(context); + } + } + + private boolean shouldDestroyLaunchFiles(IDebugAdapterContext context) { + // Delete the temporary launch files must happen after the debuggee process is fully exited, + // otherwise it throws error saying the file is being used by other process. + // In Debug mode, the debugger is able to receive VM terminate event. It's sensible to do cleanup. + // In noDebug mode, if the debuggee is launched internally by the debugger, the debugger knows + // when the debuggee process exited. Should do cleanup. But if the debuggee is launched in the + // integrated/external terminal, the debugger lost the contact with the debuggee after it's launched. + // Have no idea when the debuggee is exited. So ignore the cleanup. + return context.getLaunchMode() == LaunchMode.DEBUG || context.getDebuggeeProcess() != null; + } + + private void destroyLaunchFiles(IDebugAdapterContext context) { + // Sometimes when the debug session is terminated, the debuggee process is not exited immediately. + // Add retry to delete the temporary launch files. + int retry = 5; + while (retry-- > 0) { + try { + if (context.getClasspathJar() != null) { + Files.deleteIfExists(context.getClasspathJar()); + context.setClasspathJar(null); + } + + if (context.getArgsfile() != null) { + Files.deleteIfExists(context.getArgsfile()); + context.setArgsfile(null); + } + + break; + } catch (IOException e) { + // do nothing. + logger.log(Level.WARNING, "Failed to destory launch files, will retry again."); + } + + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + // do nothing. + } + } + } + + protected abstract void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context); + + protected void destroyProviders(IDebugAdapterContext context) { + IHotCodeReplaceProvider hcrProvider = context.getProvider(IHotCodeReplaceProvider.class); + if (hcrProvider != null) { + hcrProvider.close(); + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/AttachRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/AttachRequestHandler.java new file mode 100755 index 0000000..5ab848c --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/AttachRequestHandler.java @@ -0,0 +1,144 @@ +/******************************************************************************* +* Copyright (c) 2017-2020 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.UsageDataSession; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.Constants; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.ICompletionsProvider; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; +import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.AttachArguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.request.EventRequest; + +import org.apache.commons.lang3.StringUtils; + +public class AttachRequestHandler implements IDebugRequestHandler { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private VMHandler vmHandler = new VMHandler(); + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.ATTACH); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + AttachArguments attachArguments = (AttachArguments) arguments; + context.setAttached(true); + context.setSourcePaths(attachArguments.sourcePaths); + context.setDebuggeeEncoding(StandardCharsets.UTF_8); // Use UTF-8 as debuggee's default encoding format. + context.setStepFilters(attachArguments.stepFilters); + context.setLocalDebugging(isLocalHost(attachArguments.hostName)); + + Map traceInfo = new HashMap<>(); + traceInfo.put("localAttach", context.isLocalDebugging()); + traceInfo.put("asyncJDWP", context.asyncJDWP()); + + IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class); + vmHandler.setVmProvider(vmProvider); + IDebugSession debugSession = null; + try { + try { + logger.info(String.format("Trying to attach to remote debuggee VM %s:%d .", attachArguments.hostName, attachArguments.port)); + debugSession = DebugUtility.attach(vmProvider.getVirtualMachineManager(), attachArguments.hostName, attachArguments.port, + attachArguments.timeout); + context.setDebugSession(debugSession); + vmHandler.connectVirtualMachine(debugSession.getVM()); + logger.info("Attaching to debuggee VM succeeded."); + } catch (IOException | IllegalConnectorArgumentsException e) { + throw AdapterUtils.createCompletionException( + String.format("Failed to attach to remote debuggee VM. Reason: %s", e.toString()), + ErrorCode.ATTACH_FAILURE, + e); + } + + Map options = new HashMap<>(); + options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding()); + if (attachArguments.projectName != null) { + options.put(Constants.PROJECT_NAME, attachArguments.projectName); + } + // TODO: Clean up the initialize mechanism + ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class); + sourceProvider.initialize(context, options); + // If the debugger and debuggee run at the different JVM platforms, show a warning message. + if (debugSession != null) { + String debuggeeVersion = debugSession.getVM().version(); + String debuggerVersion = sourceProvider.getJavaRuntimeVersion(attachArguments.projectName); + if (StringUtils.isNotBlank(debuggerVersion) && !debuggerVersion.equals(debuggeeVersion)) { + String warnMessage = String.format("[Warn] The debugger and the debuggee are running in different versions of JVMs. " + + "You could see wrong source mapping results.\n" + + "Debugger JVM version: %s\n" + + "Debuggee JVM version: %s", debuggerVersion, debuggeeVersion); + logger.warning(warnMessage); + context.getProtocolServer().sendEvent(Events.OutputEvent.createConsoleOutput(warnMessage)); + } + + EventRequest request = debugSession.getVM().eventRequestManager().createVMDeathRequest(); + request.setSuspendPolicy(EventRequest.SUSPEND_NONE); + long sent = System.currentTimeMillis(); + request.enable(); + long received = System.currentTimeMillis(); + long latency = received - sent; + context.setJDWPLatency(latency); + logger.info("Network latency for JDWP command: " + latency + "ms"); + traceInfo.put("networkLatency", latency); + } + + IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class); + evaluationProvider.initialize(context, options); + IHotCodeReplaceProvider hcrProvider = context.getProvider(IHotCodeReplaceProvider.class); + hcrProvider.initialize(context, options); + ICompletionsProvider completionsProvider = context.getProvider(ICompletionsProvider.class); + completionsProvider.initialize(context, options); + } finally { + UsageDataSession.recordInfo("attach debug info", traceInfo); + } + + // Send an InitializedEvent to indicate that the debugger is ready to accept configuration requests + // (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest). + context.getProtocolServer().sendEvent(new Events.InitializedEvent()); + return CompletableFuture.completedFuture(response); + } + + private boolean isLocalHost(String hostName) { + if (hostName == null || "localhost".equals(hostName) || "127.0.0.1".equals(hostName)) { + return true; + } + + // TODO: Check the host name of current computer as well. + return false; + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/BreakpointLocationsRequestHander.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/BreakpointLocationsRequestHander.java new file mode 100755 index 0000000..153e2f2 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/BreakpointLocationsRequestHander.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.protocol.Requests; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.BreakpointLocationsArguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Types.BreakpointLocation; + +/** + * The breakpointLocations request returns all possible locations for source breakpoints in a given range. + * Clients should only call this request if the corresponding capability supportsBreakpointLocationsRequest is true. + */ +public class BreakpointLocationsRequestHander implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.BREAKPOINTLOCATIONS); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + BreakpointLocationsArguments bpArgs = (BreakpointLocationsArguments) arguments; + String sourceUri = SetBreakpointsRequestHandler.normalizeSourcePath(bpArgs.source, context); + // When breakpoint source path is null or an invalid file path, send an ErrorResponse back. + if (StringUtils.isBlank(sourceUri)) { + throw AdapterUtils.createCompletionException( + String.format("Failed to get BreakpointLocations. Reason: '%s' is an invalid path.", bpArgs.source.path), + ErrorCode.SET_BREAKPOINT_FAILURE); + } + + int debuggerLine = AdapterUtils.convertLineNumber(bpArgs.line, context.isClientLinesStartAt1(), context.isDebuggerLinesStartAt1()); + IBreakpoint[] breakpoints = context.getBreakpointManager().getBreakpoints(sourceUri); + BreakpointLocation[] locations = new BreakpointLocation[0]; + for (int i = 0; i < breakpoints.length; i++) { + if (breakpoints[i].getLineNumber() == debuggerLine && ArrayUtils.isNotEmpty( + breakpoints[i].sourceLocation().availableBreakpointLocations())) { + locations = Stream.of(breakpoints[i].sourceLocation().availableBreakpointLocations()).map(location -> { + BreakpointLocation newLocaiton = new BreakpointLocation(); + newLocaiton.line = AdapterUtils.convertLineNumber(location.line, + context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); + newLocaiton.column = AdapterUtils.convertColumnNumber(location.column, + context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1()); + newLocaiton.endLine = AdapterUtils.convertLineNumber(location.endLine, + context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); + newLocaiton.endColumn = AdapterUtils.convertColumnNumber(location.endColumn, + context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1()); + return newLocaiton; + }).toArray(BreakpointLocation[]::new); + break; + } + } + + response.body = new Responses.BreakpointLocationsResponseBody(locations); + return CompletableFuture.completedFuture(response); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java new file mode 100755 index 0000000..a6704d4 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/CompletionsHandler.java @@ -0,0 +1,82 @@ +/******************************************************************************* +* Copyright (c) 2018 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.ICompletionsProvider; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.CompletionsArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types.CompletionItem; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.ThreadReference; + +public class CompletionsHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.COMPLETIONS); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + CompletionsArguments completionsArgs = (CompletionsArguments) arguments; + + // completions should be illegal when frameId is zero, it is sent when the program is running, while during running we cannot resolve + // the completion candidates + if (completionsArgs.frameId == 0) { + response.body = new Responses.CompletionsResponseBody(Collections.emptyList()); + return CompletableFuture.completedFuture(response); + } + + StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(completionsArgs.frameId); + + if (stackFrameReference == null) { + throw AdapterUtils.createCompletionException( + String.format("Completions: cannot find the stack frame with frameID %s", completionsArgs.frameId), + ErrorCode.COMPLETIONS_FAILURE + ); + } + + return CompletableFuture.supplyAsync(() -> { + try { + ICompletionsProvider completionsProvider = context.getProvider(ICompletionsProvider.class); + if (completionsProvider != null) { + ThreadReference thread = stackFrameReference.getThread(); + + List res = completionsProvider.codeComplete(thread.frame(stackFrameReference.getDepth()), completionsArgs.text, + completionsArgs.line, completionsArgs.column); + response.body = new Responses.CompletionsResponseBody(res); + } + return response; + } catch (IncompatibleThreadStateException e) { + throw AdapterUtils.createCompletionException( + String.format("Cannot provide code completions because of %s.", e.toString()), + ErrorCode.COMPLETIONS_FAILURE, + e + ); + } + }); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java new file mode 100755 index 0000000..6805073 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java @@ -0,0 +1,132 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugEvent; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.JdiExceptionReference; +import com.microsoft.java.debug.core.UsageDataSession; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.ExceptionEvent; +import com.sun.jdi.event.ThreadDeathEvent; +import com.sun.jdi.event.ThreadStartEvent; +import com.sun.jdi.event.VMDeathEvent; +import com.sun.jdi.event.VMDisconnectEvent; +import com.sun.jdi.event.VMStartEvent; + +public class ConfigurationDoneRequestHandler implements IDebugRequestHandler { + protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private VMHandler vmHandler = new VMHandler(); + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.CONFIGURATIONDONE); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + IDebugSession debugSession = context.getDebugSession(); + vmHandler.setVmProvider(context.getProvider(IVirtualMachineManagerProvider.class)); + if (debugSession != null) { + // This is a global event handler to handle the JDI Event from Virtual Machine. + debugSession.getEventHub().events().subscribe(debugEvent -> { + handleDebugEvent(debugEvent, debugSession, context); + }); + // configuration is done, and start debug session. + debugSession.start(); + return CompletableFuture.completedFuture(response); + } else { + context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, "Failed to launch debug session, the debugger will exit."); + } + } + + private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, IDebugAdapterContext context) { + Event event = debugEvent.event; + boolean isImportantEvent = true; + if (event instanceof VMStartEvent) { + if (context.isVmStopOnEntry()) { + DebugUtility.stopOnEntry(debugSession, context.getMainClass()).thenAccept(threadId -> { + context.getProtocolServer().sendEvent(new Events.StoppedEvent("entry", threadId)); + }); + } + } else if (event instanceof VMDeathEvent) { + vmHandler.disconnectVirtualMachine(event.virtualMachine()); + context.setVmTerminated(); + context.getProtocolServer().sendEvent(new Events.ExitedEvent(0)); + } else if (event instanceof VMDisconnectEvent) { + vmHandler.disconnectVirtualMachine(event.virtualMachine()); + if (context.isAttached()) { + context.setVmTerminated(); + context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + // Terminate eventHub thread. + try { + debugSession.getEventHub().close(); + } catch (Exception e) { + // do nothing. + } + } else { + // Skip it when the debugger is in launch mode, because LaunchRequestHandler will handle the event there. + } + } else if (event instanceof ThreadStartEvent) { + ThreadReference startThread = ((ThreadStartEvent) event).thread(); + Events.ThreadEvent threadEvent = new Events.ThreadEvent("started", startThread.uniqueID()); + context.getProtocolServer().sendEvent(threadEvent); + } else if (event instanceof ThreadDeathEvent) { + ThreadReference deathThread = ((ThreadDeathEvent) event).thread(); + Events.ThreadEvent threadDeathEvent = new Events.ThreadEvent("exited", deathThread.uniqueID()); + context.getProtocolServer().sendEvent(threadDeathEvent); + context.getThreadCache().addDeathThread(deathThread.uniqueID()); + } else if (event instanceof BreakpointEvent) { + // ignore since SetBreakpointsRequestHandler has already handled + } else if (event instanceof ExceptionEvent) { + ThreadReference thread = ((ExceptionEvent) event).thread(); + IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class); + if (engine.isInEvaluation(thread)) { + return; + } + + JdiExceptionReference jdiException = new JdiExceptionReference(((ExceptionEvent) event).exception(), + ((ExceptionEvent) event).catchLocation() == null); + context.getExceptionManager().setException(thread.uniqueID(), jdiException); + context.getThreadCache().addEventThread(thread); + context.getProtocolServer().sendEvent(new Events.StoppedEvent("exception", thread.uniqueID())); + debugEvent.shouldResume = false; + } else { + isImportantEvent = false; + } + + // record events of important types only, to get rid of noises. + if (isImportantEvent) { + UsageDataSession.recordEvent(event); + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java new file mode 100755 index 0000000..2d9b6c3 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/DataBreakpointInfoRequestHandler.java @@ -0,0 +1,72 @@ +/******************************************************************************* +* Copyright (c) 2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.adapter.variables.VariableProxy; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.DataBreakpointInfoArguments; +import com.microsoft.java.debug.core.protocol.Responses.DataBreakpointInfoResponseBody; +import com.microsoft.java.debug.core.protocol.Types.DataBreakpointAccessType; +import com.sun.jdi.Field; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; + +public class DataBreakpointInfoRequestHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.DATABREAKPOINTINFO); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + DataBreakpointInfoArguments dataBpArgs = (DataBreakpointInfoArguments) arguments; + if (dataBpArgs.variablesReference > 0) { + Object container = context.getRecyclableIdPool().getObjectById(dataBpArgs.variablesReference); + if (container instanceof VariableProxy) { + if (!(((VariableProxy) container).getProxiedVariable() instanceof StackFrameReference)) { + ObjectReference containerObj = (ObjectReference) ((VariableProxy) container).getProxiedVariable(); + ReferenceType type = containerObj.referenceType(); + Field field = type.fieldByName(dataBpArgs.name); + if (field != null) { + String fullyQualifiedName = type.name(); + String dataId = String.format("%s#%s", fullyQualifiedName, dataBpArgs.name); + String description = String.format("%s.%s : %s", getSimpleName(fullyQualifiedName), dataBpArgs.name, getSimpleName(field.typeName())); + response.body = new DataBreakpointInfoResponseBody(dataId, description, + DataBreakpointAccessType.values(), true); + } + } + } + } + return CompletableFuture.completedFuture(response); + } + + private String getSimpleName(String typeName) { + if (StringUtils.isBlank(typeName)) { + return ""; + } + + String[] names = typeName.split("\\."); + return names[names.length - 1]; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java new file mode 100755 index 0000000..a615e89 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestHandler.java @@ -0,0 +1,35 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.DisconnectArguments; + +public class DisconnectRequestHandler extends AbstractDisconnectRequestHandler { + + @Override + public void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + DisconnectArguments disconnectArguments = (DisconnectArguments) arguments; + IDebugSession debugSession = context.getDebugSession(); + if (debugSession != null) { + if (disconnectArguments.terminateDebuggee && !context.isAttached()) { + debugSession.terminate(); + } else { + debugSession.detach(); + } + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java new file mode 100755 index 0000000..59d750b --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/DisconnectRequestWithoutDebuggingHandler.java @@ -0,0 +1,37 @@ +/******************************************************************************* +* Copyright (c) 2018-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Optional; + +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.DisconnectArguments; + +public class DisconnectRequestWithoutDebuggingHandler extends AbstractDisconnectRequestHandler { + + @Override + public void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + DisconnectArguments disconnectArguments = (DisconnectArguments) arguments; + Process debuggeeProcess = context.getDebuggeeProcess(); + if (debuggeeProcess != null && disconnectArguments.terminateDebuggee) { + debuggeeProcess.destroy(); + } else if (context.getProcessId() > 0 && disconnectArguments.terminateDebuggee) { + Optional debuggeeHandle = ProcessHandle.of(context.getProcessId()); + if (debuggeeHandle.isPresent()) { + debuggeeHandle.get().destroy(); + } + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java new file mode 100755 index 0000000..d135ee5 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java @@ -0,0 +1,207 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils; +import com.microsoft.java.debug.core.adapter.variables.VariableProxy; +import com.microsoft.java.debug.core.adapter.variables.VariableUtils; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.EvaluateArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.sun.jdi.ArrayReference; +import com.sun.jdi.IntegerValue; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.Value; +import com.sun.jdi.VoidValue; + +public class EvaluateRequestHandler implements IDebugRequestHandler { + protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.EVALUATE); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + EvaluateArguments evalArguments = (EvaluateArguments) arguments; + final boolean showStaticVariables = DebugSettings.getCurrent().showStaticVariables; + Map options = context.getVariableFormatter().getDefaultOptions(); + VariableUtils.applyFormatterOptions(options, evalArguments.format != null && evalArguments.format.hex); + String expression = evalArguments.expression; + + // Async mode is supposed to be performant, then disable the advanced features like hover evaluation. + if (context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY) + && context.getJDWPLatency() > VariablesRequestHandler.USABLE_JDWP_LATENCY + && "hover".equals(evalArguments.context)) { + return CompletableFuture.completedFuture(response); + } + + if (StringUtils.isBlank(expression)) { + throw new CompletionException(AdapterUtils.createUserErrorDebugException( + "Failed to evaluate. Reason: Empty expression cannot be evaluated.", + ErrorCode.EVALUATION_COMPILE_ERROR)); + } + StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(evalArguments.frameId); + if (stackFrameReference == null) { + // stackFrameReference is null means the given thread is running + throw new CompletionException(AdapterUtils.createUserErrorDebugException( + "Evaluation failed because the thread is not suspended.", + ErrorCode.EVALUATE_NOT_SUSPENDED_THREAD)); + } + + return CompletableFuture.supplyAsync(() -> { + IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class); + try { + Value value = engine.evaluate(expression, stackFrameReference.getThread(), stackFrameReference.getDepth()).get(); + IVariableFormatter variableFormatter = context.getVariableFormatter(); + if (value instanceof VoidValue) { + response.body = new Responses.EvaluateResponseBody(value.toString(), 0, "", 0); + return response; + } + long threadId = stackFrameReference.getThread().uniqueID(); + if (value instanceof ObjectReference) { + VariableProxy varProxy = new VariableProxy(stackFrameReference.getThread(), "eval", value, null, expression); + int indexedVariables = -1; + Value sizeValue = null; + if (value instanceof ArrayReference) { + indexedVariables = ((ArrayReference) value).length(); + } else if (value instanceof ObjectReference && supportsLogicStructureView(context, evalArguments.context) && engine != null) { + try { + JavaLogicalStructure structure = JavaLogicalStructureManager.getLogicalStructure((ObjectReference) value); + if (structure != null && structure.getSizeExpression() != null) { + sizeValue = structure.getSize((ObjectReference) value, stackFrameReference.getThread(), engine); + if (sizeValue != null && sizeValue instanceof IntegerValue) { + indexedVariables = ((IntegerValue) sizeValue).value(); + } + } + } catch (Exception e) { + logger.log(Level.INFO, "Failed to get the logical size of the variable", e); + } + } + int referenceId = 0; + if (indexedVariables > 0 || (indexedVariables < 0 && value instanceof ObjectReference)) { + referenceId = context.getRecyclableIdPool().addObject(threadId, varProxy); + } + + boolean hasErrors = false; + String valueString = null; + try { + valueString = variableFormatter.valueToString(value, options); + } catch (OutOfMemoryError e) { + hasErrors = true; + logger.log(Level.SEVERE, "Failed to convert the value of a large object to a string", e); + valueString = ""; + } catch (Exception e) { + hasErrors = true; + logger.log(Level.SEVERE, "Failed to resolve the variable value", e); + valueString = ""; + } + + String detailsString = null; + if (hasErrors) { + // If failed to resolve the variable value, skip the details info as well. + } else if (sizeValue != null) { + detailsString = "size=" + variableFormatter.valueToString(sizeValue, options); + } else if (supportsToStringView(context, evalArguments.context)) { + try { + detailsString = VariableDetailUtils.formatDetailsValue(value, stackFrameReference.getThread(), variableFormatter, options, engine); + } catch (OutOfMemoryError e) { + logger.log(Level.SEVERE, "Failed to compute the toString() value of a large object", e); + detailsString = ""; + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to compute the toString() value", e); + detailsString = ""; + } + } + + if ("clipboard".equals(evalArguments.context) && detailsString != null) { + response.body = new Responses.EvaluateResponseBody(detailsString, -1, "String", 0); + } else { + String typeString = ""; + try { + typeString = variableFormatter.typeToString(value == null ? null : value.type(), options); + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to resolve the variable type", e); + typeString = ""; + } + response.body = new Responses.EvaluateResponseBody((detailsString == null) ? valueString : valueString + " " + detailsString, + referenceId, typeString, Math.max(indexedVariables, 0)); + } + return response; + } + // for primitive value + response.body = new Responses.EvaluateResponseBody(variableFormatter.valueToString(value, options), 0, + variableFormatter.typeToString(value == null ? null : value.type(), options), 0); + return response; + } catch (InterruptedException | ExecutionException e) { + Throwable cause = e; + if (e instanceof ExecutionException && e.getCause() != null) { + cause = e.getCause(); + } + + if (cause instanceof DebugException) { + throw new CompletionException(cause); + } + throw AdapterUtils.createCompletionException( + String.format("Cannot evaluate because of %s.", cause.toString()), + ErrorCode.EVALUATE_FAILURE, + cause); + } + }); + } + + private boolean supportsLogicStructureView(IDebugAdapterContext context, String evalContext) { + if (!"watch".equals(evalContext)) { + return true; + } + + return (!context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY) + || context.getJDWPLatency() <= VariablesRequestHandler.USABLE_JDWP_LATENCY) + && DebugSettings.getCurrent().showLogicalStructure; + } + + private boolean supportsToStringView(IDebugAdapterContext context, String evalContext) { + if (!"watch".equals(evalContext)) { + return true; + } + + return (!context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY) + || context.getJDWPLatency() <= VariablesRequestHandler.USABLE_JDWP_LATENCY) + && DebugSettings.getCurrent().showToString; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/ExceptionInfoRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ExceptionInfoRequestHandler.java new file mode 100755 index 0000000..5e065ed --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ExceptionInfoRequestHandler.java @@ -0,0 +1,103 @@ +/******************************************************************************* +* Copyright (c) 2019-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.JdiExceptionReference; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.ExceptionInfoArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types.ExceptionBreakMode; +import com.sun.jdi.ClassNotLoadedException; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.InvalidTypeException; +import com.sun.jdi.InvocationException; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Value; + +public class ExceptionInfoRequestHandler implements IDebugRequestHandler { + protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.EXCEPTIONINFO); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + ExceptionInfoArguments exceptionInfoArgs = (ExceptionInfoArguments) arguments; + ThreadReference thread = context.getThreadCache().getThread(exceptionInfoArgs.threadId); + if (thread == null) { + thread = DebugUtility.getThread(context.getDebugSession(), exceptionInfoArgs.threadId); + } + + if (thread == null) { + throw AdapterUtils.createCompletionException("Thread " + exceptionInfoArgs.threadId + " doesn't exist.", ErrorCode.EXCEPTION_INFO_FAILURE); + } + + JdiExceptionReference jdiException = context.getExceptionManager().getException(exceptionInfoArgs.threadId); + if (jdiException == null) { + throw AdapterUtils.createCompletionException("No exception exists in thread " + exceptionInfoArgs.threadId, ErrorCode.EXCEPTION_INFO_FAILURE); + } + + Method toStringMethod = null; + for (Method method : jdiException.exception.referenceType().allMethods()) { + if (Objects.equals("toString", method.name()) && Objects.equals("()Ljava/lang/String;", method.signature())) { + toStringMethod = method; + break; + } + } + + String typeName = jdiException.exception.type().name(); + String exceptionToString = typeName; + if (toStringMethod != null) { + try { + Value returnValue = jdiException.exception.invokeMethod(thread, toStringMethod, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED); + exceptionToString = returnValue.toString(); + } catch (InvalidTypeException | ClassNotLoadedException | IncompatibleThreadStateException + | InvocationException e) { + logger.log(Level.SEVERE, String.format("Failed to get the return value of the method Exception.toString(): %s", e.toString(), e)); + } finally { + try { + // See bug https://github.com/microsoft/vscode-java-debug/issues/767: + // The operation exception.invokeMethod above will resume the thread, that will cause + // the previously cached stack frames for this thread to be invalid. + context.getStackFrameManager().reloadStackFrames(thread); + } catch (Exception e) { + // do nothing. + } + } + } + + response.body = new Responses.ExceptionInfoResponse(typeName, exceptionToString, + jdiException.isUncaught ? ExceptionBreakMode.USERUNHANDLED : ExceptionBreakMode.ALWAYS); + return CompletableFuture.completedFuture(response); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/HotCodeReplaceHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/HotCodeReplaceHandler.java new file mode 100755 index 0000000..5dfd368 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/HotCodeReplaceHandler.java @@ -0,0 +1,72 @@ +/******************************************************************************* +* Copyright (c) 2017-2019 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.adapter.HotCodeReplaceEvent; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Events.HotCodeReplaceEvent.ChangeType; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Responses; + +public class HotCodeReplaceHandler implements IDebugRequestHandler { + @Override + public List getTargetCommands() { + return Arrays.asList(Command.REDEFINECLASSES); + } + + @Override + public void initialize(IDebugAdapterContext context) { + IDebugRequestHandler.super.initialize(context); + IHotCodeReplaceProvider provider = context.getProvider(IHotCodeReplaceProvider.class); + provider.getEventHub() + .subscribe(event -> { + if (event.getEventType() == HotCodeReplaceEvent.EventType.BUILD_COMPLETE) { + context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.BUILD_COMPLETE, event.getMessage())); + } else if (event.getEventType() == HotCodeReplaceEvent.EventType.STARTING) { + context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.STARTING, event.getMessage())); + } else if (event.getEventType() == HotCodeReplaceEvent.EventType.END) { + context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.END, event.getMessage())); + } else if (event.getEventType() == HotCodeReplaceEvent.EventType.ERROR) { + context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.ERROR, event.getMessage())); + } else if (event.getEventType() == HotCodeReplaceEvent.EventType.WARNING) { + context.getProtocolServer().sendEvent(new Events.HotCodeReplaceEvent(ChangeType.WARNING, event.getMessage())); + } + }); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + + IHotCodeReplaceProvider provider = context.getProvider(IHotCodeReplaceProvider.class); + + return provider.redefineClasses().thenCompose(classNames -> { + response.body = new Responses.RedefineClassesResponse(classNames.toArray(new String[0])); + return CompletableFuture.completedFuture(response); + }).exceptionally(ex -> { + String errorMessage = ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage(); + response.body = new Responses.RedefineClassesResponse(new String[0], errorMessage); + return response; + }); + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/ILaunchDelegate.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ILaunchDelegate.java new file mode 100755 index 0000000..ac12a09 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ILaunchDelegate.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2018 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.connect.VMStartException; + +public interface ILaunchDelegate { + void postLaunch(LaunchArguments launchArguments, IDebugAdapterContext context); + + void preLaunch(LaunchArguments launchArguments, IDebugAdapterContext context); + + CompletableFuture launchInTerminal(LaunchArguments launchArguments, Response response, IDebugAdapterContext context); + + Process launch(LaunchArguments launchArguments, IDebugAdapterContext context) + throws IOException, IllegalConnectorArgumentsException, VMStartException; + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java new file mode 100755 index 0000000..6b92451 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2017-2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.protocol.Messages; +import com.microsoft.java.debug.core.protocol.Requests; +import com.microsoft.java.debug.core.protocol.Types; + +public class InitializeRequestHandler implements IDebugRequestHandler { + @Override + public List getTargetCommands() { + return Arrays.asList(Requests.Command.INITIALIZE); + } + + @Override + public CompletableFuture handle(Requests.Command command, Requests.Arguments argument, Messages.Response response, + IDebugAdapterContext context) { + Requests.InitializeArguments initializeArguments = (Requests.InitializeArguments) argument; + context.setClientLinesStartAt1(initializeArguments.linesStartAt1); + context.setClientColumnsStartAt1(initializeArguments.columnsStartAt1); + String pathFormat = initializeArguments.pathFormat; + if (pathFormat != null) { + switch (pathFormat) { + case "uri": + context.setClientPathsAreUri(true); + break; + default: + context.setClientPathsAreUri(false); + } + } + context.setSupportsRunInTerminalRequest(initializeArguments.supportsRunInTerminalRequest); + + Types.Capabilities caps = new Types.Capabilities(); + caps.supportsConfigurationDoneRequest = true; + caps.supportsHitConditionalBreakpoints = true; + caps.supportsConditionalBreakpoints = true; + caps.supportsSetVariable = true; + caps.supportTerminateDebuggee = true; + caps.supportsCompletionsRequest = true; + caps.supportsRestartFrame = true; + caps.supportsLogPoints = true; + caps.supportsEvaluateForHovers = true; + Types.ExceptionBreakpointFilter[] exceptionFilters = { + Types.ExceptionBreakpointFilter.UNCAUGHT_EXCEPTION_FILTER, + Types.ExceptionBreakpointFilter.CAUGHT_EXCEPTION_FILTER, + }; + caps.exceptionBreakpointFilters = exceptionFilters; + caps.supportsExceptionInfoRequest = true; + caps.supportsDataBreakpoints = true; + caps.supportsFunctionBreakpoints = true; + caps.supportsClipboardContext = true; + caps.supportsBreakpointLocationsRequest = true; + caps.supportsStepInTargetsRequest = true; + response.body = caps; + context.setInitialized(true); + return CompletableFuture.completedFuture(response); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java new file mode 100755 index 0000000..21f77ee --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/InlineValuesRequestHandler.java @@ -0,0 +1,257 @@ +/******************************************************************************* +* Copyright (c) 2021 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.adapter.IStackFrameManager; +import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.adapter.variables.Variable; +import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.InlineVariable; +import com.microsoft.java.debug.core.protocol.Requests.InlineValuesArguments; +import com.sun.jdi.ArrayReference; +import com.sun.jdi.Field; +import com.sun.jdi.IntegerValue; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; +import com.sun.jdi.Value; + +import org.apache.commons.lang3.math.NumberUtils; + +public class InlineValuesRequestHandler implements IDebugRequestHandler { + protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.INLINEVALUES); + } + + /** + * This request only resolves the values for those non-local variables, such as + * field variables and captured variables from outer scope. Because the values + * of local variables in current stackframe are usually expanded by Variables View + * by default, inline values can reuse these values directly. However, for field + * variables and variables captured from external scopes, they are hidden as properties + * of 'this' variable and require additional evaluation to get their values. + */ + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + InlineValuesArguments inlineValuesArgs = (InlineValuesArguments) arguments; + final int variableCount = inlineValuesArgs == null || inlineValuesArgs.variables == null ? 0 : inlineValuesArgs.variables.length; + InlineVariable[] inlineVariables = inlineValuesArgs.variables; + StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(inlineValuesArgs.frameId); + if (stackFrameReference == null) { + logger.log(Level.SEVERE, String.format("InlineValues failed: invalid stackframe id %d.", inlineValuesArgs.frameId)); + response.body = new Responses.InlineValuesResponse(null); + return CompletableFuture.completedFuture(response); + } + + // Async mode is supposed to be performant, then disable the advanced features like inline values. + if (context.getJDWPLatency() > VariablesRequestHandler.USABLE_JDWP_LATENCY + && context.asyncJDWP(VariablesRequestHandler.USABLE_JDWP_LATENCY)) { + response.body = new Responses.InlineValuesResponse(null); + return CompletableFuture.completedFuture(response); + } + + IStackFrameManager stackFrameManager = context.getStackFrameManager(); + StackFrame frame = stackFrameManager.getStackFrame(stackFrameReference); + if (frame == null) { + logger.log(Level.SEVERE, String.format("InlineValues failed: stale stackframe id %d.", inlineValuesArgs.frameId)); + response.body = new Responses.InlineValuesResponse(null); + return CompletableFuture.completedFuture(response); + } + + Variable[] values = new Variable[variableCount]; + try { + if (isLambdaFrame(frame)) { + // Lambda expression stores the captured variables from 'outer' scope in a synthetic stackframe below the lambda frame. + StackFrame syntheticLambdaFrame = stackFrameReference.getThread().frame(stackFrameReference.getDepth() + 1); + resolveValuesFromThisVariable(syntheticLambdaFrame.thisObject(), inlineVariables, values, true); + } + + resolveValuesFromThisVariable(frame.thisObject(), inlineVariables, values, false); + } catch (Exception ex) { + // do nothig + } + + Types.Variable[] result = new Types.Variable[variableCount]; + IVariableFormatter variableFormatter = context.getVariableFormatter(); + Map formatterOptions = variableFormatter.getDefaultOptions(); + Map calculatedValues = new HashMap<>(); + IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class); + for (int i = 0; i < variableCount; i++) { + if (values[i] == null) { + continue; + } + + if (calculatedValues.containsKey(inlineVariables[i])) { + result[i] = calculatedValues.get(inlineVariables[i]); + continue; + } + + Value value = values[i].value; + String name = values[i].name; + int indexedVariables = -1; + Value sizeValue = null; + if (value instanceof ArrayReference) { + indexedVariables = ((ArrayReference) value).length(); + } else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure && evaluationEngine != null) { + try { + JavaLogicalStructure structure = JavaLogicalStructureManager.getLogicalStructure((ObjectReference) value); + if (structure != null && structure.getSizeExpression() != null) { + sizeValue = structure.getSize((ObjectReference) value, frame.thread(), evaluationEngine); + if (sizeValue != null && sizeValue instanceof IntegerValue) { + indexedVariables = ((IntegerValue) sizeValue).value(); + } + } + } catch (CancellationException | IllegalArgumentException | InterruptedException | ExecutionException | UnsupportedOperationException e) { + logger.log(Level.INFO, + String.format("Failed to get the logical size for the type %s.", value.type().name()), e); + } + } + + Types.Variable formattedVariable = new Types.Variable(name, variableFormatter.valueToString(value, formatterOptions)); + formattedVariable.indexedVariables = Math.max(indexedVariables, 0); + String detailsValue = null; + if (sizeValue != null) { + detailsValue = "size=" + variableFormatter.valueToString(sizeValue, formatterOptions); + } else if (DebugSettings.getCurrent().showToString) { + detailsValue = VariableDetailUtils.formatDetailsValue(value, frame.thread(), variableFormatter, formatterOptions, evaluationEngine); + } + + if (detailsValue != null) { + formattedVariable.value = formattedVariable.value + " " + detailsValue; + } + + result[i] = formattedVariable; + calculatedValues.put(inlineVariables[i], formattedVariable); + } + + response.body = new Responses.InlineValuesResponse(result); + return CompletableFuture.completedFuture(response); + } + + private static boolean isCapturedLocalVariable(String fieldName, String variableName) { + String capturedVariableName = "val$" + variableName; + return Objects.equals(fieldName, capturedVariableName) + || (fieldName.startsWith(capturedVariableName + "$") && NumberUtils.isDigits(fieldName.substring(capturedVariableName.length() + 1))); + } + + private static boolean isCapturedThisVariable(String fieldName) { + if (fieldName.startsWith("this$")) { + String suffix = fieldName.substring(5).replaceAll("\\$+$", ""); + return NumberUtils.isDigits(suffix); + } + + return false; + } + + private static boolean isLambdaFrame(StackFrame frame) { + Method method = frame.location().method(); + return method.isSynthetic() && method.name().startsWith("lambda$"); + } + + private void resolveValuesFromThisVariable(ObjectReference thisObj, InlineVariable[] unresolvedVariables, Variable[] result, + boolean isSyntheticLambdaFrame) { + if (thisObj == null) { + return; + } + + int unresolved = 0; + for (Variable item : result) { + if (item == null) { + unresolved++; + } + } + + try { + ReferenceType type = thisObj.referenceType(); + String typeName = type.name(); + ObjectReference enclosingInstance = null; + for (Field field : type.allFields()) { + String fieldName = field.name(); + boolean isSyntheticField = field.isSynthetic(); + Value fieldValue = null; + for (int i = 0; i < unresolvedVariables.length; i++) { + if (result[i] != null) { + continue; + } + + InlineVariable inlineVariable = unresolvedVariables[i]; + boolean isInlineFieldVariable = (inlineVariable.declaringClass != null); + boolean isMatch = false; + if (isSyntheticLambdaFrame) { + isMatch = !isInlineFieldVariable && Objects.equals(fieldName, inlineVariable.expression); + } else { + boolean isMatchedField = isInlineFieldVariable + && Objects.equals(fieldName, inlineVariable.expression) + && Objects.equals(typeName, inlineVariable.declaringClass); + boolean isMatchedCapturedVariable = !isInlineFieldVariable + && isSyntheticField + && isCapturedLocalVariable(fieldName, inlineVariable.expression); + isMatch = isMatchedField || isMatchedCapturedVariable; + + if (!isMatch && isSyntheticField && enclosingInstance == null && isCapturedThisVariable(fieldName)) { + Value value = thisObj.getValue(field); + if (value instanceof ObjectReference) { + enclosingInstance = (ObjectReference) value; + break; + } + } + } + + if (isMatch) { + fieldValue = fieldValue == null ? thisObj.getValue(field) : fieldValue; + result[i] = new Variable(inlineVariable.expression, fieldValue); + unresolved--; + } + } + + if (unresolved <= 0) { + break; + } + } + + if (unresolved > 0 && enclosingInstance != null) { + resolveValuesFromThisVariable(enclosingInstance, unresolvedVariables, result, isSyntheticLambdaFrame); + } + } catch (Exception ex) { + // do nothing + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java new file mode 100755 index 0000000..e5662f9 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java @@ -0,0 +1,398 @@ +/******************************************************************************* +* Copyright (c) 2018-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.LaunchException; +import com.microsoft.java.debug.core.UsageDataSession; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.LaunchMode; +import com.microsoft.java.debug.core.adapter.ProcessConsole; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Events.OutputEvent; +import com.microsoft.java.debug.core.protocol.Events.OutputEvent.Category; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.CONSOLE; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments; +import com.microsoft.java.debug.core.protocol.Requests.ShortenApproach; +import com.microsoft.java.debug.core.protocol.Types; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.connect.VMStartException; +import com.sun.jdi.event.VMDisconnectEvent; + +public class LaunchRequestHandler implements IDebugRequestHandler { + protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000; + protected ILaunchDelegate activeLaunchHandler; + private CompletableFuture waitForDebuggeeConsole = new CompletableFuture<>(); + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.LAUNCH); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + if (!context.isInitialized()) { + final String errorMessage = "'launch' request is rejected since the debug session has not been initialized yet."; + logger.log(Level.SEVERE, errorMessage); + return CompletableFuture.completedFuture( + AdapterUtils.setErrorResponse(response, ErrorCode.LAUNCH_FAILURE, errorMessage)); + } + LaunchArguments launchArguments = (LaunchArguments) arguments; + Map traceInfo = new HashMap<>(); + traceInfo.put("asyncJDWP", context.asyncJDWP()); + traceInfo.put("noDebug", launchArguments.noDebug); + traceInfo.put("console", launchArguments.console); + UsageDataSession.recordInfo("launch debug info", traceInfo); + + activeLaunchHandler = launchArguments.noDebug ? new LaunchWithoutDebuggingDelegate((daContext) -> handleTerminatedEvent(daContext)) + : new LaunchWithDebuggingDelegate(); + return handleLaunchCommand(arguments, response, context); + } + + protected CompletableFuture handleLaunchCommand(Arguments arguments, Response response, IDebugAdapterContext context) { + LaunchArguments launchArguments = (LaunchArguments) arguments; + // validation + if (StringUtils.isBlank(launchArguments.mainClass) + || ArrayUtils.isEmpty(launchArguments.modulePaths) && ArrayUtils.isEmpty(launchArguments.classPaths)) { + throw AdapterUtils.createCompletionException( + "Failed to launch debuggee VM. Missing mainClass or modulePaths/classPaths options in launch configuration.", + ErrorCode.ARGUMENT_MISSING); + } + if (StringUtils.isNotBlank(launchArguments.encoding)) { + if (!Charset.isSupported(launchArguments.encoding)) { + throw AdapterUtils.createCompletionException( + "Failed to launch debuggee VM. 'encoding' options in the launch configuration is not recognized.", + ErrorCode.INVALID_ENCODING); + } + context.setDebuggeeEncoding(Charset.forName(launchArguments.encoding)); + if (StringUtils.isBlank(launchArguments.vmArgs)) { + launchArguments.vmArgs = String.format("-Dfile.encoding=%s", context.getDebuggeeEncoding().name()); + } else { + // if vmArgs already has the file.encoding settings, duplicate options for jvm will not cause an error, the right most value wins + launchArguments.vmArgs = String.format("%s -Dfile.encoding=%s", launchArguments.vmArgs, context.getDebuggeeEncoding().name()); + } + } + + context.setLaunchMode(launchArguments.noDebug ? LaunchMode.NO_DEBUG : LaunchMode.DEBUG); + + activeLaunchHandler.preLaunch(launchArguments, context); + + // Use the specified cli style to launch the program. + if (launchArguments.shortenCommandLine == ShortenApproach.JARMANIFEST) { + if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) { + try { + Path tempfile = LaunchUtils.generateClasspathJar(launchArguments.classPaths); + launchArguments.vmArgs += " -cp \"" + tempfile.toAbsolutePath().toString() + "\""; + launchArguments.classPaths = new String[0]; + context.setClasspathJar(tempfile); + } catch (IllegalArgumentException | MalformedURLException ex) { + logger.log(Level.SEVERE, String.format("Failed to launch the program with jarmanifest style: %s", ex.toString(), ex)); + throw AdapterUtils.createCompletionException("Failed to launch the program with jarmanifest style: " + ex.toString(), + ErrorCode.LAUNCH_FAILURE, ex); + } catch (IOException e) { + logger.log(Level.SEVERE, String.format("Failed to create a temp classpath.jar: %s", e.toString()), e); + } + } + } else if (launchArguments.shortenCommandLine == ShortenApproach.ARGFILE) { + try { + /** + * See the JDK spec https://docs.oracle.com/en/java/javase/18/docs/specs/man/java.html#java-command-line-argument-files. + * The argument file must contain only ASCII characters or characters in system default encoding that's ASCII friendly. + */ + Charset systemCharset = LaunchUtils.getSystemCharset(); + CharsetEncoder encoder = systemCharset.newEncoder(); + String vmArgsForShorten = null; + String[] classPathsForShorten = null; + String[] modulePathsForShorten = null; + if (StringUtils.isNotBlank(launchArguments.vmArgs)) { + if (!encoder.canEncode(launchArguments.vmArgs)) { + logger.warning(String.format("Cannot generate the 'vmArgs' argument into the argfile because it contains characters " + + "that cannot be encoded in the system charset '%s'.", systemCharset.displayName())); + } else { + vmArgsForShorten = launchArguments.vmArgs; + } + } + + if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) { + if (!encoder.canEncode(String.join(File.pathSeparator, launchArguments.classPaths))) { + logger.warning(String.format("Cannot generate the '-cp' argument into the argfile because it contains characters " + + "that cannot be encoded in the system charset '%s'.", systemCharset.displayName())); + } else { + classPathsForShorten = launchArguments.classPaths; + } + } + + if (ArrayUtils.isNotEmpty(launchArguments.modulePaths)) { + if (!encoder.canEncode(String.join(File.pathSeparator, launchArguments.modulePaths))) { + logger.warning(String.format("Cannot generate the '--module-path' argument into the argfile because it contains characters " + + "that cannot be encoded in the system charset '%s'.", systemCharset.displayName())); + } else { + modulePathsForShorten = launchArguments.modulePaths; + } + } + + if (vmArgsForShorten != null || classPathsForShorten != null || modulePathsForShorten != null) { + Path tempfile = LaunchUtils.generateArgfile(vmArgsForShorten, classPathsForShorten, modulePathsForShorten, systemCharset); + launchArguments.vmArgs = (vmArgsForShorten == null ? launchArguments.vmArgs : "") + + " \"@" + tempfile.toAbsolutePath().toString() + "\""; + launchArguments.classPaths = (classPathsForShorten == null ? launchArguments.classPaths : new String[0]); + launchArguments.modulePaths = (modulePathsForShorten == null ? launchArguments.modulePaths : new String[0]); + context.setArgsfile(tempfile); + } + } catch (IOException e) { + logger.log(Level.SEVERE, String.format("Failed to create a temp argfile: %s", e.toString()), e); + } + } + + return launch(launchArguments, response, context).thenCompose(res -> { + long processId = context.getProcessId(); + long shellProcessId = context.getShellProcessId(); + if (context.getDebuggeeProcess() != null) { + processId = context.getDebuggeeProcess().pid(); + } + + // If processId or shellProcessId exist, send a notification to client. + if (processId > 0 || shellProcessId > 0) { + context.getProtocolServer().sendEvent(new Events.ProcessIdNotification(processId, shellProcessId)); + } + + LaunchUtils.releaseTempLaunchFile(context.getClasspathJar()); + LaunchUtils.releaseTempLaunchFile(context.getArgsfile()); + if (res.success) { + activeLaunchHandler.postLaunch(launchArguments, context); + } + + IDebugSession debugSession = context.getDebugSession(); + if (debugSession != null) { + debugSession.getEventHub().events() + .filter((debugEvent) -> debugEvent.event instanceof VMDisconnectEvent) + .subscribe((debugEvent) -> { + context.setVmTerminated(); + // Terminate eventHub thread. + try { + debugSession.getEventHub().close(); + } catch (Exception e) { + // do nothing. + } + + handleTerminatedEvent(context); + }); + } + return CompletableFuture.completedFuture(res); + }); + } + + protected void handleTerminatedEvent(IDebugAdapterContext context) { + CompletableFuture.runAsync(() -> { + try { + waitForDebuggeeConsole.get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + // do nothing. + } + + context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + }); + } + + /** + * Construct the Java command lines based on the given launch arguments. + * @param launchArguments - The launch arguments + * @param serverMode - whether to enable the debug port with server mode + * @param address - the debug port + * @return the command arrays + */ + public static String[] constructLaunchCommands(LaunchArguments launchArguments, boolean serverMode, String address) { + List launchCmds = new ArrayList<>(); + if (launchArguments.launcherScript != null) { + launchCmds.add(launchArguments.launcherScript); + } + + if (StringUtils.isNotBlank(launchArguments.javaExec)) { + launchCmds.add(launchArguments.javaExec); + } else { + final String javaHome = StringUtils.isNotEmpty(DebugSettings.getCurrent().javaHome) ? DebugSettings.getCurrent().javaHome + : System.getProperty("java.home"); + launchCmds.add(Paths.get(javaHome, "bin", "java").toString()); + } + if (StringUtils.isNotEmpty(address)) { + launchCmds.add(String.format("-agentlib:jdwp=transport=dt_socket,server=%s,suspend=y,address=%s", serverMode ? "y" : "n", address)); + } + if (StringUtils.isNotBlank(launchArguments.vmArgs)) { + launchCmds.addAll(DebugUtility.parseArguments(launchArguments.vmArgs)); + } + if (ArrayUtils.isNotEmpty(launchArguments.modulePaths)) { + launchCmds.add("--module-path"); + launchCmds.add(String.join(File.pathSeparator, launchArguments.modulePaths)); + } + if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) { + launchCmds.add("-cp"); + launchCmds.add(String.join(File.pathSeparator, launchArguments.classPaths)); + } + // For java 9 project, should specify "-m $MainClass". + String[] mainClasses = launchArguments.mainClass.split("/"); + if (mainClasses.length == 2) { + launchCmds.add("-m"); + } + launchCmds.add(launchArguments.mainClass); + if (StringUtils.isNotBlank(launchArguments.args)) { + launchCmds.addAll(DebugUtility.parseArguments(launchArguments.args)); + } + return launchCmds.toArray(new String[0]); + } + + protected CompletableFuture launch(LaunchArguments launchArguments, Response response, IDebugAdapterContext context) { + logger.info("Trying to launch Java Program with options:\n" + String.format("main-class: %s\n", launchArguments.mainClass) + + String.format("args: %s\n", launchArguments.args) + + String.format("module-path: %s\n", StringUtils.join(launchArguments.modulePaths, File.pathSeparator)) + + String.format("class-path: %s\n", StringUtils.join(launchArguments.classPaths, File.pathSeparator)) + + String.format("vmArgs: %s", launchArguments.vmArgs)); + + if (context.supportsRunInTerminalRequest() + && (launchArguments.console == CONSOLE.integratedTerminal || launchArguments.console == CONSOLE.externalTerminal)) { + waitForDebuggeeConsole.complete(true); + return activeLaunchHandler.launchInTerminal(launchArguments, response, context); + } + + CompletableFuture resultFuture = new CompletableFuture<>(); + try { + Process debuggeeProcess = activeLaunchHandler.launch(launchArguments, context); + context.setDebuggeeProcess(debuggeeProcess); + ProcessConsole debuggeeConsole = new ProcessConsole(debuggeeProcess, "Debuggee", context.getDebuggeeEncoding()); + debuggeeConsole.lineMessages() + .map((message) -> convertToOutputEvent(message.output, message.category, context)) + .doFinally(() -> waitForDebuggeeConsole.complete(true)) + .subscribe((event) -> context.getProtocolServer().sendEvent(event)); + debuggeeConsole.start(); + resultFuture.complete(response); + } catch (LaunchException e) { + if (StringUtils.isNotBlank(e.getStdout())) { + OutputEvent event = convertToOutputEvent(e.getStdout(), Category.stdout, context); + context.getProtocolServer().sendEvent(event); + } + if (StringUtils.isNotBlank(e.getStderr())) { + OutputEvent event = convertToOutputEvent(e.getStderr(), Category.stderr, context); + context.getProtocolServer().sendEvent(event); + } + + resultFuture.completeExceptionally( + new DebugException( + String.format("Failed to launch debuggee VM. Reason: %s", e.getMessage()), + ErrorCode.LAUNCH_FAILURE.getId() + ) + ); + } catch (IOException | IllegalConnectorArgumentsException | VMStartException e) { + resultFuture.completeExceptionally( + new DebugException( + String.format("Failed to launch debuggee VM. Reason: %s", e.toString()), + ErrorCode.LAUNCH_FAILURE.getId() + ) + ); + } + + return resultFuture; + } + + private static final Pattern STACKTRACE_PATTERN = Pattern.compile("\\s+at\\s+([\\w$\\.]+\\/)?(([\\w$]+\\.)+[<\\w$>]+)\\(([\\w-$]+\\.java:\\d+)\\)"); + + private static OutputEvent convertToOutputEvent(String message, Category category, IDebugAdapterContext context) { + Matcher matcher = STACKTRACE_PATTERN.matcher(message); + if (matcher.find()) { + String methodField = matcher.group(2); + String locationField = matcher.group(matcher.groupCount()); + String fullyQualifiedName = methodField.substring(0, methodField.lastIndexOf(".")); + String packageName = fullyQualifiedName.lastIndexOf(".") > -1 ? fullyQualifiedName.substring(0, fullyQualifiedName.lastIndexOf(".")) : ""; + String[] locations = locationField.split(":"); + String sourceName = locations[0]; + int lineNumber = Integer.parseInt(locations[1]); + String sourcePath = StringUtils.isBlank(packageName) ? sourceName + : packageName.replace('.', File.separatorChar) + File.separatorChar + sourceName; + Types.Source source = null; + try { + source = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, sourceName, sourcePath, context); + } catch (URISyntaxException e) { + // do nothing. + } + + return new OutputEvent(category, message, source, lineNumber); + } + + return new OutputEvent(category, message); + } + + protected static String[] constructEnvironmentVariables(LaunchArguments launchArguments) { + String[] envVars = null; + if (launchArguments.env != null && !launchArguments.env.isEmpty()) { + Map environment = new HashMap<>(System.getenv()); + List duplicated = new ArrayList<>(); + for (Entry entry : launchArguments.env.entrySet()) { + if (environment.containsKey(entry.getKey())) { + duplicated.add(entry.getKey()); + } + environment.put(entry.getKey(), entry.getValue()); + } + // For duplicated variables, show a warning message. + if (!duplicated.isEmpty()) { + logger.warning(String.format("There are duplicated environment variables. The values specified in launch.json will be used. " + + "Here are the duplicated entries: %s.", String.join(",", duplicated))); + } + + envVars = new String[environment.size()]; + int i = 0; + for (Entry entry : environment.entrySet()) { + envVars[i++] = entry.getKey() + "=" + entry.getValue(); + } + } + return envVars; + } + + public static String parseMainClassWithoutModuleName(String mainClass) { + int index = mainClass.indexOf('/'); + return mainClass.substring(index + 1); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java new file mode 100755 index 0000000..27fdb18 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java @@ -0,0 +1,389 @@ +/******************************************************************************* +* Copyright (c) 2021-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.adapter.AdapterUtils; + +public class LaunchUtils { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private static Set tempFilesInUse = new HashSet<>(); + private static final Charset SYSTEM_CHARSET; + + static { + Charset result = null; + try { + // JEP 400: Java 17+ populates this system property. + String encoding = System.getProperty("native.encoding"); //$NON-NLS-1$ + if (encoding != null && !encoding.isBlank()) { + result = Charset.forName(encoding); + } else { + // JVM internal property, works on older JVM's too + encoding = System.getProperty("sun.jnu.encoding"); //$NON-NLS-1$ + if (encoding != null && !encoding.isBlank()) { + result = Charset.forName(encoding); + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error occurs during resolving system encoding", e); + } + if (result == null) { + // This is always UTF-8 on Java >= 18. + result = Charset.defaultCharset(); + } + SYSTEM_CHARSET = result; + } + + public static Charset getSystemCharset() { + return SYSTEM_CHARSET; + } + + /** + * Generate the classpath parameters to a temporary classpath.jar. + * @param classPaths - the classpath parameters + * @return the file path of the generate classpath.jar + * @throws IOException Some errors occur during generating the classpath.jar + */ + public static synchronized Path generateClasspathJar(String[] classPaths) throws IOException { + List classpathUrls = new ArrayList<>(); + for (String classpath : classPaths) { + classpathUrls.add(AdapterUtils.toUrl(classpath)); + } + + Manifest manifest = new Manifest(); + Attributes attributes = manifest.getMainAttributes(); + attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + // In jar manifest, the absolute path C:\a.jar should be converted to the url style file:///C:/a.jar + String classpathValue = String.join(" ", classpathUrls); + attributes.put(Attributes.Name.CLASS_PATH, classpathValue); + String baseName = "cp_" + getMd5(classpathValue); + cleanupTempFiles(baseName, ".jar"); + Path tempfile = createTempFile(baseName, ".jar"); + JarOutputStream jar = new JarOutputStream(new FileOutputStream(tempfile.toFile()), manifest); + jar.close(); + lockTempLaunchFile(tempfile); + + return tempfile; + } + + /** + * Generate the classpath parameters to a temporary argfile file. + * @param classPaths - the classpath parameters + * @param modulePaths - the modulepath parameters + * @return the file path of the generated argfile + * @throws IOException Some errors occur during generating the argfile + */ + public static synchronized Path generateArgfile(String vmArgs, String[] classPaths, String[] modulePaths, Charset encoding) throws IOException { + String argfile = ""; + if (StringUtils.isNotBlank(vmArgs)) { + argfile += vmArgs; + } + + if (ArrayUtils.isNotEmpty(classPaths)) { + argfile += " -cp \"" + String.join(File.pathSeparator, classPaths) + "\""; + } + + if (ArrayUtils.isNotEmpty(modulePaths)) { + argfile += " --module-path \"" + String.join(File.pathSeparator, modulePaths) + "\""; + } + + argfile = argfile.replace("\\", "\\\\"); + String baseName = "cp_" + getMd5(argfile); + cleanupTempFiles(baseName, ".argfile"); + Path tempfile = createTempFile(baseName, ".argfile"); + Files.writeString(tempfile, argfile, encoding); + lockTempLaunchFile(tempfile); + + return tempfile; + } + + public static void lockTempLaunchFile(Path tempFile) { + if (tempFile != null) { + tempFilesInUse.add(tempFile); + } + } + + public static void releaseTempLaunchFile(Path tempFile) { + if (tempFile != null) { + tempFilesInUse.remove(tempFile); + } + } + + public static ProcessHandle findJavaProcessInTerminalShell(long shellPid, String javaCommand, int timeout/*ms*/) { + ProcessHandle shellProcess = ProcessHandle.of(shellPid).orElse(null); + if (shellProcess != null) { + int retry = 0; + final int INTERVAL = 20/*ms*/; + final int maxRetries = timeout / INTERVAL; + final boolean isCygwinShell = isCygwinShell(shellProcess.info().command().orElse(null)); + while (retry <= maxRetries) { + Optional subProcessHandle = shellProcess.descendants().filter(proc -> { + String command = proc.info().command().orElse(""); + return Objects.equals(command, javaCommand) || command.endsWith("\\java.exe") || command.endsWith("/java"); + }).findFirst(); + + if (subProcessHandle.isPresent()) { + if (retry > 0) { + logger.info("Retried " + retry + " times to find Java subProcess."); + } + logger.info("shellPid: " + shellPid + ", javaPid: " + subProcessHandle.get().pid()); + return subProcessHandle.get(); + } else if (isCygwinShell) { + long javaPid = findJavaProcessByCygwinPsCommand(shellProcess, javaCommand); + if (javaPid > 0) { + if (retry > 0) { + logger.info("Retried " + retry + " times to find Java subProcess."); + } + logger.info("[Cygwin Shell] shellPid: " + shellPid + ", javaPid: " + javaPid); + return ProcessHandle.of(javaPid).orElse(null); + } + } + + retry++; + if (retry > maxRetries) { + break; + } + + try { + Thread.sleep(INTERVAL); + } catch (InterruptedException e) { + // do nothing + } + } + + logger.info("Retried " + retry + " times but failed to find Java subProcess of shell pid " + shellPid); + } + + return null; + } + + private static long findJavaProcessByCygwinPsCommand(ProcessHandle shellProcess, String javaCommand) { + String psCommand = detectPsCommandPath(shellProcess.info().command().orElse(null)); + if (psCommand == null) { + return -1; + } + + BufferedReader psReader = null; + List psProcs = new ArrayList<>(); + List javaCandidates = new ArrayList<>(); + try { + String[] headers = null; + int pidIndex = -1; + int ppidIndex = -1; + int winpidIndex = -1; + String line; + String javaExeName = Paths.get(javaCommand).toFile().getName().replaceFirst("\\.exe$", ""); + + Process p = Runtime.getRuntime().exec(new String[] {psCommand, "-l"}); + psReader = new BufferedReader(new InputStreamReader(p.getInputStream())); + /** + * Here is a sample output when running ps command in Cygwin/MINGW64 shell. + * PID PPID PGID WINPID TTY UID STIME COMMAND + * 1869 1 1869 7852 cons2 4096 15:29:27 /usr/bin/bash + * 2271 1 2271 30820 cons4 4096 19:38:30 /usr/bin/bash + * 1812 1 1812 21540 cons1 4096 15:05:03 /usr/bin/bash + * 2216 1 2216 11328 cons3 4096 19:38:18 /usr/bin/bash + * 1720 1 1720 5404 cons0 4096 13:46:42 /usr/bin/bash + * 2269 2216 2269 6676 cons3 4096 19:38:21 /c/Program Files/Microsoft/jdk-11.0.14.9-hotspot/bin/java + * 1911 1869 1869 29708 cons2 4096 15:29:31 /c/Program Files/nodejs/node + * 2315 2271 2315 18064 cons4 4096 19:38:34 /usr/bin/ps + */ + while ((line = psReader.readLine()) != null) { + String[] cols = line.strip().split("\\s+"); + if (headers == null) { + headers = cols; + pidIndex = ArrayUtils.indexOf(headers, "PID"); + ppidIndex = ArrayUtils.indexOf(headers, "PPID"); + winpidIndex = ArrayUtils.indexOf(headers, "WINPID"); + if (pidIndex < 0 || ppidIndex < 0 || winpidIndex < 0) { + logger.warning("Failed to find Java process because ps command is not the standard Cygwin ps command."); + return -1; + } + } else if (cols.length >= headers.length) { + long pid = Long.parseLong(cols[pidIndex]); + long ppid = Long.parseLong(cols[ppidIndex]); + long winpid = Long.parseLong(cols[winpidIndex]); + PsProcess process = new PsProcess(pid, ppid, winpid); + psProcs.add(process); + if (cols[cols.length - 1].endsWith("/" + javaExeName) || cols[cols.length - 1].endsWith("/java")) { + javaCandidates.add(process); + } + } + } + } catch (Exception err) { + logger.log(Level.WARNING, "Failed to find Java process by Cygwin ps command.", err); + } finally { + if (psReader != null) { + try { + psReader.close(); + } catch (IOException e) { + // ignore + } + } + } + + if (!javaCandidates.isEmpty()) { + Set descendantWinpids = shellProcess.descendants().map(proc -> proc.pid()).collect(Collectors.toSet()); + long shellWinpid = shellProcess.pid(); + for (PsProcess javaCandidate: javaCandidates) { + if (descendantWinpids.contains(javaCandidate.winpid)) { + return javaCandidate.winpid; + } + + for (PsProcess psProc : psProcs) { + if (javaCandidate.ppid != psProc.pid) { + continue; + } + + if (descendantWinpids.contains(psProc.winpid) || psProc.winpid == shellWinpid) { + return javaCandidate.winpid; + } + + break; + } + } + } + + return -1; + } + + private static boolean isCygwinShell(String shellPath) { + if (!SystemUtils.IS_OS_WINDOWS || shellPath == null) { + return false; + } + + String lowerShellPath = shellPath.toLowerCase(); + return lowerShellPath.endsWith("git\\bin\\bash.exe") + || lowerShellPath.endsWith("git\\usr\\bin\\bash.exe") + || lowerShellPath.endsWith("mintty.exe") + || lowerShellPath.endsWith("cygwin64\\bin\\bash.exe") + || (lowerShellPath.endsWith("bash.exe") && detectPsCommandPath(shellPath) != null) + || (lowerShellPath.endsWith("sh.exe") && detectPsCommandPath(shellPath) != null); + } + + private static String detectPsCommandPath(String shellPath) { + if (shellPath == null) { + return null; + } + + Path psPath = Paths.get(shellPath, "..\\ps.exe"); + if (!Files.exists(psPath)) { + psPath = Paths.get(shellPath, "..\\..\\usr\\bin\\ps.exe"); + if (!Files.exists(psPath)) { + psPath = null; + } + } + + if (psPath == null) { + return null; + } + + return psPath.normalize().toString(); + } + + private static Path tmpdir = null; + + private static synchronized Path getTmpDir() throws IOException { + if (tmpdir == null) { + Path tmpfile = Files.createTempFile("", UUID.randomUUID().toString()); + tmpdir = tmpfile.getParent(); + try { + Files.deleteIfExists(tmpfile); + } catch (Exception ex) { + // do nothing + } + } + + return tmpdir; + } + + private static void cleanupTempFiles(String baseName, String suffix) throws IOException { + for (int i = 0; ; i++) { + Path tempFile = getTmpDir().resolve(baseName + (i == 0 ? "" : i) + suffix); + if (tempFilesInUse.contains(tempFile)) { + continue; + } else if (!Files.exists(tempFile)) { + break; + } else { + try { + // delete the old temp file + Files.deleteIfExists(tempFile); + } catch (Exception e) { + // do nothing + } + } + } + } + + private static Path createTempFile(String baseName, String suffix) throws IOException { + // loop until the temp file can be created + for (int i = 0; ; i++) { + Path tempFile = getTmpDir().resolve(baseName + (i == 0 ? "" : i) + suffix); + if (!Files.exists(tempFile)) { + return Files.createFile(tempFile); + } + } + } + + private static String getMd5(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] messageDigest = md.digest(input.getBytes()); + BigInteger md5 = new BigInteger(1, messageDigest); + return md5.toString(Character.MAX_RADIX); + } catch (NoSuchAlgorithmException e) { + return Integer.toString(input.hashCode(), Character.MAX_RADIX); + } + } + + private static class PsProcess { + long pid; + long ppid; + long winpid; + + public PsProcess(long pid, long ppid, long winpid) { + this.pid = pid; + this.ppid = ppid; + this.winpid = winpid; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java new file mode 100755 index 0000000..2962294 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java @@ -0,0 +1,249 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; + +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.DebugSession; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.adapter.Constants; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.ICompletionsProvider; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; +import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.JsonUtils; +import com.microsoft.java.debug.core.protocol.Messages.Request; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.CONSOLE; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments; +import com.microsoft.java.debug.core.protocol.Requests.RunInTerminalRequestArguments; +import com.microsoft.java.debug.core.protocol.Responses.RunInTerminalResponseBody; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.connect.Connector; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.connect.ListeningConnector; +import com.sun.jdi.connect.TransportTimeoutException; +import com.sun.jdi.connect.VMStartException; + +public class LaunchWithDebuggingDelegate implements ILaunchDelegate { + + protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private static final int ATTACH_TERMINAL_TIMEOUT = 20 * 1000; + protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000; + private VMHandler vmHandler = new VMHandler(); + + @Override + public CompletableFuture launchInTerminal(LaunchArguments launchArguments, Response response, IDebugAdapterContext context) { + CompletableFuture resultFuture = new CompletableFuture<>(); + + IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class); + vmHandler.setVmProvider(vmProvider); + final String launchInTerminalErrorFormat = "Failed to launch debuggee in terminal. Reason: %s"; + + try { + List connectors = vmProvider.getVirtualMachineManager().listeningConnectors(); + ListeningConnector listenConnector = connectors.get(0); + Map args = listenConnector.defaultArguments(); + ((Connector.IntegerArgument) args.get("timeout")).setValue(ATTACH_TERMINAL_TIMEOUT); + String address = listenConnector.startListening(args); + + final String[] names = launchArguments.mainClass.split("[/\\.]"); + final String terminalName = "Debug: " + names[names.length - 1]; + String[] cmds = LaunchRequestHandler.constructLaunchCommands(launchArguments, false, address); + RunInTerminalRequestArguments requestArgs = null; + if (launchArguments.console == CONSOLE.integratedTerminal) { + requestArgs = RunInTerminalRequestArguments.createIntegratedTerminal( + cmds, + launchArguments.cwd, + launchArguments.env, + terminalName); + } else { + requestArgs = RunInTerminalRequestArguments.createExternalTerminal( + cmds, + launchArguments.cwd, + launchArguments.env, + terminalName); + } + Request request = new Request(Command.RUNINTERMINAL.getName(), + (JsonObject) JsonUtils.toJsonTree(requestArgs, RunInTerminalRequestArguments.class)); + + // Notes: In windows (reference to https://support.microsoft.com/en-us/help/830473/command-prompt-cmd--exe-command-line-string-limitation), + // when launching the program in cmd.exe, if the command line length exceed the threshold value (8191 characters), + // it will be automatically truncated so that launching in terminal failed. Especially, for maven project, the class path contains + // the local .m2 repository path, it may exceed the limit. + context.getProtocolServer().sendRequest(request, RUNINTERMINAL_TIMEOUT) + .whenComplete((runResponse, ex) -> { + if (runResponse != null) { + if (runResponse.success) { + try { + try { + RunInTerminalResponseBody terminalResponse = JsonUtils.fromJson( + JsonUtils.toJson(runResponse.body), RunInTerminalResponseBody.class); + context.setProcessId(terminalResponse.processId); + context.setShellProcessId(terminalResponse.shellProcessId); + } catch (JsonSyntaxException e) { + logger.severe("Failed to resolve runInTerminal response: " + e.toString()); + } + VirtualMachine vm = listenConnector.accept(args); + vmHandler.connectVirtualMachine(vm); + context.setDebugSession(new DebugSession(vm)); + logger.info("Launching debuggee in terminal console succeeded."); + if (context.getShellProcessId() > 0) { + ProcessHandle debuggeeProcess = LaunchUtils.findJavaProcessInTerminalShell(context.getShellProcessId(), cmds[0], 0); + if (debuggeeProcess != null) { + context.setProcessId(debuggeeProcess.pid()); + } + } + resultFuture.complete(response); + } catch (TransportTimeoutException e) { + int commandLength = StringUtils.length(launchArguments.cwd) + 1; + for (String cmd : cmds) { + commandLength += StringUtils.length(cmd) + 1; + } + + final int threshold = SystemUtils.IS_OS_WINDOWS ? 8092 : 32 * 1024; + String errorMessage = String.format(launchInTerminalErrorFormat, e.toString()); + if (commandLength >= threshold) { + errorMessage = "Failed to launch debuggee in terminal. The possible reason is the command line too long. " + + "More details: " + e.toString(); + logger.severe(errorMessage + + "\r\n" + + "The estimated command line length is " + commandLength + ". " + + "Try to enable shortenCommandLine option in the debug launch configuration."); + } + + resultFuture.completeExceptionally( + new DebugException( + errorMessage, + ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId() + ) + ); + } catch (IOException | IllegalConnectorArgumentsException e) { + resultFuture.completeExceptionally( + new DebugException( + String.format(launchInTerminalErrorFormat, e.toString()), + ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId() + ) + ); + } + } else { + resultFuture.completeExceptionally( + new DebugException( + String.format(launchInTerminalErrorFormat, runResponse.message), + ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId() + ) + ); + } + } else { + if (ex instanceof CompletionException && ex.getCause() != null) { + ex = ex.getCause(); + } + String errorMessage = String.format(launchInTerminalErrorFormat, ex != null ? ex.toString() : "Null response"); + resultFuture.completeExceptionally( + new DebugException( + String.format(launchInTerminalErrorFormat, errorMessage), + ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId() + ) + ); + } + }); + } catch (IOException | IllegalConnectorArgumentsException e) { + resultFuture.completeExceptionally( + new DebugException( + String.format(launchInTerminalErrorFormat, e.toString()), + ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId() + ) + ); + } + + return resultFuture; + } + + @Override + public Process launch(LaunchArguments launchArguments, IDebugAdapterContext context) + throws IOException, IllegalConnectorArgumentsException, VMStartException { + IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class); + vmHandler.setVmProvider(vmProvider); + + IDebugSession debugSession = DebugUtility.launch( + vmProvider.getVirtualMachineManager(), + launchArguments.mainClass, + launchArguments.args, + launchArguments.vmArgs, + Arrays.asList(launchArguments.modulePaths), + Arrays.asList(launchArguments.classPaths), + launchArguments.cwd, + LaunchRequestHandler.constructEnvironmentVariables(launchArguments), + launchArguments.javaExec); + context.setDebugSession(debugSession); + vmHandler.connectVirtualMachine(debugSession.getVM()); + + logger.info("Launching debuggee VM succeeded."); + return debugSession.process(); + } + + @Override + public void postLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) { + Map options = new HashMap<>(); + options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding()); + if (launchArguments.projectName != null) { + options.put(Constants.PROJECT_NAME, launchArguments.projectName); + } + if (launchArguments.mainClass != null) { + options.put(Constants.MAIN_CLASS, launchArguments.mainClass); + } + + // TODO: Clean up the initialize mechanism + ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class); + sourceProvider.initialize(context, options); + IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class); + evaluationProvider.initialize(context, options); + IHotCodeReplaceProvider hcrProvider = context.getProvider(IHotCodeReplaceProvider.class); + hcrProvider.initialize(context, options); + ICompletionsProvider completionsProvider = context.getProvider(ICompletionsProvider.class); + completionsProvider.initialize(context, options); + + // send an InitializedEvent to indicate that the debugger is ready to accept + // configuration requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest). + context.getProtocolServer().sendEvent(new Events.InitializedEvent()); + } + + @Override + public void preLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) { + // debug only + context.setAttached(false); + context.setSourcePaths(launchArguments.sourcePaths); + context.setVmStopOnEntry(launchArguments.stopOnEntry); + context.setMainClass(LaunchRequestHandler.parseMainClassWithoutModuleName(launchArguments.mainClass)); + context.setStepFilters(launchArguments.stepFilters); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java new file mode 100755 index 0000000..82a4817 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java @@ -0,0 +1,168 @@ +/******************************************************************************* +* Copyright (c) 2018-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; +import java.util.logging.Logger; + +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.JsonUtils; +import com.microsoft.java.debug.core.protocol.Messages.Request; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.CONSOLE; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments; +import com.microsoft.java.debug.core.protocol.Requests.RunInTerminalRequestArguments; +import com.microsoft.java.debug.core.protocol.Responses.RunInTerminalResponseBody; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.connect.VMStartException; + +public class LaunchWithoutDebuggingDelegate implements ILaunchDelegate { + protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000; + private Consumer terminateHandler; + + public LaunchWithoutDebuggingDelegate(Consumer terminateHandler) { + this.terminateHandler = terminateHandler; + } + + @Override + public Process launch(LaunchArguments launchArguments, IDebugAdapterContext context) + throws IOException, IllegalConnectorArgumentsException, VMStartException { + String[] cmds = LaunchRequestHandler.constructLaunchCommands(launchArguments, false, null); + File workingDir = null; + if (launchArguments.cwd != null && Files.isDirectory(Paths.get(launchArguments.cwd))) { + workingDir = new File(launchArguments.cwd); + } + Process debuggeeProcess = Runtime.getRuntime().exec(cmds, LaunchRequestHandler.constructEnvironmentVariables(launchArguments), + workingDir); + new Thread() { + public void run() { + try { + debuggeeProcess.waitFor(); + } catch (InterruptedException ignore) { + logger.warning(String.format("Current thread is interrupted. Reason: %s", ignore.toString())); + debuggeeProcess.destroy(); + } finally { + terminateHandler.accept(context); + } + } + }.start(); + logger.info("Launching debuggee proccess succeeded."); + return debuggeeProcess; + } + + @Override + public void postLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) { + // For NO_DEBUG launch mode, the debugger does not respond to requests like + // SetBreakpointsRequest, + // but the front end keeps sending them according to the Debug Adapter Protocol. + // To avoid receiving them, a workaround is not to send InitializedEvent back to + // the front end. + // See https://github.com/Microsoft/vscode/issues/55850#issuecomment-412819676 + return; + } + + @Override + public CompletableFuture launchInTerminal(LaunchArguments launchArguments, Response response, + IDebugAdapterContext context) { + CompletableFuture resultFuture = new CompletableFuture<>(); + + final String launchInTerminalErrorFormat = "Failed to launch debuggee in terminal. Reason: %s"; + + final String[] names = launchArguments.mainClass.split("[/\\.]"); + final String terminalName = "Run: " + names[names.length - 1]; + String[] cmds = LaunchRequestHandler.constructLaunchCommands(launchArguments, false, null); + RunInTerminalRequestArguments requestArgs = null; + if (launchArguments.console == CONSOLE.integratedTerminal) { + requestArgs = RunInTerminalRequestArguments.createIntegratedTerminal(cmds, launchArguments.cwd, + launchArguments.env, terminalName); + } else { + requestArgs = RunInTerminalRequestArguments.createExternalTerminal(cmds, launchArguments.cwd, + launchArguments.env, terminalName); + } + Request request = new Request(Command.RUNINTERMINAL.getName(), + (JsonObject) JsonUtils.toJsonTree(requestArgs, RunInTerminalRequestArguments.class)); + + // Notes: In windows (reference to + // https://support.microsoft.com/en-us/help/830473/command-prompt-cmd--exe-command-line-string-limitation), + // when launching the program in cmd.exe, if the command line length exceed the + // threshold value (8191 characters), + // it will be automatically truncated so that launching in terminal failed. + // Especially, for maven project, the class path contains + // the local .m2 repository path, it may exceed the limit. + context.getProtocolServer().sendRequest(request, RUNINTERMINAL_TIMEOUT).whenComplete((runResponse, ex) -> { + if (runResponse != null) { + if (runResponse.success) { + ProcessHandle debuggeeProcess = null; + try { + RunInTerminalResponseBody terminalResponse = JsonUtils.fromJson( + JsonUtils.toJson(runResponse.body), RunInTerminalResponseBody.class); + context.setProcessId(terminalResponse.processId); + context.setShellProcessId(terminalResponse.shellProcessId); + + if (terminalResponse.processId > 0) { + debuggeeProcess = ProcessHandle.of(terminalResponse.processId).orElse(null); + } else if (terminalResponse.shellProcessId > 0) { + debuggeeProcess = LaunchUtils.findJavaProcessInTerminalShell(terminalResponse.shellProcessId, cmds[0], 3000); + } + + if (debuggeeProcess != null) { + context.setProcessId(debuggeeProcess.pid()); + debuggeeProcess.onExit().thenAcceptAsync(proc -> { + context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + }); + } + } catch (JsonSyntaxException e) { + logger.severe("Failed to resolve runInTerminal response: " + e.toString()); + } + + if (debuggeeProcess == null || !debuggeeProcess.isAlive()) { + context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + } + resultFuture.complete(response); + } else { + resultFuture.completeExceptionally( + new DebugException(String.format(launchInTerminalErrorFormat, runResponse.message), + ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId())); + } + } else { + if (ex instanceof CompletionException && ex.getCause() != null) { + ex = ex.getCause(); + } + String errorMessage = String.format(launchInTerminalErrorFormat, + ex != null ? ex.toString() : "Null response"); + resultFuture.completeExceptionally( + new DebugException(String.format(launchInTerminalErrorFormat, errorMessage), + ErrorCode.LAUNCH_IN_TERMINAL_FAILURE.getId())); + } + }); + return resultFuture; + } + + @Override + public void preLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) { + context.setSourcePaths(launchArguments.sourcePaths); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/ProcessIdHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ProcessIdHandler.java new file mode 100755 index 0000000..d3eb5ad --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ProcessIdHandler.java @@ -0,0 +1,43 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Responses.ProcessIdResponseBody; + +public class ProcessIdHandler implements IDebugRequestHandler { + @Override + public List getTargetCommands() { + return Arrays.asList(Command.PROCESSID); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + long processId = context.getProcessId(); + long shellProcessId = context.getShellProcessId(); + if (context.getDebuggeeProcess() != null) { + processId = context.getDebuggeeProcess().pid(); + } + + response.body = new ProcessIdResponseBody(processId, shellProcessId); + return CompletableFuture.completedFuture(response); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshVariablesHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshVariablesHandler.java new file mode 100755 index 0000000..01214b6 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/RefreshVariablesHandler.java @@ -0,0 +1,50 @@ +/******************************************************************************* +* Copyright (c) 2021 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.protocol.Events.InvalidatedAreas; +import com.microsoft.java.debug.core.protocol.Events.InvalidatedEvent; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.RefreshVariablesArguments; + +public class RefreshVariablesHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.REFRESHVARIABLES); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + RefreshVariablesArguments refreshArgs = (RefreshVariablesArguments) arguments; + if (refreshArgs != null) { + DebugSettings.getCurrent().showHex = refreshArgs.showHex; + DebugSettings.getCurrent().showQualifiedNames = refreshArgs.showQualifiedNames; + DebugSettings.getCurrent().showStaticVariables = refreshArgs.showStaticVariables; + DebugSettings.getCurrent().showLogicalStructure = refreshArgs.showLogicalStructure; + DebugSettings.getCurrent().showToString = refreshArgs.showToString; + } + + context.getProtocolServer().sendEvent(new InvalidatedEvent(InvalidatedAreas.VARIABLES)); + return CompletableFuture.completedFuture(response); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java new file mode 100755 index 0000000..1d76ac3 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/RestartFrameHandler.java @@ -0,0 +1,129 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.StackFrameUtility; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Events.UserNotificationEvent.NotificationType; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.RestartFrameArguments; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.StackFrame; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.request.StepRequest; + +/** + * Support Eclipse's `Drop To Frame` action, which is restartFrame in VSCode's + * debug. + */ +public class RestartFrameHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.RESTARTFRAME); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + RestartFrameArguments restartFrameArgs = (RestartFrameArguments) arguments; + StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(restartFrameArgs.frameId); + + if (stackFrameReference == null) { + throw AdapterUtils.createCompletionException( + String.format("RestartFrame: cannot find the stack frame with frameID %s", restartFrameArgs.frameId), + ErrorCode.RESTARTFRAME_FAILURE); + } + + if (canRestartFrame(context, stackFrameReference)) { + try { + ThreadReference reference = stackFrameReference.getThread(); + popStackFrames(context, stackFrameReference); + stepInto(context, reference); + } catch (DebugException de) { + context.getProtocolServer().sendEvent(new Events.UserNotificationEvent(NotificationType.ERROR, de.getMessage())); + throw AdapterUtils.createCompletionException( + String.format("Failed to restart stack frame. Reason: %s", de.getMessage()), + ErrorCode.RESTARTFRAME_FAILURE, + de); + } + return CompletableFuture.completedFuture(response); + } else { + context.getProtocolServer().sendEvent(new Events.UserNotificationEvent(NotificationType.ERROR, "Current stack frame doesn't support restart.")); + throw AdapterUtils.createCompletionException("Current stack frame doesn't support restart.", ErrorCode.RESTARTFRAME_FAILURE); + } + } + + private boolean canRestartFrame(IDebugAdapterContext context, StackFrameReference frameReference) { + if (!context.getDebugSession().getVM().canPopFrames()) { + return false; + } + ThreadReference reference = frameReference.getThread(); + int totalFrames; + try { + totalFrames = reference.frameCount(); + } catch (IncompatibleThreadStateException e) { + return false; + } + + // The frame cannot be the bottom one of the call stack: + if (totalFrames <= frameReference.getDepth() + 1) { + return false; + } + + StackFrame[] frames = context.getStackFrameManager().reloadStackFrames(reference, 0, frameReference.getDepth() + 2); + if (frames.length == 0) { + return false; + } + + // Cannot restart frame involved with native call stacks: + for (int i = 0; i <= frameReference.getDepth() + 1; i++) { + if (StackFrameUtility.isNative(frames[i])) { + return false; + } + } + return true; + } + + private void popStackFrames(IDebugAdapterContext context, StackFrameReference stackFrameRef) throws DebugException { + StackFrame frame = context.getStackFrameManager().getStackFrame(stackFrameRef); + if (frame == null) { + return; + } + StackFrameUtility.pop(frame); + } + + private void stepInto(IDebugAdapterContext context, ThreadReference thread) { + StepRequest request = DebugUtility.createStepIntoRequest(thread, context.getStepFilters().allowClasses, context.getStepFilters().skipClasses); + context.getDebugSession().getEventHub().stepEvents().filter(debugEvent -> request.equals(debugEvent.event.request())).take(1).subscribe(debugEvent -> { + debugEvent.shouldResume = false; + // Have to send two events to keep the UI sync with the step in operations: + context.getProtocolServer().sendEvent(new Events.ContinuedEvent(thread.uniqueID())); + context.getProtocolServer().sendEvent(new Events.StoppedEvent("restartframe", thread.uniqueID())); + }); + request.enable(); + thread.resume(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/ScopesRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ScopesRequestHandler.java new file mode 100755 index 0000000..e7b1a94 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ScopesRequestHandler.java @@ -0,0 +1,55 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.adapter.variables.VariableProxy; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.ScopesArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types; +import com.sun.jdi.ThreadReference; + +public class ScopesRequestHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SCOPES); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + ScopesArguments scopesArgs = (ScopesArguments) arguments; + List scopes = new ArrayList<>(); + StackFrameReference stackFrameReference = (StackFrameReference) context.getRecyclableIdPool().getObjectById(scopesArgs.frameId); + if (stackFrameReference == null) { + response.body = new Responses.ScopesResponseBody(scopes); + return CompletableFuture.completedFuture(response); + } + ThreadReference thread = stackFrameReference.getThread(); + VariableProxy localScope = new VariableProxy(thread, "Local", stackFrameReference, null, null); + int localScopeId = context.getRecyclableIdPool().addObject(thread.uniqueID(), localScope); + scopes.add(new Types.Scope(localScope.getScope(), localScopeId, false)); + + response.body = new Responses.ScopesResponseBody(scopes); + return CompletableFuture.completedFuture(response); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java new file mode 100755 index 0000000..7fe6c3f --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetBreakpointsRequestHandler.java @@ -0,0 +1,350 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugException; +import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.IEvaluatableBreakpoint; +import com.microsoft.java.debug.core.JavaBreakpointLocation; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.HotCodeReplaceEvent.EventType; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.SetBreakpointArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types; +import com.sun.jdi.BooleanValue; +import com.sun.jdi.Field; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StringReference; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VMDisconnectedException; +import com.sun.jdi.Value; +import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.StepEvent; +import com.sun.jdi.request.EventRequest; + +public class SetBreakpointsRequestHandler implements IDebugRequestHandler { + + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + private boolean registered = false; + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SETBREAKPOINTS); + } + + @Override + public void initialize(IDebugAdapterContext context) { + IDebugRequestHandler.super.initialize(context); + IHotCodeReplaceProvider provider = context.getProvider(IHotCodeReplaceProvider.class); + provider.getEventHub() + .filter(event -> event.getEventType() == EventType.END) + .subscribe(event -> { + try { + List classNames = (List) event.getData(); + reinstallBreakpoints(context, classNames); + } catch (Exception e) { + logger.severe(e.toString()); + } + }); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + if (context.getDebugSession() == null) { + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, "Empty debug session."); + } + + if (!registered) { + registered = true; + registerBreakpointHandler(context); + } + + SetBreakpointArguments bpArguments = (SetBreakpointArguments) arguments; + String sourcePath = normalizeSourcePath(bpArguments.source, context); + // When breakpoint source path is null or an invalid file path, send an ErrorResponse back. + if (StringUtils.isBlank(sourcePath)) { + throw AdapterUtils.createCompletionException( + String.format("Failed to setBreakpoint. Reason: '%s' is an invalid path.", bpArguments.source.path), + ErrorCode.SET_BREAKPOINT_FAILURE); + } + + try { + List res = new ArrayList<>(); + IBreakpoint[] toAdds = this.convertClientBreakpointsToDebugger(sourcePath, bpArguments.breakpoints, context); + // See the VSCode bug https://github.com/Microsoft/vscode/issues/36471. + // The source uri sometimes is encoded by VSCode, the debugger will decode it to keep the uri consistent. + IBreakpoint[] added = context.getBreakpointManager() + .setBreakpoints(AdapterUtils.decodeURIComponent(sourcePath), toAdds, bpArguments.sourceModified); + for (int i = 0; i < bpArguments.breakpoints.length; i++) { + added[i].setAsync(context.asyncJDWP()); + // For newly added breakpoint, should install it to debuggee first. + if (toAdds[i] == added[i] && added[i].className() != null) { + added[i].install().thenAccept(bp -> { + Events.BreakpointEvent bpEvent = new Events.BreakpointEvent("changed", this.convertDebuggerBreakpointToClient(bp, context)); + context.getProtocolServer().sendEvent(bpEvent); + }); + } else if (added[i].className() != null) { + if (toAdds[i].getHitCount() != added[i].getHitCount()) { + // Update hitCount condition. + added[i].setHitCount(toAdds[i].getHitCount()); + } + + if (!StringUtils.equals(toAdds[i].getLogMessage(), added[i].getLogMessage())) { + added[i].setLogMessage(toAdds[i].getLogMessage()); + } + + if (!StringUtils.equals(toAdds[i].getCondition(), added[i].getCondition())) { + added[i].setCondition(toAdds[i].getCondition()); + } + + } + res.add(this.convertDebuggerBreakpointToClient(added[i], context)); + } + response.body = new Responses.SetBreakpointsResponseBody(res); + return CompletableFuture.completedFuture(response); + } catch (DebugException e) { + throw AdapterUtils.createCompletionException( + String.format("Failed to setBreakpoint. Reason: '%s'", e.toString()), + ErrorCode.SET_BREAKPOINT_FAILURE); + } + } + + public static String normalizeSourcePath(Types.Source source, IDebugAdapterContext context) { + String clientPath = source.path; + if (AdapterUtils.isWindows()) { + // VSCode may send drive letters with inconsistent casing which will mess up the key + // in the BreakpointManager. See https://github.com/Microsoft/vscode/issues/6268 + // Normalize the drive letter casing. Note that drive letters + // are not localized so invariant is safe here. + String drivePrefix = FilenameUtils.getPrefix(clientPath); + if (drivePrefix != null && drivePrefix.length() >= 2 + && Character.isLowerCase(drivePrefix.charAt(0)) && drivePrefix.charAt(1) == ':') { + drivePrefix = drivePrefix.substring(0, 2); // d:\ is an illegal regex string, convert it to d: + clientPath = clientPath.replaceFirst(drivePrefix, drivePrefix.toUpperCase()); + } + } + String sourcePath = clientPath; + if (source.sourceReference != 0 && context.getSourceUri(source.sourceReference) != null) { + sourcePath = context.getSourceUri(source.sourceReference); + } else if (StringUtils.isNotBlank(clientPath)) { + // See the bug https://github.com/Microsoft/vscode/issues/30996 + // Source.path in the SetBreakpointArguments could be a file system path or uri. + sourcePath = AdapterUtils.convertPath(clientPath, AdapterUtils.isUri(clientPath), context.isDebuggerPathsAreUri()); + } + + return sourcePath; + } + + private IBreakpoint getAssociatedEvaluatableBreakpoint(IDebugAdapterContext context, BreakpointEvent event) { + return Arrays.asList(context.getBreakpointManager().getBreakpoints()).stream().filter( + bp -> { + return bp instanceof IEvaluatableBreakpoint + && ((IEvaluatableBreakpoint) bp).containsEvaluatableExpression() + && bp.requests().contains(event.request()); + } + ).findFirst().orElse(null); + } + + private void registerBreakpointHandler(IDebugAdapterContext context) { + IDebugSession debugSession = context.getDebugSession(); + if (debugSession != null) { + debugSession.getEventHub().events().filter(debugEvent -> debugEvent.event instanceof BreakpointEvent).subscribe(debugEvent -> { + Event event = debugEvent.event; + if (debugEvent.eventSet.size() > 1 && debugEvent.eventSet.stream().anyMatch(t -> t instanceof StepEvent)) { + // The StepEvent and BreakpointEvent are grouped in the same event set only if they occurs at the same location and in the same thread. + // In order to avoid two duplicated StoppedEvents, the debugger will skip the BreakpointEvent. + } else { + ThreadReference bpThread = ((BreakpointEvent) event).thread(); + IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class); + if (engine.isInEvaluation(bpThread)) { + return; + } + + // find the breakpoint related to this breakpoint event + IBreakpoint expressionBP = getAssociatedEvaluatableBreakpoint(context, (BreakpointEvent) event); + String breakpointName = computeBreakpointName(event.request()); + + if (expressionBP != null) { + CompletableFuture.runAsync(() -> { + engine.evaluateForBreakpoint((IEvaluatableBreakpoint) expressionBP, bpThread).whenComplete((value, ex) -> { + boolean resume = handleEvaluationResult(context, bpThread, (IEvaluatableBreakpoint) expressionBP, value, ex); + // Clear the evaluation environment caused by above evaluation. + engine.clearState(bpThread); + + if (resume) { + debugEvent.eventSet.resume(); + } else { + context.getThreadCache().addEventThread(bpThread); + context.getProtocolServer().sendEvent(new Events.StoppedEvent( + breakpointName, bpThread.uniqueID())); + } + }); + }); + } else { + context.getThreadCache().addEventThread(bpThread); + context.getProtocolServer().sendEvent(new Events.StoppedEvent( + breakpointName, bpThread.uniqueID())); + } + debugEvent.shouldResume = false; + } + }); + } + } + + private String computeBreakpointName(EventRequest request) { + switch ((int) request.getProperty(IBreakpoint.REQUEST_TYPE)) { + case IBreakpoint.REQUEST_TYPE_LAMBDA: + return "lambda breakpoint"; + case IBreakpoint.REQUEST_TYPE_METHOD: + return "function breakpoint"; + default: + return "breakpoint"; + } + } + + /** + * Check whether the condition expression is satisfied, and return a boolean value to determine to resume the thread or not. + */ + public static boolean handleEvaluationResult(IDebugAdapterContext context, ThreadReference bpThread, IEvaluatableBreakpoint breakpoint, + Value value, Throwable ex) { + if (StringUtils.isNotBlank(breakpoint.getLogMessage())) { + if (ex != null) { + logger.log(Level.SEVERE, String.format("[Logpoint]: %s", ex.getMessage() != null ? ex.getMessage() : ex.toString()), ex); + context.getProtocolServer().sendEvent(new Events.UserNotificationEvent( + Events.UserNotificationEvent.NotificationType.ERROR, + String.format("[Logpoint] Log message '%s' error: %s", breakpoint.getLogMessage(), ex.getMessage()))); + } else if (value != null) { + if (value instanceof StringReference) { + String message = ((StringReference) value).value(); + context.getProtocolServer().sendEvent(Events.OutputEvent.createConsoleOutput( + message + System.lineSeparator())); + } + } + return true; + } else { + boolean resume = false; + boolean resultNotBoolean = false; + if (value != null && ex == null) { + if (value instanceof BooleanValue) { + resume = !((BooleanValue) value).booleanValue(); + } else if (value instanceof ObjectReference + && ((ObjectReference) value).type().name().equals("java.lang.Boolean")) { + // get boolean value from java.lang.Boolean object + Field field = ((ReferenceType) ((ObjectReference) value).type()).fieldByName("value"); + resume = !((BooleanValue) ((ObjectReference) value).getValue(field)).booleanValue(); + } else { + resultNotBoolean = true; + } + } + if (resume) { + return true; + } else { + if (context.isVmTerminated()) { + // do nothing + } else if (ex != null) { + if (!(ex instanceof VMDisconnectedException || ex.getCause() instanceof VMDisconnectedException)) { + logger.log(Level.SEVERE, String.format("[ConditionalBreakpoint]: %s", ex.getMessage() != null ? ex.getMessage() : ex.toString()), ex); + context.getProtocolServer().sendEvent(new Events.UserNotificationEvent( + Events.UserNotificationEvent.NotificationType.ERROR, + String.format("Breakpoint condition '%s' error: %s", breakpoint.getCondition(), ex.getMessage()))); + } + } else if (value == null || resultNotBoolean) { + context.getProtocolServer().sendEvent(new Events.UserNotificationEvent( + Events.UserNotificationEvent.NotificationType.WARNING, + String.format("Result of breakpoint condition '%s' is not a boolean, please correct your expression.", breakpoint.getCondition()))); + } + return false; + } + } + } + + private Types.Breakpoint convertDebuggerBreakpointToClient(IBreakpoint breakpoint, IDebugAdapterContext context) { + int id = (int) breakpoint.getProperty("id"); + boolean verified = breakpoint.getProperty("verified") != null && (boolean) breakpoint.getProperty("verified"); + int lineNumber = AdapterUtils.convertLineNumber(breakpoint.getLineNumber(), context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); + return new Types.Breakpoint(id, verified, lineNumber, ""); + } + + private IBreakpoint[] convertClientBreakpointsToDebugger(String sourceFile, Types.SourceBreakpoint[] sourceBreakpoints, IDebugAdapterContext context) + throws DebugException { + Types.SourceBreakpoint[] debugSourceBreakpoints = Stream.of(sourceBreakpoints).map(sourceBreakpoint -> { + int line = AdapterUtils.convertLineNumber(sourceBreakpoint.line, context.isClientLinesStartAt1(), context.isDebuggerLinesStartAt1()); + int column = AdapterUtils.convertColumnNumber(sourceBreakpoint.column, context.isClientColumnsStartAt1(), context.isDebuggerColumnsStartAt1()); + return new Types.SourceBreakpoint(line, column); + }).toArray(Types.SourceBreakpoint[]::new); + + ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class); + JavaBreakpointLocation[] locations = sourceProvider.getBreakpointLocations(sourceFile, debugSourceBreakpoints); + IBreakpoint[] breakpoints = new IBreakpoint[locations.length]; + for (int i = 0; i < locations.length; i++) { + int hitCount = 0; + try { + hitCount = Integer.parseInt(sourceBreakpoints[i].hitCondition); + } catch (NumberFormatException e) { + hitCount = 0; // If hitCount is an illegal number, ignore hitCount condition. + } + breakpoints[i] = context.getDebugSession().createBreakpoint(locations[i], hitCount, sourceBreakpoints[i].condition, + sourceBreakpoints[i].logMessage); + if (sourceProvider.supportsRealtimeBreakpointVerification() && StringUtils.isNotBlank(locations[i].className())) { + breakpoints[i].putProperty("verified", true); + } + } + return breakpoints; + } + + private void reinstallBreakpoints(IDebugAdapterContext context, List typenames) { + if (typenames == null || typenames.isEmpty()) { + return; + } + IBreakpoint[] breakpoints = context.getBreakpointManager().getBreakpoints(); + + for (IBreakpoint breakpoint : breakpoints) { + if (typenames.contains(breakpoint.className())) { + try { + breakpoint.close(); + breakpoint.install().thenAccept(bp -> { + Events.BreakpointEvent bpEvent = new Events.BreakpointEvent("new", this.convertDebuggerBreakpointToClient(bp, context)); + context.getProtocolServer().sendEvent(bpEvent); + }); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e); + } + } + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java new file mode 100755 index 0000000..6d3e8c0 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetDataBreakpointsRequestHandler.java @@ -0,0 +1,167 @@ +/******************************************************************************* +* Copyright (c) 2019-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.IEvaluatableBreakpoint; +import com.microsoft.java.debug.core.IWatchpoint; +import com.microsoft.java.debug.core.Watchpoint; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Events.BreakpointEvent; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.SetDataBreakpointsArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types.Breakpoint; +import com.microsoft.java.debug.core.protocol.Types.DataBreakpoint; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.WatchpointEvent; + +public class SetDataBreakpointsRequestHandler implements IDebugRequestHandler { + private boolean registered = false; + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SETDATABREAKPOINTS); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + if (context.getDebugSession() == null) { + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, "Empty debug session."); + } + + if (!registered) { + registered = true; + registerWatchpointHandler(context); + } + + SetDataBreakpointsArguments dataBpArgs = (SetDataBreakpointsArguments) arguments; + IWatchpoint[] requestedWatchpoints = (dataBpArgs.breakpoints == null) ? new Watchpoint[0] : new Watchpoint[dataBpArgs.breakpoints.length]; + for (int i = 0; i < requestedWatchpoints.length; i++) { + DataBreakpoint dataBreakpoint = dataBpArgs.breakpoints[i]; + if (dataBreakpoint.dataId != null) { + String[] segments = dataBreakpoint.dataId.split("#"); + if (segments.length == 2 && StringUtils.isNotBlank(segments[0]) && StringUtils.isNotBlank(segments[1])) { + int hitCount = 0; + try { + hitCount = Integer.parseInt(dataBreakpoint.hitCondition); + } catch (NumberFormatException e) { + hitCount = 0; // If hitCount is an illegal number, ignore hitCount condition. + } + + String accessType = dataBreakpoint.accessType != null ? dataBreakpoint.accessType.label() : null; + requestedWatchpoints[i] = context.getDebugSession().createWatchPoint(segments[0], segments[1], accessType, + dataBreakpoint.condition, hitCount); + } + } + } + + IWatchpoint[] currentWatchpoints = context.getBreakpointManager().setWatchpoints(requestedWatchpoints); + List breakpoints = new ArrayList<>(); + for (int i = 0; i < currentWatchpoints.length; i++) { + if (currentWatchpoints[i] == null) { + breakpoints.add(new Breakpoint(false)); + continue; + } + + // If the requested watchpoint exists in the watchpoint manager, it will reuse the cached watchpoint object. + // Otherwise add the requested watchpoint to the cache. + // So if the returned watchpoint from the manager is same as the requested wantchpoint, this means it's a new watchpoint, need install it. + if (currentWatchpoints[i] == requestedWatchpoints[i]) { + currentWatchpoints[i].install().thenAccept(wp -> { + BreakpointEvent bpEvent = new BreakpointEvent("new", convertDebuggerWatchpointToClient(wp)); + context.getProtocolServer().sendEvent(bpEvent); + }); + } else { + if (currentWatchpoints[i].getHitCount() != requestedWatchpoints[i].getHitCount()) { + currentWatchpoints[i].setHitCount(requestedWatchpoints[i].getHitCount()); + } + + if (!Objects.equals(currentWatchpoints[i].getCondition(), requestedWatchpoints[i].getCondition())) { + currentWatchpoints[i].setCondition(requestedWatchpoints[i].getCondition()); + } + } + + breakpoints.add(convertDebuggerWatchpointToClient(currentWatchpoints[i])); + } + + response.body = new Responses.SetDataBreakpointsResponseBody(breakpoints); + return CompletableFuture.completedFuture(response); + } + + private Breakpoint convertDebuggerWatchpointToClient(IWatchpoint watchpoint) { + return new Breakpoint((int) watchpoint.getProperty("id"), + watchpoint.getProperty("verified") != null && (boolean) watchpoint.getProperty("verified")); + } + + private void registerWatchpointHandler(IDebugAdapterContext context) { + IDebugSession debugSession = context.getDebugSession(); + if (debugSession != null) { + debugSession.getEventHub().events().filter(debugEvent -> debugEvent.event instanceof WatchpointEvent).subscribe(debugEvent -> { + Event event = debugEvent.event; + ThreadReference bpThread = ((WatchpointEvent) event).thread(); + IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class); + if (engine.isInEvaluation(bpThread)) { + return; + } + + // Find the watchpoint related to this watchpoint event + IWatchpoint watchpoint = Stream.of(context.getBreakpointManager().getWatchpoints()) + .filter(wp -> { + return wp instanceof IEvaluatableBreakpoint + && ((IEvaluatableBreakpoint) wp).containsEvaluatableExpression() + && wp.requests().contains(event.request()); + }) + .findFirst().orElse(null); + + if (watchpoint != null) { + CompletableFuture.runAsync(() -> { + engine.evaluateForBreakpoint((IEvaluatableBreakpoint) watchpoint, bpThread).whenComplete((value, ex) -> { + boolean resume = SetBreakpointsRequestHandler.handleEvaluationResult( + context, bpThread, (IEvaluatableBreakpoint) watchpoint, value, ex); + // Clear the evaluation environment caused by above evaluation. + engine.clearState(bpThread); + + if (resume) { + debugEvent.eventSet.resume(); + } else { + context.getThreadCache().addEventThread(bpThread); + context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID())); + } + }); + }); + } else { + context.getThreadCache().addEventThread(bpThread); + context.getProtocolServer().sendEvent(new Events.StoppedEvent("data breakpoint", bpThread.uniqueID())); + } + debugEvent.shouldResume = false; + }); + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java new file mode 100755 index 0000000..3a4e642 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetExceptionBreakpointsRequestHandler.java @@ -0,0 +1,96 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.apache.commons.lang3.ArrayUtils; + +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.DebugSettings.IDebugSettingChangeListener; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.ClassFilters; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.SetExceptionBreakpointsArguments; +import com.microsoft.java.debug.core.protocol.Types; +import com.sun.jdi.event.VMDeathEvent; +import com.sun.jdi.event.VMDisconnectEvent; + +public class SetExceptionBreakpointsRequestHandler implements IDebugRequestHandler, IDebugSettingChangeListener { + private IDebugSession debugSession = null; + private boolean isInitialized = false; + private boolean notifyCaught = false; + private boolean notifyUncaught = false; + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SETEXCEPTIONBREAKPOINTS); + } + + @Override + public synchronized CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + if (context.getDebugSession() == null) { + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, "Empty debug session."); + } + + if (!isInitialized) { + isInitialized = true; + debugSession = context.getDebugSession(); + DebugSettings.addDebugSettingChangeListener(this); + debugSession.getEventHub().events().subscribe(debugEvent -> { + if (debugEvent.event instanceof VMDeathEvent + || debugEvent.event instanceof VMDisconnectEvent) { + DebugSettings.removeDebugSettingChangeListener(this); + } + }); + } + + String[] filters = ((SetExceptionBreakpointsArguments) arguments).filters; + try { + this.notifyCaught = ArrayUtils.contains(filters, Types.ExceptionBreakpointFilter.CAUGHT_EXCEPTION_FILTER_NAME); + this.notifyUncaught = ArrayUtils.contains(filters, Types.ExceptionBreakpointFilter.UNCAUGHT_EXCEPTION_FILTER_NAME); + setExceptionBreakpoints(context.getDebugSession(), this.notifyCaught, this.notifyUncaught); + return CompletableFuture.completedFuture(response); + } catch (Exception ex) { + throw AdapterUtils.createCompletionException( + String.format("Failed to setExceptionBreakpoints. Reason: '%s'", ex.toString()), + ErrorCode.SET_EXCEPTIONBREAKPOINT_FAILURE, + ex); + } + } + + private void setExceptionBreakpoints(IDebugSession debugSession, boolean notifyCaught, boolean notifyUncaught) { + ClassFilters exceptionFilters = DebugSettings.getCurrent().exceptionFilters; + String[] classFilters = (exceptionFilters == null ? null : exceptionFilters.allowClasses); + String[] classExclusionFilters = (exceptionFilters == null ? null : exceptionFilters.skipClasses); + debugSession.setExceptionBreakpoints(notifyCaught, notifyUncaught, classFilters, classExclusionFilters); + } + + @Override + public synchronized void update(DebugSettings oldSettings, DebugSettings newSettings) { + try { + if (newSettings != null && newSettings.exceptionFiltersUpdated) { + setExceptionBreakpoints(debugSession, notifyCaught, notifyUncaught); + } + } catch (Exception ex) { + DebugSettings.removeDebugSettingChangeListener(this); + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java new file mode 100755 index 0000000..59a948d --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetFunctionBreakpointsRequestHandler.java @@ -0,0 +1,192 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Gayan Perera - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.IEvaluatableBreakpoint; +import com.microsoft.java.debug.core.IMethodBreakpoint; +import com.microsoft.java.debug.core.MethodBreakpoint; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Events.BreakpointEvent; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.SetFunctionBreakpointsArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types.Breakpoint; +import com.microsoft.java.debug.core.protocol.Types.FunctionBreakpoint; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.event.MethodEntryEvent; + +public class SetFunctionBreakpointsRequestHandler implements IDebugRequestHandler { + private boolean registered = false; + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SETFUNCTIONBREAKPOINTS); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + if (context.getDebugSession() == null) { + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, + "Empty debug session."); + } + + if (!registered) { + registered = true; + registerMethodBreakpointHandler(context); + } + + SetFunctionBreakpointsArguments funcBpArgs = (SetFunctionBreakpointsArguments) arguments; + IMethodBreakpoint[] requestedMethodBreakpoints = (funcBpArgs.breakpoints == null) ? new IMethodBreakpoint[0] + : new MethodBreakpoint[funcBpArgs.breakpoints.length]; + for (int i = 0; i < requestedMethodBreakpoints.length; i++) { + FunctionBreakpoint funcBreakpoint = funcBpArgs.breakpoints[i]; + if (funcBreakpoint.name != null) { + String[] segments = funcBreakpoint.name.split("#"); + if (segments.length == 2 && StringUtils.isNotBlank(segments[0]) + && StringUtils.isNotBlank(segments[1])) { + int hitCount = 0; + try { + hitCount = Integer.parseInt(funcBreakpoint.hitCondition); + } catch (NumberFormatException e) { + hitCount = 0; // If hitCount is an illegal number, ignore hitCount condition. + } + requestedMethodBreakpoints[i] = context.getDebugSession().createFunctionBreakpoint(segments[0], + segments[1], + funcBreakpoint.condition, hitCount); + } + } + } + + IMethodBreakpoint[] currentMethodBreakpoints = context.getBreakpointManager() + .setMethodBreakpoints(requestedMethodBreakpoints); + List breakpoints = new ArrayList<>(); + for (int i = 0; i < currentMethodBreakpoints.length; i++) { + if (currentMethodBreakpoints[i] == null) { + breakpoints.add(new Breakpoint(false)); + continue; + } + + currentMethodBreakpoints[i].setAsync(context.asyncJDWP()); + // If the requested method breakpoint exists in the manager, it will reuse + // the cached breakpoint exists object. + // Otherwise add the requested method breakpoint to the cache. + // So if the returned method breakpoint from the manager is same as the + // requested method breakpoint, this means it's a new method breakpoint, need + // install it. + if (currentMethodBreakpoints[i] == requestedMethodBreakpoints[i]) { + currentMethodBreakpoints[i].install().thenAccept(wp -> { + BreakpointEvent bpEvent = new BreakpointEvent("changed", convertDebuggerMethodToClient(wp)); + context.getProtocolServer().sendEvent(bpEvent); + }); + } else { + if (currentMethodBreakpoints[i].getHitCount() != requestedMethodBreakpoints[i].getHitCount()) { + currentMethodBreakpoints[i].setHitCount(requestedMethodBreakpoints[i].getHitCount()); + } + + if (!Objects.equals(currentMethodBreakpoints[i].getCondition(), + requestedMethodBreakpoints[i].getCondition())) { + currentMethodBreakpoints[i].setCondition(requestedMethodBreakpoints[i].getCondition()); + } + } + + breakpoints.add(convertDebuggerMethodToClient(currentMethodBreakpoints[i])); + } + + response.body = new Responses.SetDataBreakpointsResponseBody(breakpoints); + return CompletableFuture.completedFuture(response); + } + + private Breakpoint convertDebuggerMethodToClient(IMethodBreakpoint methodBreakpoint) { + return new Breakpoint((int) methodBreakpoint.getProperty("id"), + methodBreakpoint.getProperty("verified") != null && (boolean) methodBreakpoint.getProperty("verified")); + } + + private void registerMethodBreakpointHandler(IDebugAdapterContext context) { + IDebugSession debugSession = context.getDebugSession(); + if (debugSession != null) { + debugSession.getEventHub().events().filter(debugEvent -> debugEvent.event instanceof MethodEntryEvent) + .subscribe(debugEvent -> { + MethodEntryEvent methodEntryEvent = (MethodEntryEvent) debugEvent.event; + ThreadReference bpThread = methodEntryEvent.thread(); + IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class); + + // Find the method breakpoint related to this method entry event + IMethodBreakpoint methodBreakpoint = Stream + .of(context.getBreakpointManager().getMethodBreakpoints()) + .filter(mp -> { + return mp.requests().contains(methodEntryEvent.request()) + && matches(methodEntryEvent, mp); + }) + .findFirst().orElse(null); + + if (methodBreakpoint != null) { + if (methodBreakpoint instanceof IEvaluatableBreakpoint + && ((IEvaluatableBreakpoint) methodBreakpoint).containsConditionalExpression()) { + if (engine.isInEvaluation(bpThread)) { + return; + } + CompletableFuture.runAsync(() -> { + engine.evaluateForBreakpoint((IEvaluatableBreakpoint) methodBreakpoint, bpThread) + .whenComplete((value, ex) -> { + boolean resume = SetBreakpointsRequestHandler.handleEvaluationResult( + context, bpThread, (IEvaluatableBreakpoint) methodBreakpoint, + value, + ex); + // Clear the evaluation environment caused by above evaluation. + engine.clearState(bpThread); + + if (resume) { + debugEvent.eventSet.resume(); + } else { + context.getThreadCache().addEventThread(bpThread); + context.getProtocolServer().sendEvent(new Events.StoppedEvent( + "function breakpoint", bpThread.uniqueID())); + } + }); + }); + + } else { + context.getThreadCache().addEventThread(bpThread); + context.getProtocolServer() + .sendEvent(new Events.StoppedEvent("function breakpoint", bpThread.uniqueID())); + } + + debugEvent.shouldResume = false; + } + }); + } + } + + private boolean matches(MethodEntryEvent methodEntryEvent, IMethodBreakpoint breakpoint) { + return breakpoint.className().equals(methodEntryEvent.location().declaringType().name()) + && breakpoint.methodName().equals(methodEntryEvent.method().name()); + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetVariableRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetVariableRequestHandler.java new file mode 100755 index 0000000..c2d3aa1 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SetVariableRequestHandler.java @@ -0,0 +1,260 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.adapter.variables.VariableProxy; +import com.microsoft.java.debug.core.adapter.variables.VariableUtils; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.SetVariableArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.sun.jdi.AbsentInformationException; +import com.sun.jdi.ArrayReference; +import com.sun.jdi.ArrayType; +import com.sun.jdi.ClassNotLoadedException; +import com.sun.jdi.ClassType; +import com.sun.jdi.Field; +import com.sun.jdi.InvalidTypeException; +import com.sun.jdi.LocalVariable; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; +import com.sun.jdi.Type; +import com.sun.jdi.TypeComponent; +import com.sun.jdi.Value; + +public class SetVariableRequestHandler implements IDebugRequestHandler { + private static final String PATTERN = "([a-zA-Z_0-9$]+)\\s*\\(([^)]+)\\)"; + private IDebugAdapterContext context = null; + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SETVARIABLE); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + SetVariableArguments setVarArguments = (SetVariableArguments) arguments; + if (setVarArguments.value == null) { + // Just exit out of editing if we're given an empty expression. + return CompletableFuture.completedFuture(response); + } else if (setVarArguments.variablesReference == -1) { + throw AdapterUtils.createCompletionException( + "SetVariablesRequest: property 'variablesReference' is missing, null, or empty", + ErrorCode.ARGUMENT_MISSING); + } else if (StringUtils.isBlank(setVarArguments.name)) { + throw AdapterUtils.createCompletionException( + "SetVariablesRequest: property 'name' is missing, null, or empty", + ErrorCode.ARGUMENT_MISSING); + } + + this.context = context; + boolean showStaticVariables = DebugSettings.getCurrent().showStaticVariables; + IVariableFormatter variableFormatter = context.getVariableFormatter(); + Map options = variableFormatter.getDefaultOptions(); + VariableUtils.applyFormatterOptions(options, setVarArguments.format != null && setVarArguments.format.hex); + + Object container = context.getRecyclableIdPool().getObjectById(setVarArguments.variablesReference); + // container is null means the stack frame is continued by user manually. + if (container == null) { + throw AdapterUtils.createCompletionException( + "Failed to set variable. Reason: Cannot set value because the thread is resumed.", + ErrorCode.SET_VARIABLE_FAILURE); + } + + String name = setVarArguments.name; + Value newValue = null; + String belongToClass = null; + + if (setVarArguments.name.contains("(")) { + name = setVarArguments.name.replaceFirst(PATTERN, "$1"); + belongToClass = setVarArguments.name.replaceFirst(PATTERN, "$2"); + } + + try { + Object containerObj = ((VariableProxy) container).getProxiedVariable(); + if (containerObj instanceof StackFrameReference) { + StackFrameReference stackFrameReference = (StackFrameReference) containerObj; + StackFrame sf = context.getStackFrameManager().getStackFrame(stackFrameReference); + newValue = handleSetValueForStackFrame(name, belongToClass, setVarArguments.value, + showStaticVariables, sf, options); + } else if (containerObj instanceof ObjectReference) { + newValue = handleSetValueForObject(name, belongToClass, setVarArguments.value, (ObjectReference) containerObj, options); + } else { + throw AdapterUtils.createCompletionException( + String.format("SetVariableRequest: Variable %s cannot be found.", setVarArguments.variablesReference), + ErrorCode.SET_VARIABLE_FAILURE); + } + } catch (IllegalArgumentException | AbsentInformationException | InvalidTypeException + | UnsupportedOperationException | ClassNotLoadedException e) { + throw AdapterUtils.createCompletionException( + String.format("Failed to set variable. Reason: %s", e.toString()), + ErrorCode.SET_VARIABLE_FAILURE, + e); + } + int referenceId = 0; + if (newValue instanceof ObjectReference && VariableUtils.hasChildren(newValue, showStaticVariables)) { + long threadId = ((VariableProxy) container).getThreadId(); + String scopeName = ((VariableProxy) container).getScope(); + VariableProxy varProxy = new VariableProxy(((VariableProxy) container).getThread(), scopeName, newValue, (VariableProxy) container, name); + referenceId = context.getRecyclableIdPool().addObject(threadId, varProxy); + } + + int indexedVariables = 0; + if (newValue instanceof ArrayReference) { + indexedVariables = ((ArrayReference) newValue).length(); + } + response.body = new Responses.SetVariablesResponseBody( + context.getVariableFormatter().typeToString(newValue == null ? null : newValue.type(), options), // type + context.getVariableFormatter().valueToString(newValue, options), // value, + referenceId, indexedVariables); + return CompletableFuture.completedFuture(response); + } + + private Value handleSetValueForObject(String name, String belongToClass, String valueString, + ObjectReference container, Map options) throws InvalidTypeException, ClassNotLoadedException { + Value newValue; + if (container instanceof ArrayReference) { + ArrayReference array = (ArrayReference) container; + Type eleType = ((ArrayType) array.referenceType()).componentType(); + newValue = setArrayValue(array, eleType, Integer.parseInt(name), valueString, options); + } else { + if (StringUtils.isBlank(belongToClass)) { + Field field = container.referenceType().fieldByName(name); + if (field != null) { + if (field.isStatic()) { + newValue = this.setStaticFieldValue(container.referenceType(), field, name, valueString, options); + } else { + newValue = this.setObjectFieldValue(container, field, name, valueString, options); + } + } else { + throw new IllegalArgumentException( + String.format("SetVariableRequest: Variable %s cannot be found.", name)); + } + } else { + newValue = setFieldValueWithConflict(container, container.referenceType().allFields(), name, belongToClass, valueString, options); + } + } + return newValue; + } + + private Value handleSetValueForStackFrame(String name, String belongToClass, String valueString, + boolean showStaticVariables, StackFrame container, Map options) + throws AbsentInformationException, InvalidTypeException, ClassNotLoadedException { + Value newValue; + if (name.equals("this")) { + throw new UnsupportedOperationException("SetVariableRequest: 'This' variable cannot be changed."); + } + LocalVariable variable = container.visibleVariableByName(name); + if (StringUtils.isBlank(belongToClass) && variable != null) { + newValue = this.setFrameValue(container, variable, valueString, options); + } else { + if (showStaticVariables && container.location().method().isStatic()) { + ReferenceType type = container.location().declaringType(); + if (StringUtils.isBlank(belongToClass)) { + Field field = type.fieldByName(name); + newValue = setStaticFieldValue(type, field, name, valueString, options); + } else { + newValue = setFieldValueWithConflict(null, type.allFields(), name, belongToClass, + valueString, options); + } + } else { + throw new UnsupportedOperationException( + String.format("SetVariableRequest: Variable %s cannot be found.", name)); + } + } + return newValue; + } + + private Value setValueProxy(Type type, String value, SetValueFunction setValueFunc, Map options) + throws ClassNotLoadedException, InvalidTypeException { + Value newValue = context.getVariableFormatter().stringToValue(value, type, options); + setValueFunc.apply(newValue); + return newValue; + } + + private Value setStaticFieldValue(Type declaringType, Field field, String name, String value, Map options) + throws ClassNotLoadedException, InvalidTypeException { + if (field.isFinal()) { + throw new UnsupportedOperationException( + String.format("SetVariableRequest: Final field %s cannot be changed.", name)); + } + if (!(declaringType instanceof ClassType)) { + throw new UnsupportedOperationException( + String.format("SetVariableRequest: Field %s in interface cannot be changed.", name)); + } + return setValueProxy(field.type(), value, newValue -> ((ClassType) declaringType).setValue(field, newValue), options); + } + + private Value setFrameValue(StackFrame frame, LocalVariable localVariable, String value, Map options) + throws ClassNotLoadedException, InvalidTypeException { + return setValueProxy(localVariable.type(), value, newValue -> frame.setValue(localVariable, newValue), options); + } + + private Value setObjectFieldValue(ObjectReference obj, Field field, String name, String value, Map options) + throws ClassNotLoadedException, InvalidTypeException { + if (field.isFinal()) { + throw new UnsupportedOperationException( + String.format("SetVariableRequest: Final field %s cannot be changed.", name)); + } + return setValueProxy(field.type(), value, newValue -> obj.setValue(field, newValue), options); + } + + private Value setArrayValue(ArrayReference array, Type eleType, int index, String value, Map options) + throws ClassNotLoadedException, InvalidTypeException { + return setValueProxy(eleType, value, newValue -> array.setValue(index, newValue), options); + } + + private Value setFieldValueWithConflict(ObjectReference obj, List fields, String name, String belongToClass, + String value, Map options) throws ClassNotLoadedException, InvalidTypeException { + Field field; + // first try to resolve field by fully qualified name + List narrowedFields = fields.stream().filter(TypeComponent::isStatic) + .filter(t -> t.name().equals(name) && t.declaringType().name().equals(belongToClass)) + .collect(Collectors.toList()); + if (narrowedFields.isEmpty()) { + // second try to resolve field by formatted name + narrowedFields = fields.stream().filter(TypeComponent::isStatic) + .filter(t -> t.name().equals(name) + && context.getVariableFormatter().typeToString(t.declaringType(), options).equals(belongToClass)) + .collect(Collectors.toList()); + } + if (narrowedFields.size() == 1) { + field = narrowedFields.get(0); + } else { + throw new UnsupportedOperationException(String.format("SetVariableRequest: Name conflicted for %s.", name)); + } + return field.isStatic() ? setStaticFieldValue(field.declaringType(), field, name, value, options) + : this.setObjectFieldValue(obj, field, name, value, options); + } + + @FunctionalInterface + interface SetValueFunction { + void apply(Value value) throws InvalidTypeException, ClassNotLoadedException; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/SourceRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SourceRequestHandler.java new file mode 100755 index 0000000..01ae090 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/SourceRequestHandler.java @@ -0,0 +1,50 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.SourceArguments; +import com.microsoft.java.debug.core.protocol.Responses; + +public class SourceRequestHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.SOURCE); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + int sourceReference = ((SourceArguments) arguments).sourceReference; + if (sourceReference <= 0) { + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.ARGUMENT_MISSING, + "SourceRequest: property 'sourceReference' is missing, null, or empty"); + } else { + String uri = context.getSourceUri(sourceReference); + ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class); + response.body = new Responses.SourceResponseBody(sourceProvider.getSourceContents(uri)); + return CompletableFuture.completedFuture(response); + } + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java new file mode 100755 index 0000000..fbfce49 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java @@ -0,0 +1,290 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.io.File; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.AsyncJdwpUtils; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; +import com.microsoft.java.debug.core.adapter.formatter.SimpleTypeFormatter; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.StackTraceArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types; +import com.sun.jdi.AbsentInformationException; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.LocalVariable; +import com.sun.jdi.Location; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectCollectedException; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.request.BreakpointRequest; + +public class StackTraceRequestHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.STACKTRACE); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + StackTraceArguments stacktraceArgs = (StackTraceArguments) arguments; + List result = new ArrayList<>(); + if (stacktraceArgs.startFrame < 0 || stacktraceArgs.levels < 0) { + response.body = new Responses.StackTraceResponseBody(result, 0); + return CompletableFuture.completedFuture(response); + } + ThreadReference thread = context.getThreadCache().getThread(stacktraceArgs.threadId); + if (thread == null) { + thread = DebugUtility.getThread(context.getDebugSession(), stacktraceArgs.threadId); + } + int totalFrames = 0; + if (thread != null) { + try { + // Thread state has changed and then invalidate the stack frame cache. + if (stacktraceArgs.startFrame == 0) { + context.getStackFrameManager().clearStackFrames(thread); + } + + totalFrames = thread.frameCount(); + int count = stacktraceArgs.levels == 0 ? totalFrames - stacktraceArgs.startFrame + : Math.min(totalFrames - stacktraceArgs.startFrame, stacktraceArgs.levels); + if (totalFrames <= stacktraceArgs.startFrame) { + response.body = new Responses.StackTraceResponseBody(result, totalFrames); + return CompletableFuture.completedFuture(response); + } + + StackFrame[] frames = context.getStackFrameManager().reloadStackFrames(thread, stacktraceArgs.startFrame, count); + List jdiFrames = resolveStackFrameInfos(frames, context.asyncJDWP()); + for (int i = 0; i < count; i++) { + StackFrameReference frameReference = new StackFrameReference(thread, stacktraceArgs.startFrame + i); + int frameId = context.getRecyclableIdPool().addObject(stacktraceArgs.threadId, frameReference); + StackFrameInfo jdiFrame = jdiFrames.get(i); + Types.StackFrame lspFrame = convertDebuggerStackFrameToClient(jdiFrame, frameId, i == 0, context); + result.add(lspFrame); + frameReference.setSource(lspFrame.source); + } + } catch (IncompatibleThreadStateException | IndexOutOfBoundsException | URISyntaxException + | AbsentInformationException | ObjectCollectedException + | CancellationException | CompletionException e) { + // when error happens, the possible reason is: + // 1. the vscode has wrong parameter/wrong uri + // 2. the thread actually terminates + // TODO: should record a error log here. + } + } + response.body = new Responses.StackTraceResponseBody(result, totalFrames); + return CompletableFuture.completedFuture(response); + } + + private static List resolveStackFrameInfos(StackFrame[] frames, boolean async) + throws AbsentInformationException, IncompatibleThreadStateException { + List jdiFrames = new ArrayList<>(); + List> futures = new ArrayList<>(); + for (StackFrame frame : frames) { + StackFrameInfo jdiFrame = new StackFrameInfo(frame); + jdiFrame.location = jdiFrame.frame.location(); + jdiFrame.method = jdiFrame.location.method(); + jdiFrame.methodName = jdiFrame.method.name(); + jdiFrame.isNative = jdiFrame.method.isNative(); + jdiFrame.declaringType = jdiFrame.location.declaringType(); + if (async) { + // JDWP Command: M_LINE_TABLE + futures.add(AsyncJdwpUtils.runAsync(() -> { + jdiFrame.lineNumber = jdiFrame.location.lineNumber(); + })); + + // JDWP Commands: RT_SOURCE_DEBUG_EXTENSION, RT_SOURCE_FILE + futures.add(AsyncJdwpUtils.runAsync(() -> { + try { + // When the .class file doesn't contain source information in meta data, + // invoking Location#sourceName() would throw AbsentInformationException. + jdiFrame.sourceName = jdiFrame.declaringType.sourceName(); + } catch (AbsentInformationException e) { + jdiFrame.sourceName = null; + } + })); + + // JDWP Command: RT_SIGNATURE + futures.add(AsyncJdwpUtils.runAsync(() -> { + jdiFrame.typeSignature = jdiFrame.declaringType.signature(); + })); + } else { + jdiFrame.lineNumber = jdiFrame.location.lineNumber(); + jdiFrame.typeSignature = jdiFrame.declaringType.signature(); + try { + // When the .class file doesn't contain source information in meta data, + // invoking Location#sourceName() would throw AbsentInformationException. + jdiFrame.sourceName = jdiFrame.declaringType.sourceName(); + } catch (AbsentInformationException e) { + jdiFrame.sourceName = null; + } + } + + jdiFrames.add(jdiFrame); + } + + AsyncJdwpUtils.await(futures); + for (StackFrameInfo jdiFrame : jdiFrames) { + jdiFrame.typeName = jdiFrame.declaringType.name(); + jdiFrame.argumentTypeNames = jdiFrame.method.argumentTypeNames(); + if (jdiFrame.sourceName == null) { + String enclosingType = AdapterUtils.parseEnclosingType(jdiFrame.typeName); + jdiFrame.sourceName = enclosingType.substring(enclosingType.lastIndexOf('.') + 1) + ".java"; + jdiFrame.sourcePath = enclosingType.replace('.', File.separatorChar) + ".java"; + } else { + jdiFrame.sourcePath = jdiFrame.declaringType.sourcePaths(null).get(0); + } + } + + return jdiFrames; + } + + private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFrame, int frameId, boolean isTopFrame, IDebugAdapterContext context) + throws URISyntaxException, AbsentInformationException { + Types.Source clientSource = convertDebuggerSourceToClient(jdiFrame.typeName, jdiFrame.sourceName, jdiFrame.sourcePath, context); + String methodName = formatMethodName(jdiFrame.methodName, jdiFrame.argumentTypeNames, jdiFrame.typeName, true, true); + int clientLineNumber = AdapterUtils.convertLineNumber(jdiFrame.lineNumber, context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); + // Line number returns -1 if the information is not available; specifically, always returns -1 for native methods. + String presentationHint = null; + if (clientLineNumber < 0) { + presentationHint = "subtle"; + if (jdiFrame.isNative) { + // For native method, display a tip text "native method" in the Call Stack View. + methodName += "[native method]"; + } else { + // For other unavailable method, such as lambda expression's built-in methods run/accept/apply, + // display "Unknown Source" in the Call Stack View. + clientSource = null; + } + } + + int clientColumnNumber = context.isClientColumnsStartAt1() ? 1 : 0; + // If the top-level frame is a lambda method, it might be paused on a lambda breakpoint. + // We can associate its column number with the target lambda breakpoint. + if (isTopFrame && jdiFrame.methodName.startsWith("lambda$")) { + for (IBreakpoint breakpoint : context.getBreakpointManager().getBreakpoints()) { + if (breakpoint.getColumnNumber() > 0 && breakpoint.getLineNumber() == jdiFrame.lineNumber + && Objects.equals(jdiFrame.typeName, breakpoint.className())) { + boolean match = breakpoint.requests().stream().anyMatch(request -> { + return request instanceof BreakpointRequest + && Objects.equals(((BreakpointRequest) request).location(), jdiFrame.location); + }); + if (match) { + clientColumnNumber = AdapterUtils.convertColumnNumber(breakpoint.getColumnNumber(), + context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1()); + } + } + } + } + + return new Types.StackFrame(frameId, methodName, clientSource, clientLineNumber, clientColumnNumber, presentationHint); + } + + /** + * Find the source mapping for the specified source file name. + */ + public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedName, String sourceName, String relativeSourcePath, + IDebugAdapterContext context) throws URISyntaxException { + // use a lru cache for better performance + String uri = context.getSourceLookupCache().computeIfAbsent(fullyQualifiedName, key -> { + String fromProvider = context.getProvider(ISourceLookUpProvider.class).getSourceFileURI(key, relativeSourcePath); + // avoid return null which will cause the compute function executed again + return StringUtils.isBlank(fromProvider) ? "" : fromProvider; + }); + + if (!StringUtils.isBlank(uri)) { + // The Source.path could be a file system path or uri string. + if (uri.startsWith("file:")) { + String clientPath = AdapterUtils.convertPath(uri, context.isDebuggerPathsAreUri(), context.isClientPathsAreUri()); + return new Types.Source(sourceName, clientPath, 0); + } else { + // If the debugger returns uri in the Source.path for the StackTrace response, VSCode client will try to find a TextDocumentContentProvider + // to render the contents. + // Language Support for Java by Red Hat extension has already registered a jdt TextDocumentContentProvider to parse the jdt-based uri. + // The jdt uri looks like 'jdt://contents/rt.jar/java.io/PrintStream.class?=1.helloworld/%5C/usr%5C/lib%5C/jvm%5C/java-8-oracle%5C/jre%5C/ + // lib%5C/rt.jar%3Cjava.io(PrintStream.class'. + return new Types.Source(sourceName, uri, 0); + } + } else { + // If the source lookup engine cannot find the source file, then lookup it in the source directories specified by user. + String absoluteSourcepath = AdapterUtils.sourceLookup(context.getSourcePaths(), relativeSourcePath); + if (absoluteSourcepath != null) { + return new Types.Source(sourceName, absoluteSourcepath, 0); + } else { + return null; + } + } + } + + private String formatMethodName(String methodName, List argumentTypeNames, String fqn, boolean showContextClass, boolean showParameter) { + StringBuilder formattedName = new StringBuilder(); + if (showContextClass) { + formattedName.append(SimpleTypeFormatter.trimTypeName(fqn)); + formattedName.append("."); + } + formattedName.append(methodName); + if (showParameter) { + argumentTypeNames = argumentTypeNames.stream().map(SimpleTypeFormatter::trimTypeName).collect(Collectors.toList()); + formattedName.append("("); + formattedName.append(String.join(",", argumentTypeNames)); + formattedName.append(")"); + } + return formattedName.toString(); + } + + static class StackFrameInfo { + public StackFrame frame; + public Location location; + public Method method; + public String methodName; + public List argumentTypeNames = new ArrayList<>(); + public boolean isNative = false; + public int lineNumber; + public ReferenceType declaringType = null; + public String typeName; + public String typeSignature; + public String sourceName = ""; + public String sourcePath = ""; + + // variables + public List visibleVariables = null; + public ObjectReference thisObject; + + public StackFrameInfo(StackFrame frame) { + this.frame = frame; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepInTargetsRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepInTargetsRequestHandler.java new file mode 100755 index 0000000..5b3b735 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepInTargetsRequestHandler.java @@ -0,0 +1,138 @@ +/******************************************************************************* +* Copyright (c) 2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Gayan Perera - initial API and implementation +*******************************************************************************/ +package com.microsoft.java.debug.core.adapter.handler; + +import java.io.File; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider.MethodInvocation; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.StepInTargetsArguments; +import com.microsoft.java.debug.core.protocol.Responses.StepInTargetsResponse; +import com.microsoft.java.debug.core.protocol.Types.Source; +import com.microsoft.java.debug.core.protocol.Types.StepInTarget; +import com.sun.jdi.AbsentInformationException; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; + +public class StepInTargetsRequestHandler implements IDebugRequestHandler { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.STEPIN_TARGETS); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + final StepInTargetsArguments stepInTargetsArguments = (StepInTargetsArguments) arguments; + + final int frameId = stepInTargetsArguments.frameId; + return CompletableFuture.supplyAsync(() -> { + response.body = new StepInTargetsResponse( + findFrame(frameId, context).map(f -> findTargets(f, context)) + .orElse(Collections.emptyList()).toArray(StepInTarget[]::new)); + return response; + }); + } + + private Optional findFrame(int frameId, IDebugAdapterContext context) { + Object object = context.getRecyclableIdPool().getObjectById(frameId); + if (object instanceof StackFrameReference) { + return Optional.of((StackFrameReference) object); + } + return Optional.empty(); + } + + private List findTargets(StackFrameReference frameReference, IDebugAdapterContext context) { + StackFrame stackframe = context.getStackFrameManager().getStackFrame(frameReference); + if (stackframe == null) { + return Collections.emptyList(); + } + + Source source = frameReference.getSource() == null ? findSource(stackframe, context) : frameReference.getSource(); + if (source == null) { + return Collections.emptyList(); + } + + String sourceUri = AdapterUtils.convertPath(source.path, AdapterUtils.isUri(source.path), true); + if (sourceUri == null) { + return Collections.emptyList(); + } + + ISourceLookUpProvider sourceLookUpProvider = context.getProvider(ISourceLookUpProvider.class); + List invocations = sourceLookUpProvider.findMethodInvocations(sourceUri, stackframe.location().lineNumber()); + if (invocations.isEmpty()) { + return Collections.emptyList(); + } + + long threadId = stackframe.thread().uniqueID(); + List targets = new ArrayList<>(invocations.size()); + for (MethodInvocation methodInvocation : invocations) { + int id = context.getRecyclableIdPool().addObject(threadId, methodInvocation); + StepInTarget target = new StepInTarget(id, methodInvocation.expression); + target.column = AdapterUtils.convertColumnNumber(methodInvocation.columnStart, + context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1()); + target.endColumn = AdapterUtils.convertColumnNumber(methodInvocation.columnEnd, + context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1()); + target.line = AdapterUtils.convertLineNumber(methodInvocation.lineStart, + context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); + target.endLine = AdapterUtils.convertLineNumber(methodInvocation.lineEnd, + context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1()); + targets.add(target); + } + + // TODO remove the executed method calls. + return targets; + } + + private Source findSource(StackFrame frame, IDebugAdapterContext context) { + ReferenceType declaringType = frame.location().declaringType(); + String typeName = declaringType.name(); + String sourceName = null; + String sourcePath = null; + try { + // When the .class file doesn't contain source information in meta data, + // invoking ReferenceType#sourceName() would throw AbsentInformationException. + sourceName = declaringType.sourceName(); + sourcePath = declaringType.sourcePaths(null).get(0); + } catch (AbsentInformationException e) { + String enclosingType = AdapterUtils.parseEnclosingType(typeName); + sourceName = enclosingType.substring(enclosingType.lastIndexOf('.') + 1) + ".java"; + sourcePath = enclosingType.replace('.', File.separatorChar) + ".java"; + } + + try { + return StackTraceRequestHandler.convertDebuggerSourceToClient(typeName, sourceName, sourcePath, context); + } catch (URISyntaxException e) { + logger.log(Level.SEVERE, "Failed to resolve the source info of the stack frame.", e); + } + + return null; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java new file mode 100755 index 0000000..bd324a7 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java @@ -0,0 +1,488 @@ +/******************************************************************************* + * Copyright (c) 2017-2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import org.apache.commons.lang3.ArrayUtils; + +import com.microsoft.java.debug.core.AsyncJdwpUtils; +import com.microsoft.java.debug.core.DebugEvent; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.IDebugSession; +import com.microsoft.java.debug.core.JdiExceptionReference; +import com.microsoft.java.debug.core.JdiMethodResult; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider.MethodInvocation; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.StepArguments; +import com.microsoft.java.debug.core.protocol.Requests.StepFilters; +import com.microsoft.java.debug.core.protocol.Requests.StepInArguments; +import com.sun.jdi.ClassType; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.InterfaceType; +import com.sun.jdi.Location; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Value; +import com.sun.jdi.VoidValue; +import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.ExceptionEvent; +import com.sun.jdi.event.LocatableEvent; +import com.sun.jdi.event.MethodExitEvent; +import com.sun.jdi.event.StepEvent; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.EventRequestManager; +import com.sun.jdi.request.MethodExitRequest; +import com.sun.jdi.request.StepRequest; + +import io.reactivex.disposables.Disposable; + +public class StepRequestHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.STEPIN, Command.STEPOUT, Command.NEXT); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + if (context.getDebugSession() == null) { + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, "Debug Session doesn't exist."); + } + + StepArguments stepArguments = (StepArguments) arguments; + long threadId = stepArguments.threadId; + int targetId = (stepArguments instanceof StepInArguments) ? ((StepInArguments) stepArguments).targetId : 0; + ThreadReference thread = context.getThreadCache().getThread(threadId); + if (thread == null) { + thread = DebugUtility.getThread(context.getDebugSession(), threadId); + } + if (thread != null) { + JdiExceptionReference exception = context.getExceptionManager().removeException(threadId); + context.getStepResultManager().removeMethodResult(threadId); + try { + final ThreadReference targetThread = thread; + ThreadState threadState = new ThreadState(); + threadState.threadId = threadId; + threadState.pendingStepType = command; + threadState.eventSubscription = context.getDebugSession().getEventHub().events() + .filter(debugEvent -> (debugEvent.event instanceof StepEvent && debugEvent.event.request().equals(threadState.pendingStepRequest)) + || (debugEvent.event instanceof MethodExitEvent && debugEvent.event.request().equals(threadState.pendingMethodExitRequest)) + || debugEvent.event instanceof BreakpointEvent + || debugEvent.event instanceof ExceptionEvent) + .subscribe(debugEvent -> { + handleDebugEvent(debugEvent, context.getDebugSession(), context, threadState); + }); + + if (command == Command.STEPIN) { + threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, + context.getStepFilters().allowClasses, + context.getStepFilters().skipClasses); + } else if (command == Command.STEPOUT) { + threadState.pendingStepRequest = DebugUtility.createStepOutRequest(thread, + context.getStepFilters().allowClasses, + context.getStepFilters().skipClasses); + } else { + threadState.pendingStepRequest = DebugUtility.createStepOverRequest(thread, null); + } + + threadState.pendingMethodExitRequest = thread.virtualMachine().eventRequestManager().createMethodExitRequest(); + threadState.pendingMethodExitRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + + threadState.targetStepIn = targetId > 0 + ? (MethodInvocation) context.getRecyclableIdPool().getObjectById(targetId) : null; + if (context.asyncJDWP()) { + List> futures = new ArrayList<>(); + futures.add(AsyncJdwpUtils.runAsync(() -> { + // JDWP Command: TR_FRAMES + try { + threadState.topFrame = getTopFrame(targetThread); + threadState.stepLocation = threadState.topFrame.location(); + threadState.pendingMethodExitRequest.addClassFilter(threadState.stepLocation.declaringType()); + if (targetThread.virtualMachine().canUseInstanceFilters()) { + try { + // JDWP Command: SF_THIS_OBJECT + ObjectReference thisObject = threadState.topFrame.thisObject(); + if (thisObject != null) { + threadState.pendingMethodExitRequest.addInstanceFilter(thisObject); + } + } catch (Exception e) { + // ignore + } + } + } catch (IncompatibleThreadStateException e1) { + throw new CompletionException(e1); + } + })); + futures.add(AsyncJdwpUtils.runAsync( + // JDWP Command: OR_IS_COLLECTED + () -> threadState.pendingMethodExitRequest.addThreadFilter(targetThread) + )); + futures.add(AsyncJdwpUtils.runAsync(() -> { + try { + // JDWP Command: TR_FRAME_COUNT + threadState.stackDepth = targetThread.frameCount(); + } catch (IncompatibleThreadStateException e) { + throw new CompletionException(e); + } + })); + futures.add( + // JDWP Command: ER_SET + AsyncJdwpUtils.runAsync(() -> threadState.pendingStepRequest.enable()) + ); + + try { + AsyncJdwpUtils.await(futures); + } catch (CompletionException ex) { + if (ex.getCause() instanceof IncompatibleThreadStateException) { + throw (IncompatibleThreadStateException) ex.getCause(); + } + throw ex; + } + + // JDWP Command: ER_SET + threadState.pendingMethodExitRequest.enable(); + } else { + threadState.topFrame = getTopFrame(targetThread); + threadState.stackDepth = targetThread.frameCount(); + threadState.stepLocation = threadState.topFrame.location(); + threadState.pendingMethodExitRequest.addThreadFilter(thread); + threadState.pendingMethodExitRequest.addClassFilter(threadState.stepLocation.declaringType()); + if (targetThread.virtualMachine().canUseInstanceFilters()) { + try { + ObjectReference thisObject = threadState.topFrame.thisObject(); + if (thisObject != null) { + threadState.pendingMethodExitRequest.addInstanceFilter(thisObject); + } + } catch (Exception e) { + // ignore + } + } + threadState.pendingStepRequest.enable(); + threadState.pendingMethodExitRequest.enable(); + } + + context.getThreadCache().removeEventThread(thread.uniqueID()); + DebugUtility.resumeThread(thread); + ThreadsRequestHandler.checkThreadRunningAndRecycleIds(thread, context); + } catch (IncompatibleThreadStateException ex) { + // Roll back the Exception info if stepping fails. + context.getExceptionManager().setException(threadId, exception); + final String failureMessage = String.format("Failed to step because the thread '%s' is not suspended in the target VM.", thread.name()); + throw AdapterUtils.createCompletionException( + failureMessage, + ErrorCode.STEP_FAILURE, + ex); + } catch (IndexOutOfBoundsException ex) { + // Roll back the Exception info if stepping fails. + context.getExceptionManager().setException(threadId, exception); + final String failureMessage = String.format("Failed to step because the thread '%s' doesn't contain any stack frame", thread.name()); + throw AdapterUtils.createCompletionException( + failureMessage, + ErrorCode.STEP_FAILURE, + ex); + } catch (Exception ex) { + // Roll back the Exception info if stepping fails. + context.getExceptionManager().setException(threadId, exception); + final String failureMessage = String.format("Failed to step because of the error '%s'", ex.getMessage()); + throw AdapterUtils.createCompletionException( + failureMessage, + ErrorCode.STEP_FAILURE, + ex.getCause() != null ? ex.getCause() : ex); + } + } + + return CompletableFuture.completedFuture(response); + } + + private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, IDebugAdapterContext context, + ThreadState threadState) { + Event event = debugEvent.event; + EventRequestManager eventRequestManager = debugSession.getVM().eventRequestManager(); + + // When a breakpoint occurs, abort any pending step requests from the same thread. + if (event instanceof BreakpointEvent || event instanceof ExceptionEvent) { + // if we have a pending target step in then ignore and continue. + if (threadState.targetStepIn != null) { + debugEvent.shouldResume = true; + return; + } + + long threadId = ((LocatableEvent) event).thread().uniqueID(); + if (threadId == threadState.threadId && threadState.pendingStepRequest != null) { + threadState.deleteStepRequest(eventRequestManager); + threadState.deleteMethodExitRequest(eventRequestManager); + context.getStepResultManager().removeMethodResult(threadId); + if (threadState.eventSubscription != null) { + threadState.eventSubscription.dispose(); + } + } + } else if (event instanceof StepEvent) { + ThreadReference thread = ((StepEvent) event).thread(); + long threadId = thread.uniqueID(); + threadState.deleteStepRequest(eventRequestManager); + if (isStepFiltersConfigured(context.getStepFilters()) || threadState.targetStepIn != null) { + try { + if (threadState.pendingStepType == Command.STEPIN || threadState.targetStepIn != null) { + int currentStackDepth = thread.frameCount(); + StackFrame topFrame = getTopFrame(thread); + Location currentStepLocation = topFrame.location(); + if (threadState.targetStepIn != null) { + if (isStoppedAtSelectedMethod(topFrame, threadState.targetStepIn)) { + // hit: send StoppedEvent + } else { + if (currentStackDepth > threadState.stackDepth) { + context.getStepResultManager().removeMethodResult(threadId); + threadState.pendingStepRequest = DebugUtility.createStepOutRequest(thread, + context.getStepFilters().allowClasses, + context.getStepFilters().skipClasses); + threadState.pendingStepRequest.enable(); + debugEvent.shouldResume = true; + return; + } else if (currentStackDepth == threadState.stackDepth) { + // If the ending step location is same as the original location where the step into operation is originated, + // do another step of the same kind. + if (isSameLocation(currentStepLocation, threadState.stepLocation)) { + context.getStepResultManager().removeMethodResult(threadId); + threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, + context.getStepFilters().allowClasses, + context.getStepFilters().skipClasses); + threadState.pendingStepRequest.enable(); + debugEvent.shouldResume = true; + return; + } + } + } + } else if (shouldFilterLocation(threadState.stepLocation, currentStepLocation, context) + || shouldDoExtraStepInto(threadState.stackDepth, threadState.stepLocation, + currentStackDepth, currentStepLocation)) { + // If the ending step location is filtered, or same as the original location where the step into operation is originated, + // do another step of the same kind. + context.getStepResultManager().removeMethodResult(threadId); + String[] allowedClasses = context.getStepFilters().allowClasses; + if (currentStackDepth > threadState.stackDepth) { + threadState.pendingStepRequest = DebugUtility.createStepOutRequest(thread, + allowedClasses, + context.getStepFilters().skipClasses); + } else { + threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread, + allowedClasses, + context.getStepFilters().skipClasses); + } + threadState.pendingStepRequest.enable(); + debugEvent.shouldResume = true; + return; + } + } + } catch (IncompatibleThreadStateException | IndexOutOfBoundsException ex) { + // ignore. + } + } + threadState.deleteMethodExitRequest(eventRequestManager); + if (threadState.eventSubscription != null) { + threadState.eventSubscription.dispose(); + } + context.getThreadCache().addEventThread(thread); + context.getProtocolServer().sendEvent(new Events.StoppedEvent("step", thread.uniqueID())); + debugEvent.shouldResume = false; + } else if (event instanceof MethodExitEvent) { + MethodExitEvent methodExitEvent = (MethodExitEvent) event; + long threadId = methodExitEvent.thread().uniqueID(); + if (threadId == threadState.threadId && methodExitEvent.method().equals(threadState.stepLocation.method())) { + Value returnValue = methodExitEvent.returnValue(); + if (returnValue instanceof VoidValue) { + context.getStepResultManager().removeMethodResult(threadId); + } else { + JdiMethodResult methodResult = new JdiMethodResult(methodExitEvent.method(), returnValue); + context.getStepResultManager().setMethodResult(threadId, methodResult); + } + } + debugEvent.shouldResume = true; + } + } + + private boolean isStoppedAtSelectedMethod(StackFrame frame, MethodInvocation selectedMethod) { + Method method = frame.location().method(); + if (method != null + && Objects.equals(method.name(), selectedMethod.methodName) + && (Objects.equals(method.signature(), selectedMethod.methodSignature) + || Objects.equals(method.genericSignature(), selectedMethod.methodGenericSignature))) { + ObjectReference thisObject = frame.thisObject(); + ReferenceType currentType = (thisObject == null) ? method.declaringType() : thisObject.referenceType(); + if ("java.lang.Object".equals(selectedMethod.declaringTypeName)) { + return true; + } + + return isSubType(currentType, selectedMethod.declaringTypeName); + } + + return false; + } + + private boolean isSubType(ReferenceType currentType, String baseType) { + if (baseType.equals(currentType.name())) { + return true; + } + + if (currentType instanceof ClassType) { + ClassType classType = (ClassType) currentType; + ClassType superClassType = classType.superclass(); + if (superClassType != null && isSubType(superClassType, baseType)) { + return true; + } + + List interfaces = classType.allInterfaces(); + for (InterfaceType iface : interfaces) { + if (isSubType(iface, baseType)) { + return true; + } + } + } + + if (currentType instanceof InterfaceType) { + List superInterfaces = ((InterfaceType) currentType).superinterfaces(); + for (InterfaceType superInterface : superInterfaces) { + if (isSubType(superInterface, baseType)) { + return true; + } + } + } + + return false; + } + + private boolean isStepFiltersConfigured(StepFilters filters) { + if (filters == null) { + return false; + } + return ArrayUtils.isNotEmpty(filters.allowClasses) || ArrayUtils.isNotEmpty(filters.skipClasses) + || ArrayUtils.isNotEmpty(filters.classNameFilters) || filters.skipConstructors + || filters.skipStaticInitializers || filters.skipSynthetics; + } + + /** + * Return true if the StepEvent's location is a Method that the user has indicated to filter. + * + * @throws IncompatibleThreadStateException + * if the thread is not suspended in the target VM. + */ + private boolean shouldFilterLocation(Location originalLocation, Location currentLocation, IDebugAdapterContext context) + throws IncompatibleThreadStateException { + if (originalLocation == null || currentLocation == null) { + return false; + } + return !shouldFilterMethod(originalLocation.method(), context) && shouldFilterMethod(currentLocation.method(), context); + } + + private boolean shouldFilterMethod(Method method, IDebugAdapterContext context) { + return (context.getStepFilters().skipStaticInitializers && method.isStaticInitializer()) + || (context.getStepFilters().skipSynthetics && method.isSynthetic()) + || (context.getStepFilters().skipConstructors && method.isConstructor()); + } + + /** + * Check if the current top stack is same as the original top stack and if we + * are not in target step in we should not request an extra step in. But if we + * are processing a target step in, we only check if the original and current + * location are same. If they are not same we request a extra step in. + * + * @throws IncompatibleThreadStateException + * if the thread is not suspended in + * the target VM. + */ + private boolean shouldDoExtraStepInto(int originalStackDepth, Location originalLocation, int currentStackDepth, + Location currentLocation) + throws IncompatibleThreadStateException { + if (originalStackDepth != currentStackDepth) { + return false; + } + if (originalLocation == null) { + return false; + } + + Method originalMethod = originalLocation.method(); + Method currentMethod = currentLocation.method(); + if (!originalMethod.equals(currentMethod)) { + return false; + } + if (originalLocation.lineNumber() != currentLocation.lineNumber()) { + return false; + } + + return true; + } + + private boolean isSameLocation(Location original, Location current) { + if (original == null || current == null) { + return false; + } + + Method originalMethod = original.method(); + Method currentMethod = current.method(); + return originalMethod.equals(currentMethod) + && original.lineNumber() == current.lineNumber(); + } + + /** + * Return the top stack frame of the target thread. + * + * @param thread + * the target thread. + * @return the top frame. + * @throws IncompatibleThreadStateException + * if the thread is not suspended in the target VM. + * @throws IndexOutOfBoundsException + * if the thread doesn't contain any stack frame. + */ + private StackFrame getTopFrame(ThreadReference thread) throws IncompatibleThreadStateException { + return thread.frame(0); + } + + class ThreadState { + long threadId = -1; + Command pendingStepType; + StepRequest pendingStepRequest = null; + MethodExitRequest pendingMethodExitRequest = null; + int stackDepth = -1; + StackFrame topFrame = null; + Location stepLocation = null; + Disposable eventSubscription = null; + MethodInvocation targetStepIn = null; + + public void deleteMethodExitRequest(EventRequestManager manager) { + DebugUtility.deleteEventRequestSafely(manager, this.pendingMethodExitRequest); + this.pendingMethodExitRequest = null; + } + + public void deleteStepRequest(EventRequestManager manager) { + DebugUtility.deleteEventRequestSafely(manager, this.pendingStepRequest); + this.pendingStepRequest = null; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java new file mode 100755 index 0000000..2a90399 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/ThreadsRequestHandler.java @@ -0,0 +1,318 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.java.debug.core.AsyncJdwpUtils; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.ContinueArguments; +import com.microsoft.java.debug.core.protocol.Requests.PauseArguments; +import com.microsoft.java.debug.core.protocol.Requests.ThreadOperationArguments; +import com.microsoft.java.debug.core.protocol.Requests.ThreadsArguments; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types; +import com.sun.jdi.ObjectCollectedException; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.VMDisconnectedException; + +public class ThreadsRequestHandler implements IDebugRequestHandler { + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.THREADS, Command.PAUSE, Command.CONTINUE, Command.CONTINUEALL, + Command.CONTINUEOTHERS, Command.PAUSEALL, Command.PAUSEOTHERS); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, + IDebugAdapterContext context) { + if (context.getDebugSession() == null) { + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.EMPTY_DEBUG_SESSION, + "Debug Session doesn't exist."); + } + + switch (command) { + case THREADS: + return this.threads((ThreadsArguments) arguments, response, context); + case PAUSE: + return this.pause((PauseArguments) arguments, response, context); + case CONTINUE: + return this.resume((ContinueArguments) arguments, response, context); + case CONTINUEALL: + return this.resumeAll((ThreadOperationArguments) arguments, response, context); + case CONTINUEOTHERS: + return this.resumeOthers((ThreadOperationArguments) arguments, response, context); + case PAUSEALL: + return this.pauseAll((ThreadOperationArguments) arguments, response, context); + case PAUSEOTHERS: + return this.pauseOthers((ThreadOperationArguments) arguments, response, context); + default: + return AdapterUtils.createAsyncErrorResponse(response, ErrorCode.UNRECOGNIZED_REQUEST_FAILURE, + String.format("Unrecognized request: { _request: %s }", command.toString())); + } + } + + private CompletableFuture threads(ThreadsArguments arguments, Response response, IDebugAdapterContext context) { + ArrayList threads = new ArrayList<>(); + try { + List allThreads = context.getThreadCache().visibleThreads(context); + context.getThreadCache().resetThreads(allThreads); + allThreads = allThreads.stream().filter((thread) -> !context.getThreadCache().isDeathThread(thread.uniqueID())).collect(Collectors.toList()); + List jdiThreads = resolveThreadInfos(allThreads, context); + for (ThreadInfo jdiThread : jdiThreads) { + String name = StringUtils.isBlank(jdiThread.name) ? String.valueOf(jdiThread.thread.uniqueID()) : jdiThread.name; + threads.add(new Types.Thread(jdiThread.thread.uniqueID(), "Thread [" + name + "]")); + } + } catch (ObjectCollectedException | CancellationException | CompletionException ex) { + // allThreads may throw VMDisconnectedException when VM terminates and thread.name() may throw ObjectCollectedException + // when the thread is exiting. + } + response.body = new Responses.ThreadsResponseBody(threads); + return CompletableFuture.completedFuture(response); + } + + private static List resolveThreadInfos(List allThreads, IDebugAdapterContext context) { + List threadInfos = new ArrayList<>(allThreads.size()); + List> futures = new ArrayList<>(); + for (ThreadReference thread : allThreads) { + ThreadInfo threadInfo = new ThreadInfo(thread); + long threadId = thread.uniqueID(); + if (context.getThreadCache().getThreadName(threadId) != null) { + threadInfo.name = context.getThreadCache().getThreadName(threadId); + } else { + if (context.asyncJDWP()) { + futures.add(AsyncJdwpUtils.runAsync(() -> { + threadInfo.name = threadInfo.thread.name(); + context.getThreadCache().setThreadName(threadId, threadInfo.name); + })); + } else { + threadInfo.name = threadInfo.thread.name(); + context.getThreadCache().setThreadName(threadId, threadInfo.name); + } + } + + threadInfos.add(threadInfo); + } + + AsyncJdwpUtils.await(futures); + return threadInfos; + } + + private CompletableFuture pause(PauseArguments arguments, Response response, IDebugAdapterContext context) { + ThreadReference thread = context.getThreadCache().getThread(arguments.threadId); + if (thread == null) { + thread = DebugUtility.getThread(context.getDebugSession(), arguments.threadId); + } + if (thread != null) { + pauseThread(thread, context); + } else { + context.getStepResultManager().removeAllMethodResults(); + context.getDebugSession().suspend(); + context.getProtocolServer().sendEvent(new Events.StoppedEvent("pause", arguments.threadId, true)); + } + return CompletableFuture.completedFuture(response); + } + + private CompletableFuture resume(ContinueArguments arguments, Response response, IDebugAdapterContext context) { + boolean allThreadsContinued = true; + ThreadReference thread = context.getThreadCache().getThread(arguments.threadId); + if (thread == null) { + thread = DebugUtility.getThread(context.getDebugSession(), arguments.threadId); + } + /** + * See the jdi doc https://docs.oracle.com/javase/7/docs/jdk/api/jpda/jdi/com/sun/jdi/ThreadReference.html#resume(), + * suspends of both the virtual machine and individual threads are counted. Before a thread will run again, it must + * be resumed (through ThreadReference#resume() or VirtualMachine#resume()) the same number of times it has been suspended. + */ + if (thread != null) { + context.getThreadCache().removeEventThread(arguments.threadId); + context.getStepResultManager().removeMethodResult(arguments.threadId); + context.getExceptionManager().removeException(arguments.threadId); + allThreadsContinued = false; + DebugUtility.resumeThread(thread); + context.getStackFrameManager().clearStackFrames(thread); + checkThreadRunningAndRecycleIds(thread, context); + } else { + context.getStepResultManager().removeAllMethodResults(); + context.getExceptionManager().removeAllExceptions(); + resumeVM(context); + context.getStackFrameManager().clearStackFrames(); + context.getRecyclableIdPool().removeAllObjects(); + } + response.body = new Responses.ContinueResponseBody(allThreadsContinued); + return CompletableFuture.completedFuture(response); + } + + private CompletableFuture resumeAll(ThreadOperationArguments arguments, Response response, IDebugAdapterContext context) { + context.getStepResultManager().removeAllMethodResults(); + context.getExceptionManager().removeAllExceptions(); + resumeVM(context); + context.getProtocolServer().sendEvent(new Events.ContinuedEvent(arguments.threadId, true)); + context.getStackFrameManager().clearStackFrames(); + context.getRecyclableIdPool().removeAllObjects(); + return CompletableFuture.completedFuture(response); + } + + private CompletableFuture resumeOthers(ThreadOperationArguments arguments, Response response, IDebugAdapterContext context) { + List threads = context.getThreadCache().visibleThreads(context); + List> futures = new ArrayList<>(); + for (ThreadReference thread : threads) { + if (thread.uniqueID() == arguments.threadId) { + continue; + } + + if (context.asyncJDWP()) { + futures.add(AsyncJdwpUtils.runAsync(() -> resumeThread(thread, context))); + } else { + resumeThread(thread, context); + } + } + AsyncJdwpUtils.await(futures); + return CompletableFuture.completedFuture(response); + } + + private CompletableFuture pauseAll(ThreadOperationArguments arguments, Response response, IDebugAdapterContext context) { + context.getDebugSession().suspend(); + context.getProtocolServer().sendEvent(new Events.StoppedEvent("pause", arguments.threadId, true)); + return CompletableFuture.completedFuture(response); + } + + private CompletableFuture pauseOthers(ThreadOperationArguments arguments, Response response, IDebugAdapterContext context) { + List threads = context.getThreadCache().visibleThreads(context); + List> futures = new ArrayList<>(); + for (ThreadReference thread : threads) { + if (thread.uniqueID() == arguments.threadId) { + continue; + } + + if (context.asyncJDWP()) { + futures.add(AsyncJdwpUtils.runAsync(() -> pauseThread(thread, context))); + } else { + pauseThread(thread, context); + } + } + AsyncJdwpUtils.await(futures); + return CompletableFuture.completedFuture(response); + } + + /** + * Recycle the related ids owned by the specified thread. + */ + public static void checkThreadRunningAndRecycleIds(ThreadReference thread, IDebugAdapterContext context) { + try { + IEvaluationProvider engine = context.getProvider(IEvaluationProvider.class); + engine.clearState(thread); + context.getRecyclableIdPool().removeObjectsByOwner(thread.uniqueID()); + } catch (VMDisconnectedException ex) { + // isSuspended may throw VMDisconnectedException when the VM terminates + context.getRecyclableIdPool().removeAllObjects(); + } catch (ObjectCollectedException collectedEx) { + // isSuspended may throw ObjectCollectedException when the thread terminates + context.getRecyclableIdPool().removeObjectsByOwner(thread.uniqueID()); + } + } + + private void resumeVM(IDebugAdapterContext context) { + List visibleThreads = context.getThreadCache().visibleThreads(context); + context.getThreadCache().clearEventThread(); + + List> futures = new ArrayList<>(); + /** + * To ensure that all threads are fully resumed when the VM is resumed, make sure the suspend count + * of each thread is no larger than 1. + * Notes: Decrementing the thread' suspend count to 1 is on purpose, because it doesn't break the + * the thread's suspend state, and also make sure the next instruction vm.resume() is able to resume + * all threads fully. + */ + Consumer resumeThread = (ThreadReference tr) -> { + try { + while (tr.suspendCount() > 1) { + tr.resume(); + } + } catch (ObjectCollectedException ex) { + // Ignore it if the thread is garbage collected. + } + }; + for (ThreadReference tr : visibleThreads) { + if (context.asyncJDWP()) { + futures.add(AsyncJdwpUtils.runAsync(() -> resumeThread.accept(tr))); + } else { + resumeThread.accept(tr); + } + } + + AsyncJdwpUtils.await(futures); + context.getDebugSession().getVM().resume(); + } + + private void resumeThread(ThreadReference thread, IDebugAdapterContext context) { + try { + context.getThreadCache().removeEventThread(thread.uniqueID()); + int suspends = thread.suspendCount(); + if (suspends > 0) { + long threadId = thread.uniqueID(); + context.getExceptionManager().removeException(threadId); + DebugUtility.resumeThread(thread, suspends); + context.getProtocolServer().sendEvent(new Events.ContinuedEvent(threadId)); + context.getStackFrameManager().clearStackFrames(thread); + checkThreadRunningAndRecycleIds(thread, context); + } + } catch (ObjectCollectedException ex) { + // the thread is garbage collected. + context.getThreadCache().addDeathThread(thread.uniqueID()); + } + } + + private void pauseThread(ThreadReference thread, IDebugAdapterContext context) { + try { + // Ignore it if the thread status is unknown or zombie + if (!thread.isSuspended() && thread.status() > 0) { + long threadId = thread.uniqueID(); + context.getStepResultManager().removeMethodResult(threadId); + thread.suspend(); + context.getProtocolServer().sendEvent(new Events.StoppedEvent("pause", threadId)); + } + } catch (ObjectCollectedException ex) { + // the thread is garbage collected. + context.getThreadCache().addDeathThread(thread.uniqueID()); + } + } + + static class ThreadInfo { + public ThreadReference thread; + public String name; + + public ThreadInfo(ThreadReference thread) { + this.thread = thread; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/VMHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/VMHandler.java new file mode 100755 index 0000000..07c57d0 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/VMHandler.java @@ -0,0 +1,54 @@ +/******************************************************************************* +* Copyright (c) 2020 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import com.microsoft.java.debug.core.adapter.IVirtualMachineManager; +import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.VirtualMachineManager; + +public class VMHandler { + private IVirtualMachineManagerProvider vmProvider = null; + + public VMHandler() { + } + + public VMHandler(IVirtualMachineManagerProvider vmProvider) { + this.vmProvider = vmProvider; + } + + public IVirtualMachineManagerProvider getVmProvider() { + return vmProvider; + } + + public void setVmProvider(IVirtualMachineManagerProvider vmProvider) { + this.vmProvider = vmProvider; + } + + public void connectVirtualMachine(VirtualMachine vm) { + if (vm != null && vmProvider != null) { + VirtualMachineManager vmManager = vmProvider.getVirtualMachineManager(); + if (vmManager instanceof IVirtualMachineManager) { + ((IVirtualMachineManager) vmManager).connectVirtualMachine(vm); + } + } + } + + public void disconnectVirtualMachine(VirtualMachine vm) { + if (vm != null && vmProvider != null) { + VirtualMachineManager vmManager = vmProvider.getVirtualMachineManager(); + if (vmManager instanceof IVirtualMachineManager) { + ((IVirtualMachineManager) vmManager).disconnectVirtualMachine(vm); + } + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java b/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java new file mode 100755 index 0000000..11a6391 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java @@ -0,0 +1,510 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.handler; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import com.microsoft.java.debug.core.AsyncJdwpUtils; +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.JdiMethodResult; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; +import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.adapter.IStackFrameManager; +import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure.LogicalStructureExpression; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure.LogicalVariable; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager; +import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.adapter.variables.StringReferenceProxy; +import com.microsoft.java.debug.core.adapter.variables.Variable; +import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils; +import com.microsoft.java.debug.core.adapter.variables.VariableProxy; +import com.microsoft.java.debug.core.adapter.variables.VariableUtils; +import com.microsoft.java.debug.core.protocol.Messages.Response; +import com.microsoft.java.debug.core.protocol.Requests.Arguments; +import com.microsoft.java.debug.core.protocol.Requests.Command; +import com.microsoft.java.debug.core.protocol.Requests.VariablesArguments; +import com.microsoft.java.debug.core.protocol.Types.VariablePresentationHint; +import com.microsoft.java.debug.core.protocol.Responses; +import com.microsoft.java.debug.core.protocol.Types; +import com.sun.jdi.AbsentInformationException; +import com.sun.jdi.ArrayReference; +import com.sun.jdi.IntegerValue; +import com.sun.jdi.InternalException; +import com.sun.jdi.InvalidStackFrameException; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; +import com.sun.jdi.StringReference; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public class VariablesRequestHandler implements IDebugRequestHandler { + protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + /** + * When the debugger enables logical structures and + * toString settings, for each Object variable in the + * variable list, the debugger needs to check its + * superclass and interface to find out if it inherits + * from Collection or overrides the toString method. + * This will cause the debugger to send a lot of JDWP + * requests for them. For a test case with 4 object + * variables, the debug adapter may need to send more + * than 100 JDWP requests to handle these variable + * requests. To achieve a DAP latency of 1s with a + * single-threaded JDWP request processing strategy, + * a single JDWP latency is about 10ms. + */ + static final long USABLE_JDWP_LATENCY = 10/**ms*/; + + @Override + public List getTargetCommands() { + return Arrays.asList(Command.VARIABLES); + } + + @Override + public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { + IVariableFormatter variableFormatter = context.getVariableFormatter(); + VariablesArguments varArgs = (VariablesArguments) arguments; + + boolean showStaticVariables = DebugSettings.getCurrent().showStaticVariables; + + Map options = variableFormatter.getDefaultOptions(); + VariableUtils.applyFormatterOptions(options, varArgs.format != null && varArgs.format.hex); + IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class); + + List list = new ArrayList<>(); + Object container = context.getRecyclableIdPool().getObjectById(varArgs.variablesReference); + // vscode will always send variables request to a staled scope, return the empty list is ok since the next + // variable request will contain the right variablesReference. + if (container == null) { + response.body = new Responses.VariablesResponseBody(list); + return CompletableFuture.completedFuture(response); + } + + if (!(container instanceof VariableProxy)) { + throw AdapterUtils.createCompletionException( + String.format("VariablesRequest: Invalid variablesReference %d.", varArgs.variablesReference), + ErrorCode.GET_VARIABLE_FAILURE); + } + + VariableProxy containerNode = (VariableProxy) container; + + if (supportsToStringView(context) && containerNode.isLazyVariable()) { + Types.Variable typedVariable = this.resolveLazyVariable(context, containerNode, variableFormatter, options, evaluationEngine); + if (typedVariable != null) { + list.add(typedVariable); + response.body = new Responses.VariablesResponseBody(list); + return CompletableFuture.completedFuture(response); + } + } + List childrenList = new ArrayList<>(); + IStackFrameManager stackFrameManager = context.getStackFrameManager(); + String containerEvaluateName = containerNode.getEvaluateName(); + boolean isUnboundedTypeContainer = containerNode.isUnboundedType(); + if (containerNode.getProxiedVariable() instanceof StackFrameReference) { + StackFrameReference stackFrameReference = (StackFrameReference) containerNode.getProxiedVariable(); + StackFrame frame = stackFrameManager.getStackFrame(stackFrameReference); + if (frame == null) { + throw AdapterUtils.createCompletionException( + String.format("Invalid stackframe id %d to get variables.", varArgs.variablesReference), + ErrorCode.GET_VARIABLE_FAILURE); + } + try { + long threadId = stackFrameReference.getThread().uniqueID(); + JdiMethodResult result = context.getStepResultManager().getMethodResult(threadId); + if (result != null) { + String returnIcon = (AdapterUtils.isWin || AdapterUtils.isMac) ? "⎯►" : "->"; + childrenList.add(new Variable(returnIcon + result.method.name() + "()", result.value, null)); + } + + if (useAsyncJDWP(context)) { + childrenList.addAll(getVariablesOfFrameAsync(frame, showStaticVariables)); + } else { + childrenList.addAll(VariableUtils.listLocalVariables(frame)); + Variable thisVariable = VariableUtils.getThisVariable(frame); + if (thisVariable != null) { + childrenList.add(thisVariable); + } + if (showStaticVariables && frame.location().method().isStatic()) { + childrenList.addAll(VariableUtils.listStaticVariables(frame)); + } + } + } catch (CompletionException | InternalException | InvalidStackFrameException | CancellationException | AbsentInformationException e) { + throw AdapterUtils.createCompletionException( + String.format("Failed to get variables. Reason: %s", e.toString()), + ErrorCode.GET_VARIABLE_FAILURE, + e.getCause() != null ? e.getCause() : e); + } + } else { + try { + ObjectReference containerObj = (ObjectReference) containerNode.getProxiedVariable(); + if (supportsLogicStructureView(context) && evaluationEngine != null) { + JavaLogicalStructure logicalStructure = null; + try { + logicalStructure = JavaLogicalStructureManager.getLogicalStructure(containerObj); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to get the logical structure for the variable, fall back to the Object view.", e); + } + if (isUnboundedTypeContainer && logicalStructure != null && containerEvaluateName != null) { + containerEvaluateName = "((" + logicalStructure.getFullyQualifiedName() + ")" + containerEvaluateName + ")"; + isUnboundedTypeContainer = false; + } + while (logicalStructure != null) { + LogicalStructureExpression valueExpression = logicalStructure.getValueExpression(); + LogicalVariable[] logicalVariables = logicalStructure.getVariables(); + try { + if (valueExpression != null) { + containerEvaluateName = containerEvaluateName == null ? null : containerEvaluateName + "." + valueExpression.evaluateName; + isUnboundedTypeContainer = valueExpression.returnUnboundedType; + Value value = logicalStructure.getValue(containerObj, containerNode.getThread(), evaluationEngine); + if (value instanceof ObjectReference) { + containerObj = (ObjectReference) value; + logicalStructure = JavaLogicalStructureManager.getLogicalStructure(containerObj); + continue; + } else { + childrenList = Arrays.asList(new Variable("logical structure", value)); + } + } else if (logicalVariables != null && logicalVariables.length > 0) { + for (LogicalVariable logicalVariable : logicalVariables) { + String name = logicalVariable.getName(); + Value value = logicalVariable.getValue(containerObj, containerNode.getThread(), evaluationEngine); + Variable variable = new Variable(name, value, logicalVariable.getEvaluateName()); + variable.setUnboundedType(logicalVariable.returnUnboundedType()); + childrenList.add(variable); + } + } + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to get the logical structure for the variable, fall back to the Object view.", e); + } + + logicalStructure = null; + } + } + + if (childrenList.isEmpty() && VariableUtils.hasChildren(containerObj, showStaticVariables)) { + if (varArgs.count > 0) { + childrenList = VariableUtils.listFieldVariables(containerObj, varArgs.start, varArgs.count); + } else { + childrenList = VariableUtils.listFieldVariables(containerObj, showStaticVariables, useAsyncJDWP(context)); + } + } + } catch (AbsentInformationException e) { + throw AdapterUtils.createCompletionException( + String.format("Failed to get variables. Reason: %s", e.toString()), + ErrorCode.GET_VARIABLE_FAILURE, + e); + } + } + + // Find variable name duplicates + Set duplicateNames = getDuplicateNames(childrenList.stream().map(var -> var.name).collect(Collectors.toList())); + List duplicateVars = childrenList.stream() + .filter(var -> duplicateNames.contains(var.name)) + .collect(Collectors.toList()); + // Since JDI caches the fetched properties locally, in async mode we can warm up the JDI cache in advance. + if (useAsyncJDWP(context)) { + try { + AsyncJdwpUtils.await(warmUpJDICache(childrenList, duplicateVars)); + } catch (CompletionException | CancellationException e) { + response.body = new Responses.VariablesResponseBody(list); + return CompletableFuture.completedFuture(response); + } + } + + Map variableNameMap = new HashMap<>(); + if (!duplicateVars.isEmpty()) { + Map> duplicateVarGroups = duplicateVars.stream() + .collect(Collectors.groupingBy(var -> var.name, Collectors.toList())); + duplicateVarGroups.forEach((k, duplicateVariables) -> { + Set declarationTypeNames = new HashSet<>(); + boolean declarationTypeNameConflict = false; + // try use type formatter to resolve name conflict + for (Variable javaVariable : duplicateVariables) { + Type declarationType = javaVariable.getDeclaringType(); + if (declarationType != null) { + String declarationTypeName = variableFormatter.typeToString(declarationType, options); + String compositeName = String.format("%s (%s)", javaVariable.name, declarationTypeName); + if (!declarationTypeNames.add(compositeName)) { + declarationTypeNameConflict = true; + break; + } + variableNameMap.put(javaVariable, compositeName); + } + } + // If there are duplicate names on declaration types, use fully qualified name + if (declarationTypeNameConflict) { + for (Variable javaVariable : duplicateVariables) { + Type declarationType = javaVariable.getDeclaringType(); + if (declarationType != null) { + variableNameMap.put(javaVariable, String.format("%s (%s)", javaVariable.name, declarationType.name())); + } + } + } + }); + } + + for (Variable javaVariable : childrenList) { + Value value = javaVariable.value; + String name = javaVariable.name; + if (variableNameMap.containsKey(javaVariable)) { + name = variableNameMap.get(javaVariable); + } + int indexedVariables = -1; + Value sizeValue = null; + if (value instanceof ArrayReference) { + indexedVariables = ((ArrayReference) value).length(); + } else if (supportsLogicStructureView(context) && value instanceof ObjectReference && evaluationEngine != null) { + try { + JavaLogicalStructure structure = JavaLogicalStructureManager.getLogicalStructure((ObjectReference) value); + if (structure != null && structure.getSizeExpression() != null) { + sizeValue = structure.getSize((ObjectReference) value, containerNode.getThread(), evaluationEngine); + if (sizeValue != null && sizeValue instanceof IntegerValue) { + indexedVariables = ((IntegerValue) sizeValue).value(); + } + } + } catch (Exception e) { + logger.log(Level.INFO, "Failed to get the logical size of the variable", e); + } + } + + String evaluateName = null; + if (javaVariable.evaluateName == null || (containerEvaluateName == null && containerNode.getProxiedVariable() instanceof ObjectReference)) { + // Disable evaluate on the method return value. + evaluateName = null; + } else if (isUnboundedTypeContainer && !containerNode.isIndexedVariable()) { + // The type name returned by JDI is the binary name, which uses '$' as the separator of + // inner class e.g. Foo$Bar. But the evaluation expression only accepts using '.' as the class + // name separator. + String typeName = ((ObjectReference) containerNode.getProxiedVariable()).referenceType().name(); + // TODO: This replacement will possibly change the $ in the class name itself. + typeName = typeName.replaceAll("\\$", "."); + evaluateName = VariableUtils.getEvaluateName(javaVariable.evaluateName, "((" + typeName + ")" + containerEvaluateName + ")", false); + } else { + if (containerEvaluateName != null && containerEvaluateName.contains("%s")) { + evaluateName = String.format(containerEvaluateName, javaVariable.evaluateName); + } else { + evaluateName = VariableUtils.getEvaluateName(javaVariable.evaluateName, containerEvaluateName, containerNode.isIndexedVariable()); + } + } + + VariableProxy varProxy = null; + if (indexedVariables > 0 || (indexedVariables < 0 && value instanceof ObjectReference)) { + varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value, containerNode, evaluateName); + varProxy.setIndexedVariable(indexedVariables >= 0); + varProxy.setUnboundedType(javaVariable.isUnboundedType()); + } + + boolean hasErrors = false; + String valueString = null; + try { + valueString = variableFormatter.valueToString(value, options); + } catch (OutOfMemoryError e) { + hasErrors = true; + logger.log(Level.SEVERE, "Failed to convert the value of a large object to a string", e); + valueString = ""; + } catch (Exception e) { + hasErrors = true; + logger.log(Level.SEVERE, "Failed to resolve the variable value", e); + valueString = ""; + } + + String typeString = ""; + try { + typeString = variableFormatter.typeToString(value == null ? null : value.type(), options); + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to resolve the variable type", e); + typeString = ""; + } + + String detailsValue = null; + if (hasErrors) { + // If failed to resolve the variable value, skip the details info as well. + } else if (sizeValue != null) { + detailsValue = "size=" + variableFormatter.valueToString(sizeValue, options); + } else if (supportsToStringView(context)) { + if (VariableDetailUtils.isLazyLoadingSupported(value) && varProxy != null) { + varProxy.setLazyVariable(true); + } else { + try { + detailsValue = VariableDetailUtils.formatDetailsValue(value, containerNode.getThread(), variableFormatter, options, evaluationEngine); + } catch (OutOfMemoryError e) { + logger.log(Level.SEVERE, "Failed to compute the toString() value of a large object", e); + detailsValue = ""; + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to compute the toString() value", e); + detailsValue = ""; + } + } + } + + int referenceId = 0; + if (varProxy != null) { + referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy); + } + + Types.Variable typedVariables = new Types.Variable(name, valueString, typeString, referenceId, evaluateName); + typedVariables.indexedVariables = Math.max(indexedVariables, 0); + if (varProxy != null && varProxy.isLazyVariable()) { + typedVariables.presentationHint = new VariablePresentationHint(true); + } + + if (detailsValue != null) { + typedVariables.value = typedVariables.value + " " + detailsValue; + } + list.add(typedVariables); + } + + if (list.isEmpty() && containerNode.getProxiedVariable() instanceof ObjectReference) { + list.add(new Types.Variable("Class has no fields", "", null, 0, null)); + } + + response.body = new Responses.VariablesResponseBody(list); + + return CompletableFuture.completedFuture(response); + } + + private boolean supportsLogicStructureView(IDebugAdapterContext context) { + return (!useAsyncJDWP(context) || context.getJDWPLatency() <= USABLE_JDWP_LATENCY) + && DebugSettings.getCurrent().showLogicalStructure; + } + + private boolean supportsToStringView(IDebugAdapterContext context) { + return (!useAsyncJDWP(context) || context.getJDWPLatency() <= USABLE_JDWP_LATENCY) + && DebugSettings.getCurrent().showToString; + } + + private boolean useAsyncJDWP(IDebugAdapterContext context) { + return context.asyncJDWP(USABLE_JDWP_LATENCY); + } + + private Types.Variable resolveLazyVariable(IDebugAdapterContext context, VariableProxy containerNode, IVariableFormatter variableFormatter, + Map options, IEvaluationProvider evaluationEngine) { + VariableProxy valueReferenceProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), + containerNode.getProxiedVariable(), null /** container */, containerNode.getEvaluateName()); + valueReferenceProxy.setIndexedVariable(containerNode.isIndexedVariable()); + valueReferenceProxy.setUnboundedType(containerNode.isUnboundedType()); + int referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), valueReferenceProxy); + // this proxiedVariable is intermediate object, see https://github.com/microsoft/vscode/issues/135147#issuecomment-1076240074 + Object proxiedVariable = containerNode.getProxiedVariable(); + if (proxiedVariable instanceof ObjectReference) { + ObjectReference variable = (ObjectReference) proxiedVariable; + String valueString = variableFormatter.valueToString(variable, options); + String detailString = VariableDetailUtils.formatDetailsValue(variable, containerNode.getThread(), variableFormatter, options, + evaluationEngine); + return new Types.Variable("", valueString + " " + detailString, "", referenceId, containerNode.getEvaluateName()); + } + return null; + } + + private Set getDuplicateNames(Collection list) { + Set result = new HashSet<>(); + Set set = new HashSet<>(); + + for (String item : list) { + if (!set.contains(item)) { + set.add(item); + } else { + result.add(item); + } + } + return result; + } + + private List getVariablesOfFrameAsync(StackFrame frame, boolean showStaticVariables) { + CompletableFuture> localVariables = VariableUtils.listLocalVariablesAsync(frame); + CompletableFuture thisVariable = VariableUtils.getThisVariableAsync(frame); + CompletableFuture>[] staticVariables = new CompletableFuture[1]; + if (showStaticVariables && frame.location().method().isStatic()) { + staticVariables[0] = VariableUtils.listStaticVariablesAsync(frame); + } + + CompletableFuture futures = staticVariables[0] == null ? CompletableFuture.allOf(localVariables, thisVariable) + : CompletableFuture.allOf(localVariables, thisVariable, staticVariables[0]); + + AsyncJdwpUtils.await(futures); + + List result = new ArrayList<>(); + result.addAll(localVariables.join()); + Variable thisVar = thisVariable.join(); + if (thisVar != null) { + result.add(thisVar); + } + + if (staticVariables[0] != null) { + result.addAll(staticVariables[0].join()); + } + + return result; + } + + private CompletableFuture warmUpJDICache(List variables, List duplicatedVars) { + List> futures = new ArrayList<>(); + if (duplicatedVars != null && !duplicatedVars.isEmpty()) { + Set declaringTypes = new HashSet<>(); + duplicatedVars.forEach((var) -> { + Type declarationType = var.getDeclaringType(); + if (declarationType != null) { + declaringTypes.add(declarationType); + } + }); + + for (Type type : declaringTypes) { + if (type instanceof ReferenceType) { + // JDWP Command: RT_SIGNATURE + futures.add(AsyncJdwpUtils.runAsync(() -> type.signature())); + } + } + } + + for (Variable javaVariable : variables) { + Value value = javaVariable.value; + if (value instanceof ArrayReference) { + // JDWP Command: AR_LENGTH + futures.add(AsyncJdwpUtils.runAsync(() -> ((ArrayReference) value).length())); + } else if (value instanceof StringReference) { + // JDWP Command: SR_VALUE + futures.add(AsyncJdwpUtils.runAsync(() -> { + String strValue = ((StringReference) value).value(); + javaVariable.value = new StringReferenceProxy((StringReference) value, strValue); + })); + } + + if (value instanceof ObjectReference) { + // JDWP Command: OR_REFERENCE_TYPE, RT_SIGNATURE + futures.add(AsyncJdwpUtils.runAsync(() -> { + value.type().signature(); + })); + } + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/IVariableFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/IVariableFormatter.java new file mode 100755 index 0000000..a618f99 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/IVariableFormatter.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import java.util.Map; + +import com.microsoft.java.debug.core.adapter.formatter.ITypeFormatter; +import com.microsoft.java.debug.core.adapter.formatter.IValueFormatter; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public interface IVariableFormatter { + /** + * Register a type formatter. Be careful about the priority of formatters, the formatter with the largest + * priority which accepts the type will be used. + * + * @param typeFormatter the type formatter + * @param priority the priority for this formatter + */ + void registerTypeFormatter(ITypeFormatter typeFormatter, int priority); + + /** + * Register a value formatter. Be careful about the priority of formatters, the formatter with the largest + * priority which accepts the type will be used. + * + * @param formatter the value formatter + * @param priority the priority for this formatter + */ + void registerValueFormatter(IValueFormatter formatter, int priority); + + /** + * Get the default options for all formatters registered. + * @return The default options. + */ + Map getDefaultOptions(); + + /** + * Get display text of the value. + * + * @param value the value. + * @param options additional information about expected format + * @return the display text of the value + */ + String valueToString(Value value, Map options); + + /** + * Get the JDI value of a String. + * + * @param stringValue the text of the value need to be converted. + * @param options additional information about expected format + * @return the jdi value + */ + Value stringToValue(String stringValue, Type type, Map options); + + /** + * Get display name of type. + * + * @param type the JDI type + * @param options additional information about expected format + * @return display name of type of the value. + */ + String typeToString(Type type, Map options); +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructure.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructure.java new file mode 100755 index 0000000..0d065cf --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructure.java @@ -0,0 +1,235 @@ +/******************************************************************************* + * Copyright (c) 2019-2020 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; + +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.sun.jdi.ClassType; +import com.sun.jdi.Field; +import com.sun.jdi.InterfaceType; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public class JavaLogicalStructure { + // The binary type name. For inner type, the binary name uses '$' as the separator, e.g. java.util.Map$Entry. + private final String type; + // The fully qualified name, which uses '.' as the separator, e.g. java.util.Map.Entry. + private final String fullyQualifiedName; + private final LogicalStructureExpression valueExpression; + private final LogicalStructureExpression sizeExpression; + private final LogicalVariable[] variables; + // Indicates whether the specified type is an interface. + private final boolean isInterface; + + /** + * Constructor. + */ + public JavaLogicalStructure(String type, LogicalStructureExpression valueExpression, LogicalStructureExpression sizeExpression, + LogicalVariable[] variables) { + this(type, type, valueExpression, sizeExpression, variables); + } + + /** + * Constructor. + */ + public JavaLogicalStructure(String type, String fullyQualifiedName, LogicalStructureExpression valueExpression, LogicalStructureExpression sizeExpression, + LogicalVariable[] variables) { + this(type, type, true, valueExpression, sizeExpression, variables); + } + + public JavaLogicalStructure(String type, String fullyQualifiedName, boolean isInterface, LogicalStructureExpression valueExpression, + LogicalStructureExpression sizeExpression, LogicalVariable[] variables) { + this.valueExpression = valueExpression; + this.type = type; + this.fullyQualifiedName = fullyQualifiedName; + this.isInterface = isInterface; + this.sizeExpression = sizeExpression; + this.variables = variables; + } + + public String getType() { + return type; + } + + public String getFullyQualifiedName() { + return fullyQualifiedName; + } + + public LogicalStructureExpression getValueExpression() { + return valueExpression; + } + + public LogicalStructureExpression getSizeExpression() { + return sizeExpression; + } + + public LogicalVariable[] getVariables() { + return variables; + } + + /** + * Returns whether to support the logical structure view for the given object instance. + */ + public boolean providesLogicalStructure(ObjectReference obj) { + Type variableType = obj.type(); + if (!(variableType instanceof ClassType)) { + return false; + } + + ClassType classType = (ClassType) variableType; + if (Objects.equals(type, classType.name())) { + return true; + } + + if (isInterface) { + List interfaceTypes = ((ClassType) variableType).allInterfaces(); + for (InterfaceType interfaceType : interfaceTypes) { + if (Objects.equals(type, interfaceType.name())) { + return true; + } + } + } else { + while (classType != null) { + if (Objects.equals(type, classType.name())) { + return true; + } + + classType = classType.superclass(); + } + } + + return false; + } + + /** + * Return the logical size of the specified thisObject. + */ + public Value getSize(ObjectReference thisObject, ThreadReference thread, IEvaluationProvider evaluationEngine) + throws CancellationException, InterruptedException, IllegalArgumentException, ExecutionException, UnsupportedOperationException { + if (sizeExpression == null) { + throw new UnsupportedOperationException("The object hasn't defined the logical size operation."); + } + + return getValue(thisObject, sizeExpression, thread, evaluationEngine); + } + + /** + * Return the logical value of the specified thisObject. + */ + public Value getValue(ObjectReference thisObject, ThreadReference thread, IEvaluationProvider evaluationEngine) + throws CancellationException, IllegalArgumentException, InterruptedException, ExecutionException { + return getValue(thisObject, valueExpression, thread, evaluationEngine); + } + + private static Value getValue(ObjectReference thisObject, LogicalStructureExpression expression, ThreadReference thread, + IEvaluationProvider evaluationEngine) throws CancellationException, IllegalArgumentException, InterruptedException, ExecutionException { + if (expression.type == LogicalStructureExpressionType.METHOD) { + if (expression.value == null || expression.value.length < 2) { + throw new IllegalArgumentException("The method expression should contain at least methodName and methodSignature!"); + } + return evaluationEngine.invokeMethod(thisObject, expression.value[0], expression.value[1], null, thread, false).get(); + } else if (expression.type == LogicalStructureExpressionType.FIELD) { + if (expression.value == null || expression.value.length < 1) { + throw new IllegalArgumentException("The field expression should contain the field name!"); + } + return getValueByField(thisObject, expression.value[0], thread); + } else { + if (expression.value == null || expression.value.length < 1) { + throw new IllegalArgumentException("The evaluation expression should contain a valid expression statement!"); + } + return evaluationEngine.evaluate(expression.value[0], thisObject, thread).get(); + } + } + + private static Value getValueByField(ObjectReference thisObject, String fieldName, ThreadReference thread) { + Field targetField = thisObject.referenceType().fieldByName(fieldName); + if (targetField == null) { + return null; + } + + return thisObject.getValue(targetField); + } + + public static class LogicalVariable { + private final String name; + private final LogicalStructureExpression valueExpression; + + public LogicalVariable(String name, LogicalStructureExpression valueExpression) { + this.name = name; + this.valueExpression = valueExpression; + } + + public String getName() { + return name; + } + + public Value getValue(ObjectReference thisObject, ThreadReference thread, IEvaluationProvider evaluationEngine) + throws CancellationException, IllegalArgumentException, InterruptedException, ExecutionException { + return JavaLogicalStructure.getValue(thisObject, valueExpression, thread, evaluationEngine); + } + + public String getEvaluateName() { + if (valueExpression == null || valueExpression.evaluateName == null) { + return name; + } + + return valueExpression.evaluateName; + } + + public boolean returnUnboundedType() { + return valueExpression != null && valueExpression.returnUnboundedType; + } + } + + public static class LogicalStructureExpression { + public LogicalStructureExpressionType type; + public String[] value; + public String evaluateName; + public boolean returnUnboundedType = false; + + /** + * Constructor. + */ + public LogicalStructureExpression(LogicalStructureExpressionType type, String[] value) { + this(type, value, null); + } + + /** + * Constructor. + */ + public LogicalStructureExpression(LogicalStructureExpressionType type, String[] value, String evaluateName) { + this.type = type; + this.value = value; + this.evaluateName = evaluateName; + } + + /** + * Constructor. + */ + public LogicalStructureExpression(LogicalStructureExpressionType type, String[] value, String evaluateName, boolean returnUnboundedType) { + this.type = type; + this.value = value; + this.evaluateName = evaluateName; + this.returnUnboundedType = returnUnboundedType; + } + } + + public static enum LogicalStructureExpressionType { + FIELD, METHOD, EVALUATION_SNIPPET + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructureManager.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructureManager.java new file mode 100755 index 0000000..2762173 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructureManager.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2019-2020 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; + +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure.LogicalStructureExpression; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure.LogicalStructureExpressionType; +import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructure.LogicalVariable; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Value; + +public class JavaLogicalStructureManager { + private static final List supportedLogicalStructures = Collections.synchronizedList(new ArrayList<>()); + + static { + supportedLogicalStructures.add(new JavaLogicalStructure("java.util.Map", + new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"entrySet", "()Ljava/util/Set;"}, "entrySet()"), + new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"size", "()I"}), + new LogicalVariable[0] + )); + supportedLogicalStructures.add(new JavaLogicalStructure("java.util.Map$Entry", "java.util.Map.Entry", null, null, + new LogicalVariable[] { + new LogicalVariable("key", + new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"getKey", "()Ljava/lang/Object;"}, "getKey()", true) + ), + new LogicalVariable("value", + new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, + new String[] {"getValue", "()Ljava/lang/Object;"}, "getValue()", true) + )} + )); + supportedLogicalStructures.add(new JavaLogicalStructure("java.util.List", + new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"toArray", "()[Ljava/lang/Object;"}, "get(%s)", true), + new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"size", "()I"}), + new LogicalVariable[0] + )); + supportedLogicalStructures.add(new JavaLogicalStructure("java.util.Collection", + new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"toArray", "()[Ljava/lang/Object;"}, "toArray()", true), + new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"size", "()I"}), + new LogicalVariable[0] + )); + } + + /** + * Return the provided logical structure handler for the given variable. + */ + public static JavaLogicalStructure getLogicalStructure(ObjectReference obj) { + for (JavaLogicalStructure structure : supportedLogicalStructures) { + if (structure.providesLogicalStructure(obj)) { + return structure; + } + } + + return null; + } + + /** + * Return true if the specified Object has defined the logical size. + */ + public static boolean isIndexedVariable(ObjectReference obj) { + JavaLogicalStructure structure = getLogicalStructure(obj); + return structure != null && structure.getSizeExpression() != null; + } + + /** + * Return the logical size if the specified Object has defined the logical size. + */ + public static Value getLogicalSize(ObjectReference thisObject, ThreadReference thread, IEvaluationProvider evaluationEngine) + throws CancellationException, InterruptedException, IllegalArgumentException, ExecutionException, UnsupportedOperationException { + JavaLogicalStructure structure = getLogicalStructure(thisObject); + if (structure == null) { + return null; + } + + return structure.getSize(thisObject, thread, evaluationEngine); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/StackFrameReference.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/StackFrameReference.java new file mode 100755 index 0000000..46726cd --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/StackFrameReference.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2017-2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import com.microsoft.java.debug.core.protocol.Types.Source; +import com.sun.jdi.ThreadReference; + +public class StackFrameReference { + private final int depth; + private final int hash; + private final ThreadReference thread; + private Source source; + + /** + * Create a wrapper of JDI stackframe to keep the immutable properties of a stackframe, IStackFrameManager will use + * these properties to construct a jdi stackframe. + * + * @param thread the jdi thread. + * @param depth + * the index of this stackframe inside all frames inside one stopped + * thread + */ + public StackFrameReference(ThreadReference thread, int depth) { + if (thread == null) { + throw new NullPointerException("'thread' should not be null for StackFrameReference"); + } + + if (depth < 0) { + throw new IllegalArgumentException("'depth' should not be zero or an positive integer."); + } + this.thread = thread; + this.depth = depth; + hash = Long.hashCode(thread.hashCode()) + depth; + } + + public int getDepth() { + return depth; + } + + public ThreadReference getThread() { + return thread; + } + + public Source getSource() { + return source; + } + + public void setSource(Source source) { + this.source = source; + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj.getClass() != this.getClass()) { + return false; + } + if (this == obj) { + return true; + } + StackFrameReference sf = (StackFrameReference) obj; + return thread.equals(sf.thread) && depth == sf.depth; + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/StringReferenceProxy.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/StringReferenceProxy.java new file mode 100755 index 0000000..82c4da5 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/StringReferenceProxy.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import java.util.List; +import java.util.Map; + +import com.sun.jdi.ClassNotLoadedException; +import com.sun.jdi.Field; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.InvalidTypeException; +import com.sun.jdi.InvocationException; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StringReference; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Type; +import com.sun.jdi.Value; +import com.sun.jdi.VirtualMachine; + +public class StringReferenceProxy implements StringReference { + private StringReference delegateStringRef; + private String value = null; + + public StringReferenceProxy(StringReference sr, String value) { + this.delegateStringRef = sr; + this.value = value; + } + + public String value() { + if (value != null) { + return value; + } + + return delegateStringRef.value(); + } + + public ReferenceType referenceType() { + return delegateStringRef.referenceType(); + } + + public VirtualMachine virtualMachine() { + return delegateStringRef.virtualMachine(); + } + + public String toString() { + return delegateStringRef.toString(); + } + + public Value getValue(Field sig) { + return delegateStringRef.getValue(sig); + } + + public Map getValues(List fields) { + return delegateStringRef.getValues(fields); + } + + public void setValue(Field field, Value value) throws InvalidTypeException, ClassNotLoadedException { + delegateStringRef.setValue(field, value); + } + + public Value invokeMethod(ThreadReference thread, Method method, List arguments, int options) + throws InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException, + InvocationException { + return delegateStringRef.invokeMethod(thread, method, arguments, options); + } + + public Type type() { + return delegateStringRef.type(); + } + + public void disableCollection() { + delegateStringRef.disableCollection(); + } + + public void enableCollection() { + delegateStringRef.enableCollection(); + } + + public boolean isCollected() { + return delegateStringRef.isCollected(); + } + + public long uniqueID() { + return delegateStringRef.uniqueID(); + } + + public List waitingThreads() throws IncompatibleThreadStateException { + return delegateStringRef.waitingThreads(); + } + + public ThreadReference owningThread() throws IncompatibleThreadStateException { + return delegateStringRef.owningThread(); + } + + public int entryCount() throws IncompatibleThreadStateException { + return delegateStringRef.entryCount(); + } + + public List referringObjects(long maxReferrers) { + return delegateStringRef.referringObjects(maxReferrers); + } + + public boolean equals(Object obj) { + return delegateStringRef.equals(obj); + } + + public int hashCode() { + return delegateStringRef.hashCode(); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/Variable.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/Variable.java new file mode 100755 index 0000000..559e62b --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/Variable.java @@ -0,0 +1,124 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Objects; + +import com.sun.jdi.Field; +import com.sun.jdi.LocalVariable; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +/** + * This class represents a variable on a stopped stack frame, it contains more informations + * about this variable: + *
    + *
  • + * The field if this variable is a field value. + *
  • + *
  • + * The local variable information if this variable is a local variable. + *
  • + *
  • + * The argument index if this variable is an argument variable. + *
  • + *
+ * The above informations are for further formatter to compose an more detailed + * name for name conflict situation. + */ +public class Variable { + /** + * The JDI value. + */ + public Value value; + + /** + * The name of this variable. + */ + public String name; + + /** + * The field information if this variable is a field value. + */ + public Field field; + + /** + * The local variable information if this variable is a local variable. + */ + public LocalVariable local; + + /** + * The argument index if this variable is an argument variable. + */ + public int argumentIndex; + + /** + * The variable evaluate name for the container context. Defaults to the variable name. + */ + public String evaluateName; + + /** + * Indicates whether this variable's type is determined at runtime. + */ + private boolean isUnboundedType = false; + + /** + * The constructor of JavaVariable. + * @param name the name of this variable. + * @param value the JDI value + */ + public Variable(String name, Value value) { + this(name, value, name); + } + + /** + * The constructor of JavaVariable. + * @param name the name of this variable. + * @param value the JDI value + * @param evaluateName the variable evaluate name for the container context if any + */ + public Variable(String name, Value value, String evaluateName) { + if (StringUtils.isBlank(name)) { + throw new IllegalArgumentException("Name is required for a java variable."); + } + this.name = name; + this.value = value; + this.argumentIndex = -1; + this.evaluateName = evaluateName; + } + + /** + * Get the declaring type of this variable if it is a field declared by some class. + * + * @return the declaring type of this variable. + */ + public Type getDeclaringType() { + if (this.field != null) { + return this.field.declaringType(); + } + return null; + } + + public void setUnboundedType(boolean isUnboundedType) { + this.isUnboundedType = isUnboundedType; + } + + public boolean isUnboundedType() { + if (isUnboundedType) { + return true; + } + + return field != null && Objects.equals(field.signature(), "Ljava/lang/Object;"); + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java new file mode 100755 index 0000000..c6d0d38 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2019 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.sun.jdi.ClassType; +import com.sun.jdi.InterfaceType; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public class VariableDetailUtils { + private static final String STRING_TYPE = "java.lang.String"; + private static final String TO_STRING_METHOD = "toString"; + private static final String TO_STRING_METHOD_SIGNATURE = "()Ljava/lang/String;"; + private static final String ENTRY_TYPE = "java.util.Map$Entry"; + private static final String GET_KEY_METHOD = "getKey"; + private static final String GET_KEY_METHOD_SIGNATURE = "()Ljava/lang/Object;"; + private static final String GET_VALUE_METHOD = "getValue"; + private static final String GET_VALUE_METHOD_SIGNATURE = "()Ljava/lang/Object;"; + private static final Set COLLECTION_TYPES = new HashSet( + Arrays.asList("java.util.Map", "java.util.Collection", "java.util.Map$Entry")); + + /** + * Returns the details information for the specified variable. + */ + public static String formatDetailsValue(Value value, ThreadReference thread, IVariableFormatter variableFormatter, Map options, + IEvaluationProvider evaluationEngine) { + if (isClassType(value, STRING_TYPE)) { + // No need to show additional details information. + return null; + } else { + return computeToStringValue(value, thread, variableFormatter, options, evaluationEngine, true); + } + } + + private static String computeToStringValue(Value value, ThreadReference thread, IVariableFormatter variableFormatter, + Map options, IEvaluationProvider evaluationEngine, boolean isFirstLevel) { + if (!(value instanceof ObjectReference) || evaluationEngine == null) { + return null; + } + + String inheritedType = findInheritedType(value, COLLECTION_TYPES); + if (inheritedType != null) { + if (Objects.equals(inheritedType, ENTRY_TYPE)) { + try { + Value keyObject = evaluationEngine.invokeMethod((ObjectReference) value, GET_KEY_METHOD, GET_KEY_METHOD_SIGNATURE, + null, thread, false).get(); + Value valueObject = evaluationEngine.invokeMethod((ObjectReference) value, GET_VALUE_METHOD, GET_VALUE_METHOD_SIGNATURE, + null, thread, false).get(); + String toStringValue = computeToStringValue(keyObject, thread, variableFormatter, options, evaluationEngine, false) + + ":" + + computeToStringValue(valueObject, thread, variableFormatter, options, evaluationEngine, false); + if (!isFirstLevel) { + toStringValue = "\"" + toStringValue + "\""; + } + + return toStringValue; + } catch (InterruptedException | ExecutionException e) { + // do nothing. + } + } else if (!isFirstLevel) { + return variableFormatter.valueToString(value, options); + } + } else if (containsToStringMethod((ObjectReference) value)) { + try { + Value toStringValue = evaluationEngine.invokeMethod((ObjectReference) value, TO_STRING_METHOD, TO_STRING_METHOD_SIGNATURE, + null, thread, false).get(); + return variableFormatter.valueToString(toStringValue, options); + } catch (InterruptedException | ExecutionException e) { + // do nothing. + } + } + + return null; + } + + private static boolean containsToStringMethod(ObjectReference obj) { + ReferenceType refType = obj.referenceType(); + if (refType instanceof ClassType) { + Method m = ((ClassType) refType).concreteMethodByName(TO_STRING_METHOD, TO_STRING_METHOD_SIGNATURE); + if (m != null) { + if (!Objects.equals("Ljava/lang/Object;", m.declaringType().signature())) { + return true; + } + } + + for (InterfaceType iface : ((ClassType) refType).allInterfaces()) { + List matches = iface.methodsByName(TO_STRING_METHOD, TO_STRING_METHOD_SIGNATURE); + for (Method ifaceMethod : matches) { + if (!ifaceMethod.isAbstract()) { + return true; + } + } + } + } + + return false; + } + + private static String findInheritedType(Value value, Set typeNames) { + if (!(value instanceof ObjectReference)) { + return null; + } + + Type variableType = ((ObjectReference) value).type(); + if (!(variableType instanceof ClassType)) { + return null; + } + + ClassType classType = (ClassType) variableType; + while (classType != null) { + if (typeNames.contains(classType.name())) { + return classType.name(); + } + + classType = classType.superclass(); + } + + List interfaceTypes = ((ClassType) variableType).allInterfaces(); + for (InterfaceType interfaceType : interfaceTypes) { + if (typeNames.contains(interfaceType.name())) { + return interfaceType.name(); + } + } + + return null; + } + + private static boolean isClassType(Value value, String typeName) { + if (!(value instanceof ObjectReference)) { + return false; + } + + return Objects.equals(((ObjectReference) value).type().name(), typeName); + } + + public static boolean isLazyLoadingSupported(Value value) { + if (isClassType(value, STRING_TYPE)) { + return false; + } + if (!(value instanceof ObjectReference)) { + return false; + } + String inheritedType = findInheritedType(value, COLLECTION_TYPES); + if (inheritedType == null && !containsToStringMethod((ObjectReference) value)) { + return false; + } + return true; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableFormatter.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableFormatter.java new file mode 100755 index 0000000..4c01cf3 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableFormatter.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.microsoft.java.debug.core.adapter.formatter.IFormatter; +import com.microsoft.java.debug.core.adapter.formatter.ITypeFormatter; +import com.microsoft.java.debug.core.adapter.formatter.IValueFormatter; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public class VariableFormatter implements IVariableFormatter { + private Map valueFormatterMap; + private Map typeFormatterMap; + + /** + * Creates a variable formatter. + */ + public VariableFormatter() { + valueFormatterMap = new HashMap<>(); + typeFormatterMap = new HashMap<>(); + } + + private static IFormatter getFormatter(Map formatterMap, Type type, + Map options) { + List formatterList = + formatterMap.keySet().stream().filter(t -> t.acceptType(type, options)) + .sorted((a, b) -> + -Integer.compare(formatterMap.get(a), formatterMap.get(b))).collect(Collectors.toList()); + if (formatterList.isEmpty()) { + throw new UnsupportedOperationException(String.format("There is no related formatter for type %s.", + type == null ? "null" : type.name())); + } + return formatterList.get(0); + } + + /** + * Get display name of type. + * + * @param type the JDI type + * @param options additional information about expected format + * @return display name of type of the value. + */ + @Override + public String typeToString(Type type, Map options) { + IFormatter formatter = getFormatter(this.typeFormatterMap, type, options); + return formatter.toString(type, options); + } + + /** + * Get the default options for all formatters registered. + * @return The default options. + */ + @Override + public Map getDefaultOptions() { + Map defaultOptions = new HashMap<>(); + int count1 = valueFormatterMap.keySet().stream().mapToInt( + formatter -> this.mergeDefaultOptions(formatter, defaultOptions)).sum(); + int count2 = typeFormatterMap.keySet().stream().mapToInt( + formatter -> this.mergeDefaultOptions(formatter, defaultOptions)).sum(); + if (count1 + count2 != defaultOptions.size()) { + throw new IllegalStateException("There is some configuration conflicts on type and value formatters."); + } + return defaultOptions; + } + + + /** + * Get display text of the value. + * + * @param value the value. + * @param options additional information about expected format + * @return the display text of the value + */ + @Override + public String valueToString(Value value, Map options) { + Type type = value == null ? null : value.type(); + IFormatter formatter = getFormatter(this.valueFormatterMap, type, options); + return formatter.toString(value, options); + } + + @Override + public Value stringToValue(String stringValue, Type type, Map options) { + IValueFormatter formatter = (IValueFormatter) getFormatter(this.valueFormatterMap, type, options); + return formatter.valueOf(stringValue, type, options); + } + + public void registerValueFormatter(IValueFormatter formatter, int priority) { + valueFormatterMap.put(formatter, priority); + } + + public void registerTypeFormatter(ITypeFormatter typeFormatter, int priority) { + typeFormatterMap.put(typeFormatter, priority); + } + + private int mergeDefaultOptions(IFormatter formatter, Map options) { + int count = 0; + for (Map.Entry entry : formatter.getDefaultOptions().entrySet()) { + options.put(entry.getKey(), entry.getValue()); + count++; + } + return count; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableFormatterFactory.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableFormatterFactory.java new file mode 100755 index 0000000..8c5c4cb --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableFormatterFactory.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import com.microsoft.java.debug.core.adapter.formatter.ArrayObjectFormatter; +import com.microsoft.java.debug.core.adapter.formatter.BooleanFormatter; +import com.microsoft.java.debug.core.adapter.formatter.CharacterFormatter; +import com.microsoft.java.debug.core.adapter.formatter.ClassObjectFormatter; +import com.microsoft.java.debug.core.adapter.formatter.NullObjectFormatter; +import com.microsoft.java.debug.core.adapter.formatter.NumericFormatter; +import com.microsoft.java.debug.core.adapter.formatter.ObjectFormatter; +import com.microsoft.java.debug.core.adapter.formatter.SimpleTypeFormatter; +import com.microsoft.java.debug.core.adapter.formatter.StringObjectFormatter; + +public final class VariableFormatterFactory { + /** + * Private constructor to prevent instance of VariableFormatterFactory. + */ + private VariableFormatterFactory() { + + } + + /** + * Create an IVariableFormatter instance with proper value and type formatters. + * @return an IVariableFormatter instance + */ + public static IVariableFormatter createVariableFormatter() { + VariableFormatter formatter = new VariableFormatter(); + formatter.registerTypeFormatter(new SimpleTypeFormatter(), 1); + formatter.registerValueFormatter(new BooleanFormatter(), 1); + formatter.registerValueFormatter(new CharacterFormatter(), 1); + formatter.registerValueFormatter(new NumericFormatter(), 1); + formatter.registerValueFormatter(new ObjectFormatter(formatter::typeToString), 1); + formatter.registerValueFormatter(new NullObjectFormatter(), 1); + + formatter.registerValueFormatter(new StringObjectFormatter(), 2); + formatter.registerValueFormatter(new ArrayObjectFormatter(formatter::typeToString), 2); + formatter.registerValueFormatter(new ClassObjectFormatter(formatter::typeToString), 2); + return formatter; + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableProxy.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableProxy.java new file mode 100755 index 0000000..5ebf937 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableProxy.java @@ -0,0 +1,123 @@ +/******************************************************************************* +* Copyright (c) 2017-2020 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import java.util.Objects; + +import com.sun.jdi.ThreadReference; + +public class VariableProxy { + private final ThreadReference thread; + private final String scopeName; + private Object variable; + private int hashCode; + // The variable evaluate expression which can be passed to 'EvaluateRequest' to fetch this variable. + private final String evaluateName; + private boolean isIndexedVariable; + private boolean isUnboundedType = false; + private boolean isLazyVariable = false; + + /** + * Create a variable reference. + * + * @param thread the jdi thread + * @param scopeName + * the scope name + * @param variable + * the variable object + * @param container + * the variable container, if any + * @param evaluateName + * the variable evaluate expression which can be passed to 'EvaluateRequest' to + * fetch this variable, if any + */ + public VariableProxy(ThreadReference thread, String scopeName, Object variable, VariableProxy container, String evaluateName) { + this.thread = thread; + this.scopeName = scopeName; + this.variable = variable; + this.evaluateName = evaluateName; + + hashCode = Objects.hash(scopeName, thread, variable, evaluateName); + } + + @Override + public String toString() { + return String.format("%s %s", String.valueOf(variable), scopeName); + } + + public ThreadReference getThread() { + return thread; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + VariableProxy other = (VariableProxy) obj; + return Objects.equals(scopeName, other.scopeName) && Objects.equals(getThreadId(), other.getThreadId()) + && Objects.equals(variable, other.variable) && Objects.equals(evaluateName, other.evaluateName) + && Objects.equals(isLazyVariable, other.isLazyVariable); + } + + public long getThreadId() { + return thread.uniqueID(); + } + + public String getScope() { + return scopeName; + } + + public Object getProxiedVariable() { + return variable; + } + + public String getEvaluateName() { + return evaluateName; + } + + public boolean isIndexedVariable() { + return isIndexedVariable; + } + + public void setIndexedVariable(boolean isIndexedVariable) { + this.isIndexedVariable = isIndexedVariable; + } + + public boolean isUnboundedType() { + return isUnboundedType; + } + + public void setUnboundedType(boolean isUnboundedType) { + this.isUnboundedType = isUnboundedType; + } + + public boolean isLazyVariable() { + return isLazyVariable; + } + + public void setLazyVariable(boolean isLazyVariable) { + this.isLazyVariable = isLazyVariable; + } + +} diff --git a/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableUtils.java b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableUtils.java new file mode 100755 index 0000000..1a8139f --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableUtils.java @@ -0,0 +1,542 @@ +/******************************************************************************* + * Copyright (c) 2017-2022 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import com.microsoft.java.debug.core.AsyncJdwpUtils; +import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugSettings; +import com.microsoft.java.debug.core.adapter.formatter.NumericFormatEnum; +import com.microsoft.java.debug.core.adapter.formatter.NumericFormatter; +import com.microsoft.java.debug.core.adapter.formatter.SimpleTypeFormatter; +import com.microsoft.java.debug.core.adapter.formatter.StringObjectFormatter; +import com.sun.jdi.AbsentInformationException; +import com.sun.jdi.ArrayReference; +import com.sun.jdi.ArrayType; +import com.sun.jdi.ClassType; +import com.sun.jdi.InterfaceType; +import com.sun.jdi.ClassNotLoadedException; +import com.sun.jdi.Field; +import com.sun.jdi.InternalException; +import com.sun.jdi.LocalVariable; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; +import com.sun.jdi.Type; +import com.sun.jdi.TypeComponent; +import com.sun.jdi.Value; + +public abstract class VariableUtils { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + + /** + * Test whether the value has referenced objects. + * + * @param value + * the value. + * @param includeStatic + * whether or not the static fields are visible. + * @return true if this value is reference objects. + */ + public static boolean hasChildren(Value value, boolean includeStatic) { + if (value == null || !(value instanceof ObjectReference)) { + return false; + } + ReferenceType type = ((ObjectReference) value).referenceType(); + if (type instanceof ArrayType) { + return ((ArrayReference) value).length() > 0; + } + return type.allFields().stream().anyMatch(t -> includeStatic || !t.isStatic()); + } + + /** + * Get the variables of the object. + * + * @param obj + * the object + * @return the variable list + * @throws AbsentInformationException + * when there is any error in retrieving information + */ + public static List listFieldVariables(ObjectReference obj, boolean includeStatic) throws AbsentInformationException { + return listFieldVariables(obj, includeStatic, false); + } + + public static List listFieldVariables(ObjectReference obj, boolean includeStatic, boolean async) throws AbsentInformationException { + List res = new ArrayList<>(); + ReferenceType type = obj.referenceType(); + if (type instanceof ArrayType) { + int arrayIndex = 0; + boolean isUnboundedArrayType = Objects.equals(type.signature(), "[Ljava/lang/Object;"); + for (Value elementValue : ((ArrayReference) obj).getValues()) { + Variable ele = new Variable(String.valueOf(arrayIndex++), elementValue); + ele.setUnboundedType(isUnboundedArrayType); + res.add(ele); + } + return res; + } + List fields = resolveAllFields(type, async).stream().filter(t -> includeStatic || !t.isStatic()) + .sorted((a, b) -> { + try { + boolean v1isStatic = a.isStatic(); + boolean v2isStatic = b.isStatic(); + if (v1isStatic && !v2isStatic) { + return -1; + } + if (!v1isStatic && v2isStatic) { + return 1; + } + return a.name().compareToIgnoreCase(b.name()); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Cannot sort fields: %s", e), e); + return -1; + } + }).collect(Collectors.toList()); + + bulkFetchValues(fields, DebugSettings.getCurrent().limitOfVariablesPerJdwpRequest, (currentPage -> { + Map fieldValues = obj.getValues(currentPage); + for (Field currentField : currentPage) { + Variable var = new Variable(currentField.name(), fieldValues.get(currentField)); + var.field = currentField; + res.add(var); + } + })); + + return res; + } + + /** + * Get the variables of the object with pagination. + * + * @param obj + * the object + * @param start + * the start of the pagination + * @param count + * the number of variables needed + * @return the variable list + * @throws AbsentInformationException + * when there is any error in retrieving information + */ + public static List listFieldVariables(ObjectReference obj, int start, int count) + throws AbsentInformationException { + List res = new ArrayList<>(); + Type type = obj.type(); + if (type instanceof ArrayType) { + int arrayIndex = start; + boolean isUnboundedArrayType = Objects.equals(type.signature(), "[Ljava/lang/Object;"); + for (Value elementValue : ((ArrayReference) obj).getValues(start, count)) { + Variable variable = new Variable(String.valueOf(arrayIndex++), elementValue); + variable.setUnboundedType(isUnboundedArrayType); + res.add(variable); + } + return res; + } + throw new UnsupportedOperationException("Only Array type is supported."); + } + + /** + * Get the local variables of an stack frame. + * + * @param stackFrame + * the stack frame + * @return local variable list + * @throws AbsentInformationException + * when there is any error in retrieving information + */ + public static List listLocalVariables(StackFrame stackFrame) throws AbsentInformationException { + List res = new ArrayList<>(); + if (stackFrame.location().method().isNative()) { + return res; + } + try { + List visibleVariables = stackFrame.visibleVariables(); + // When using the API StackFrame.getValues() to batch fetch the variable values, the JDI + // probably throws timeout exception if the variables to be passed at one time are large. + // So use paging to fetch the values in chunks. + bulkFetchValues(visibleVariables, DebugSettings.getCurrent().limitOfVariablesPerJdwpRequest, (currentPage -> { + Map values = stackFrame.getValues(currentPage); + for (LocalVariable localVariable : currentPage) { + Variable var = new Variable(localVariable.name(), values.get(localVariable)); + var.local = localVariable; + res.add(var); + } + })); + } catch (AbsentInformationException ex) { + // avoid listing variable on native methods + + try { + if (stackFrame.location().method().argumentTypes().size() == 0) { + return res; + } + } catch (ClassNotLoadedException ex2) { + // ignore since the method is hit. + } + // 1. in oracle implementations, when there is no debug information, the AbsentInformationException will be + // thrown, then we need to retrieve arguments from stackFrame#getArgumentValues. + // 2. in eclipse jdt implementations, when there is no debug information, stackFrame#visibleVariables will + // return some generated variables like arg0, arg1, and the stackFrame#getArgumentValues will return null + + // for both scenarios, we need to handle the possible null returned by stackFrame#getArgumentValues and + // we need to call stackFrame.getArgumentValues get the arguments if AbsentInformationException is thrown + int argId = 0; + try { + List arguments = stackFrame.getArgumentValues(); + if (arguments == null) { + return res; + } + for (Value argValue : arguments) { + Variable var = new Variable("arg" + argId, argValue); + var.argumentIndex = argId++; + res.add(var); + } + } catch (InternalException ex2) { + // From Oracle's forums: + // This could be a JPDA bug. Unexpected JDWP Error: 32 means that an 'opaque' frame was + // detected at the lower JPDA levels, + // typically a native frame. + if (ex2.errorCode() != 32) { + throw ex; + } + } + } + return res; + } + + public static CompletableFuture> listLocalVariablesAsync(StackFrame stackFrame) { + CompletableFuture> future = new CompletableFuture<>(); + if (stackFrame.location().method().isNative()) { + return CompletableFuture.completedFuture(new ArrayList<>()); + } + + AsyncJdwpUtils.supplyAsync(() -> { + try { + return stackFrame.visibleVariables(); + } catch (AbsentInformationException ex) { + throw new CompletionException(ex); + } + }).thenCompose((visibleVariables) -> { + // When using the API StackFrame.getValues() to batch fetch the variable values, the JDI + // probably throws timeout exception if the variables to be passed at one time are large. + // So use paging to fetch the values in chunks. + return bulkFetchValuesAsync(visibleVariables, DebugSettings.getCurrent().limitOfVariablesPerJdwpRequest, (currentPage) -> { + Map values = stackFrame.getValues(currentPage); + List result = new ArrayList<>(); + for (LocalVariable localVariable : currentPage) { + Variable var = new Variable(localVariable.name(), values.get(localVariable)); + var.local = localVariable; + result.add(var); + } + + return result; + }); + }).whenComplete((res, ex) -> { + if (ex instanceof CompletionException && ex.getCause() != null) { + ex = ex.getCause(); + } + + if (ex instanceof AbsentInformationException) { + // avoid listing variable on native methods + try { + if (stackFrame.location().method().argumentTypes().size() == 0) { + future.complete(new ArrayList<>()); + return; + } + } catch (ClassNotLoadedException ex2) { + // ignore since the method is hit. + } + // 1. in oracle implementations, when there is no debug information, the AbsentInformationException will be + // thrown, then we need to retrieve arguments from stackFrame#getArgumentValues. + // 2. in eclipse jdt implementations, when there is no debug information, stackFrame#visibleVariables will + // return some generated variables like arg0, arg1, and the stackFrame#getArgumentValues will return null + + // for both scenarios, we need to handle the possible null returned by stackFrame#getArgumentValues and + // we need to call stackFrame.getArgumentValues get the arguments if AbsentInformationException is thrown + int argId = 0; + try { + List arguments = stackFrame.getArgumentValues(); + if (arguments == null) { + future.complete(new ArrayList<>()); + return; + } + + List variables = new ArrayList<>(); + for (Value argValue : arguments) { + Variable var = new Variable("arg" + argId, argValue); + var.argumentIndex = argId++; + variables.add(var); + } + future.complete(variables); + } catch (InternalException ex2) { + // From Oracle's forums: + // This could be a JPDA bug. Unexpected JDWP Error: 32 means that an 'opaque' frame was + // detected at the lower JPDA levels, + // typically a native frame. + if (ex2.errorCode() != 32) { + throw ex2; + } + } + } else if (ex != null) { + future.complete(new ArrayList<>()); + } else { + future.complete(res.stream() + .flatMap(List::stream) + .collect(Collectors.toList())); + } + }); + + return future; + } + + /** + * Get the this variable of an stack frame. + * + * @param stackFrame + * the stack frame + * @return this variable + */ + public static Variable getThisVariable(StackFrame stackFrame) { + ObjectReference thisObject = stackFrame.thisObject(); + if (thisObject == null) { + return null; + } + return new Variable("this", thisObject); + } + + public static CompletableFuture getThisVariableAsync(StackFrame stackFrame) { + return AsyncJdwpUtils.supplyAsync(() -> { + ObjectReference thisObject = stackFrame.thisObject(); + if (thisObject == null) { + return null; + } + return new Variable("this", thisObject); + }); + } + + /** + * Get the static variable of an stack frame. + * + * @param stackFrame + * the stack frame + * @return the static variable of an stack frame. + */ + public static List listStaticVariables(StackFrame stackFrame) { + List res = new ArrayList<>(); + ReferenceType type = stackFrame.location().declaringType(); + List fields = type.allFields().stream().filter(TypeComponent::isStatic).collect(Collectors.toList()); + bulkFetchValues(fields, DebugSettings.getCurrent().limitOfVariablesPerJdwpRequest, (currentPage -> { + Map fieldValues = type.getValues(currentPage); + for (Field currentField : currentPage) { + Variable var = new Variable(currentField.name(), fieldValues.get(currentField)); + var.field = currentField; + res.add(var); + } + })); + + return res; + } + + public static CompletableFuture> listStaticVariablesAsync(StackFrame stackFrame) { + CompletableFuture> future = new CompletableFuture<>(); + ReferenceType type = stackFrame.location().declaringType(); + AsyncJdwpUtils.supplyAsync(() -> { + return type.allFields().stream().filter(TypeComponent::isStatic).collect(Collectors.toList()); + }).thenCompose((fields) -> { + return bulkFetchValuesAsync(fields, DebugSettings.getCurrent().limitOfVariablesPerJdwpRequest, (currentPage) -> { + List variables = new ArrayList<>(); + Map fieldValues = type.getValues(currentPage); + for (Field currentField : currentPage) { + Variable var = new Variable(currentField.name(), fieldValues.get(currentField)); + var.field = currentField; + variables.add(var); + } + + return variables; + }); + }).whenComplete((res, ex) -> { + if (ex instanceof CompletionException && ex.getCause() != null) { + ex = ex.getCause(); + } + + if (ex != null) { + future.complete(new ArrayList<>()); + } else { + future.complete(res.stream() + .flatMap(List::stream) + .collect(Collectors.toList())); + } + }); + + return future; + } + + /** + * Apply the display options for variable formatter, it is used in variable and evaluate requests, controls the display content in + * variable view/debug console. + * + * @param defaultOptions the initial options for adding options from user settings + * @param hexInArgument when request sent by vscode declare hex format explicitly, settings this parameter true to override value in DebugSettings class. + */ + public static void applyFormatterOptions(Map defaultOptions, boolean hexInArgument) { + Map options = defaultOptions; + boolean showFullyQualifiedNames = DebugSettings.getCurrent().showQualifiedNames; + if (hexInArgument || DebugSettings.getCurrent().showHex) { + options.put(NumericFormatter.NUMERIC_FORMAT_OPTION, NumericFormatEnum.HEX); + } + if (showFullyQualifiedNames) { + options.put(SimpleTypeFormatter.QUALIFIED_CLASS_NAME_OPTION, true); + } + + if (DebugSettings.getCurrent().maxStringLength > 0) { + options.put(StringObjectFormatter.MAX_STRING_LENGTH_OPTION, DebugSettings.getCurrent().maxStringLength); + } + + if (DebugSettings.getCurrent().numericPrecision > 0) { + options.put(NumericFormatter.NUMERIC_PRECISION_OPTION, DebugSettings.getCurrent().numericPrecision); + } + } + + /** + * Get the name for evaluation of variable. + * + * @param name the variable name, if any + * @param containerName the container name, if any + * @param isArrayElement is the variable an array element? + */ + public static String getEvaluateName(String name, String containerName, boolean isArrayElement) { + if (name == null) { + return null; + } + + if (isArrayElement) { + if (containerName == null) { + return null; + } + + return String.format("%s[%s]", containerName, name); + } + + if (containerName == null) { + return name; + } + + return String.format("%s.%s", containerName, name); + } + + private static void bulkFetchValues(List elements, int numberPerPage, Consumer> consumer) { + int size = elements.size(); + numberPerPage = numberPerPage < 1 ? 1 : numberPerPage; + int page = size / numberPerPage + Math.min(size % numberPerPage, 1); + for (int i = 0; i < page; i++) { + int pageStart = i * numberPerPage; + int pageEnd = Math.min(pageStart + numberPerPage, size); + List currentPage = elements.subList(pageStart, pageEnd); + consumer.accept(currentPage); + } + } + + private static CompletableFuture> bulkFetchValuesAsync(List elements, int numberPerPage, Function, R> function) { + int size = elements.size(); + numberPerPage = numberPerPage < 1 ? 1 : numberPerPage; + int page = size / numberPerPage + Math.min(size % numberPerPage, 1); + List> futures = new ArrayList<>(); + for (int i = 0; i < page; i++) { + int pageStart = i * numberPerPage; + int pageEnd = Math.min(pageStart + numberPerPage, size); + final List currentPage = elements.subList(pageStart, pageEnd); + futures.add(AsyncJdwpUtils.supplyAsync(() -> { + return function.apply(currentPage); + })); + } + + return AsyncJdwpUtils.all(futures); + } + + private static List resolveAllFields(ReferenceType type, boolean async) { + if (async) { + return resolveAllFieldsAsync(type); + } + + return type.allFields(); + } + + private static List resolveAllFieldsAsync(ReferenceType type) { + Set result = Collections.synchronizedSet(new HashSet<>()); + AsyncJdwpUtils.await(resolveAllFieldsAsync(type, result)); + List fields = new ArrayList<>(); + fields.addAll(result); + return fields; + } + + private static CompletableFuture resolveAllFieldsAsync(ReferenceType type, Set result) { + List> futures = new ArrayList<>(); + // JDWP Command: RT_FIELDS_WITH_GENERIC + futures.add( + AsyncJdwpUtils.runAsync(() -> result.addAll(type.fields())) + ); + + if (type instanceof ClassType) { + ClassType classType = (ClassType) type; + // JDWP Command: RT_INTERFACES + futures.add(AsyncJdwpUtils.supplyAsync(() -> classType.interfaces()) + .thenCompose((its) -> { + List> itFutures = new ArrayList<>(); + for (InterfaceType it : its) { + itFutures.add(resolveAllFieldsAsync(it, result)); + } + + return CompletableFuture.allOf(itFutures.toArray(new CompletableFuture[0])); + })); + + // JDWP Command: CT_SUPERCLASS + AsyncJdwpUtils.supplyAsync(() -> classType.superclass()) + .thenCompose((superclass) -> { + if (superclass != null) { + return resolveAllFieldsAsync(superclass, result); + } + return CompletableFuture.completedFuture(null); + }); + } else if (type instanceof InterfaceType) { + InterfaceType interfaceType = (InterfaceType) type; + // JDWP Command: RT_INTERFACES + futures.add(AsyncJdwpUtils.supplyAsync(() -> interfaceType.superinterfaces()) + .thenCompose((its) -> { + List> itFutures = new ArrayList<>(); + for (InterfaceType it : its) { + itFutures.add(resolveAllFieldsAsync(it, result)); + } + + return CompletableFuture.allOf(itFutures.toArray(new CompletableFuture[0])); + })); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + private VariableUtils() { + + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/protocol/AbstractProtocolServer.java b/src/main/java/com/microsoft/java/debug/core/protocol/AbstractProtocolServer.java new file mode 100755 index 0000000..61d57bd --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/protocol/AbstractProtocolServer.java @@ -0,0 +1,307 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.protocol; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.ErrorCode; +import com.microsoft.java.debug.core.protocol.Events.DebugEvent; + +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; + +public abstract class AbstractProtocolServer implements IProtocolServer { + private static final Logger logger = Logger.getLogger("java-debug"); + private static final int BUFFER_SIZE = 4096; + private static final String TWO_CRLF = "\r\n\r\n"; + private static final Pattern CONTENT_LENGTH_MATCHER = Pattern.compile("Content-Length: (\\d+)"); + private static final Charset PROTOCOL_ENCODING = StandardCharsets.UTF_8; // vscode protocol uses UTF-8 as encoding format. + + protected boolean terminateSession = false; + + private Reader reader; + private Writer writer; + + private ByteBuffer rawData; + private int contentLength = -1; + private AtomicInteger sequenceNumber = new AtomicInteger(1); + private boolean isValidDAPRequest = true; + + private PublishSubject responseSubject = PublishSubject.create(); + private PublishSubject requestSubject = PublishSubject.create(); + + /** + * Constructs a protocol server instance based on the given input stream and + * output stream. + * + * @param input + * the input stream + * @param output + * the output stream + */ + public AbstractProtocolServer(InputStream input, OutputStream output) { + this.reader = new BufferedReader(new InputStreamReader(input, PROTOCOL_ENCODING)); + this.writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output, PROTOCOL_ENCODING))); + this.contentLength = -1; + this.rawData = new ByteBuffer(); + + requestSubject.observeOn(Schedulers.newThread()).subscribe(request -> { + try { + this.dispatchRequest(request); + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Dispatch debug protocol error: %s", e.toString()), e); + } + }); + } + + /** + * A while-loop to parse input data and send output data constantly. + */ + public void run() { + char[] buffer = new char[BUFFER_SIZE]; + try { + while (!this.terminateSession) { + int read = this.reader.read(buffer, 0, BUFFER_SIZE); + if (read == -1) { + break; + } + + this.rawData.append(new String(buffer, 0, read).getBytes(PROTOCOL_ENCODING)); + this.processData(); + } + } catch (IOException e) { + logger.log(Level.SEVERE, String.format("Read data from io exception: %s", e.toString()), e); + } + + requestSubject.onComplete(); + } + + /** + * Sets terminateSession flag to true. And the dispatcher loop will be + * terminated after current dispatching operation finishes. + */ + public void stop() { + this.terminateSession = true; + } + + /** + * Send a request/response/event to the DA. + * + * @param message + * the message. + */ + private void sendMessage(Messages.ProtocolMessage message) { + message.seq = this.sequenceNumber.getAndIncrement(); + + String jsonMessage = JsonUtils.toJson(message); + byte[] jsonBytes = jsonMessage.getBytes(PROTOCOL_ENCODING); + + String header = String.format("Content-Length: %d%s", jsonBytes.length, TWO_CRLF); + byte[] headerBytes = header.getBytes(PROTOCOL_ENCODING); + + ByteBuffer data = new ByteBuffer(); + data.append(headerBytes); + data.append(jsonBytes); + + String utf8Data = data.getString(PROTOCOL_ENCODING); + + try { + if (message instanceof Messages.Request) { + logger.fine("\n[[REQUEST]]\n" + utf8Data); + } else if (message instanceof Messages.Event) { + logger.fine("\n[[EVENT]]\n" + utf8Data); + } else { + logger.fine("\n[[RESPONSE]]\n" + utf8Data); + } + this.writer.write(utf8Data); + this.writer.flush(); + } catch (IOException e) { + logger.log(Level.SEVERE, String.format("Write data to io exception: %s", e.toString()), e); + } + } + + @Override + public void sendEvent(DebugEvent event) { + sendMessage(new Messages.Event(event.type, event)); + } + + @Override + public void sendResponse(Messages.Response response) { + sendMessage(response); + } + + @Override + public CompletableFuture sendRequest(Messages.Request request) { + return sendRequest(request, 0); + } + + @Override + public CompletableFuture sendRequest(Messages.Request request, long timeout) { + CompletableFuture future = new CompletableFuture<>(); + Timer timer = new Timer(); + Disposable[] disposable = new Disposable[1]; + disposable[0] = responseSubject.filter(response -> response.request_seq == request.seq).take(1) + .observeOn(Schedulers.newThread()).subscribe((response) -> { + try { + timer.cancel(); + future.complete(response); + if (disposable[0] != null) { + disposable[0].dispose(); + } + } catch (Exception e) { + logger.log(Level.SEVERE, String.format("Handle response error: %s", e.toString()), e); + } + }); + sendMessage(request); + if (timeout > 0) { + try { + timer.schedule(new TimerTask() { + @Override + public void run() { + if (disposable[0] != null) { + disposable[0].dispose(); + } + future.completeExceptionally(new TimeoutException("timeout")); + } + }, timeout); + } catch (IllegalStateException ex) { + // if timer or task has been cancelled, do nothing. + } + } + return future; + } + + private void processData() { + while (true) { + /** + * In vscode debug protocol, the content length represents the + * message's byte length with utf8 format. + */ + if (this.contentLength >= 0) { + if (this.rawData.length() >= this.contentLength) { + byte[] buf = this.rawData.removeFirst(this.contentLength); + this.contentLength = -1; + String messageData = new String(buf, PROTOCOL_ENCODING); + try { + Messages.ProtocolMessage message = JsonUtils.fromJson(messageData, Messages.ProtocolMessage.class); + + logger.fine(String.format("\n[%s]\n%s", message.type, messageData)); + + if (message.type.equals("request")) { + Messages.Request request = JsonUtils.fromJson(messageData, Messages.Request.class); + if (this.isValidDAPRequest) { + requestSubject.onNext(request); + } else { + Messages.Response response = new Messages.Response(request.seq, request.command); + sendResponse(AdapterUtils.setErrorResponse(response, + ErrorCode.INVALID_DAP_HEADER, + String.format("'%s' request is rejected due to not being a valid DAP message.", request.command))); + } + } else if (message.type.equals("response")) { + Messages.Response response = JsonUtils.fromJson(messageData, Messages.Response.class); + responseSubject.onNext(response); + } + } catch (Exception ex) { + logger.log(Level.SEVERE, String.format("Error parsing message: %s", ex.toString()), ex); + } + + continue; + } + } + + String rawMessage = this.rawData.getString(PROTOCOL_ENCODING); + int idx = rawMessage.indexOf(TWO_CRLF); + if (idx != -1) { + Matcher matcher = CONTENT_LENGTH_MATCHER.matcher(rawMessage); + if (matcher.find()) { + final String contentLengthText = matcher.group(1); + this.contentLength = Integer.parseInt(contentLengthText); + final String headerMessage = rawMessage.substring(0, idx + TWO_CRLF.length()); + final int headerByteLength = headerMessage.getBytes(PROTOCOL_ENCODING).length; + this.rawData.removeFirst(headerByteLength); // Remove the header from the raw message. + + int expectedHeaderLength = 16 /*"Content-Length: ".length()*/ + contentLengthText.length(); + int actualHeaderLength = idx; + if (expectedHeaderLength != actualHeaderLength) { + this.isValidDAPRequest = false; + logger.log(Level.SEVERE, String.format("Illegal DAP request is detected: %s", headerMessage)); + } else { + this.isValidDAPRequest = true; + } + continue; + } + } + + break; + } + } + + protected abstract void dispatchRequest(Messages.Request request); + + class ByteBuffer { + private byte[] buffer; + + public ByteBuffer() { + this.buffer = new byte[0]; + } + + public int length() { + return this.buffer.length; + } + + public String getString(Charset cs) { + return new String(this.buffer, cs); + } + + public void append(byte[] b) { + append(b, b.length); + } + + public void append(byte[] b, int length) { + byte[] newBuffer = new byte[this.buffer.length + length]; + System.arraycopy(buffer, 0, newBuffer, 0, this.buffer.length); + System.arraycopy(b, 0, newBuffer, this.buffer.length, length); + this.buffer = newBuffer; + } + + public byte[] removeFirst(int n) { + byte[] b = new byte[n]; + System.arraycopy(this.buffer, 0, b, 0, n); + byte[] newBuffer = new byte[this.buffer.length - n]; + System.arraycopy(this.buffer, n, newBuffer, 0, this.buffer.length - n); + this.buffer = newBuffer; + return b; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/protocol/Events.java b/src/main/java/com/microsoft/java/debug/core/protocol/Events.java new file mode 100755 index 0000000..681ec54 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/protocol/Events.java @@ -0,0 +1,308 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.protocol; + +import com.google.gson.annotations.SerializedName; +import com.microsoft.java.debug.core.protocol.Types.Source; + +/** + * The event types defined by VSCode Debug Protocol. + */ +public class Events { + public static class DebugEvent { + public String type; + + public DebugEvent(String type) { + this.type = type; + } + } + + public static class InitializedEvent extends DebugEvent { + public InitializedEvent() { + super("initialized"); + } + } + + public static class StoppedEvent extends DebugEvent { + public long threadId; + public String reason; + public String description; + public String text; + public boolean allThreadsStopped; + + /** + * Constructor. + */ + public StoppedEvent(String reason, long threadId) { + super("stopped"); + this.reason = reason; + this.threadId = threadId; + allThreadsStopped = false; + } + + /** + * Constructor. + */ + public StoppedEvent(String reason, long threadId, boolean allThreadsStopped) { + this(reason, threadId); + this.allThreadsStopped = allThreadsStopped; + } + + /** + * Constructor. + */ + public StoppedEvent(String reason, long threadId, boolean allThreadsStopped, String description, String text) { + this(reason, threadId, allThreadsStopped); + this.description = description; + this.text = text; + } + } + + public static class ContinuedEvent extends DebugEvent { + public long threadId; + public boolean allThreadsContinued; + + /** + * Constructor. + */ + public ContinuedEvent(long threadId) { + super("continued"); + this.threadId = threadId; + } + + /** + * Constructor. + */ + public ContinuedEvent(long threadId, boolean allThreadsContinued) { + this(threadId); + this.allThreadsContinued = allThreadsContinued; + } + + /** + * Constructor. + */ + public ContinuedEvent(boolean allThreadsContinued) { + super("continued"); + this.allThreadsContinued = allThreadsContinued; + } + } + + public static class ExitedEvent extends DebugEvent { + public int exitCode; + + public ExitedEvent(int code) { + super("exited"); + this.exitCode = code; + } + } + + public static class TerminatedEvent extends DebugEvent { + public boolean restart; + + public TerminatedEvent() { + super("terminated"); + } + + public TerminatedEvent(boolean restart) { + this(); + this.restart = restart; + } + } + + public static class ThreadEvent extends DebugEvent { + public String reason; + public long threadId; + + /** + * Constructor. + */ + public ThreadEvent(String reason, long threadId) { + super("thread"); + this.reason = reason; + this.threadId = threadId; + } + } + + public static class OutputEvent extends DebugEvent { + public enum Category { + console, stdout, stderr, telemetry + } + + public Category category; + public String output; + public int variablesReference; + public Source source; + public int line; + public int column; + public Object data; + + /** + * Constructor. + */ + public OutputEvent(Category category, String output) { + super("output"); + this.category = category; + this.output = output; + } + + /** + * Constructor. + */ + public OutputEvent(Category category, String output, Source source, int line) { + super("output"); + this.category = category; + this.output = output; + this.source = source; + this.line = line; + } + + public static OutputEvent createConsoleOutput(String output) { + return new OutputEvent(Category.console, output); + } + + public static OutputEvent createStdoutOutput(String output) { + return new OutputEvent(Category.stdout, output); + } + + /** + * Construct an stdout output event with source info. + */ + public static OutputEvent createStdoutOutputWithSource(String output, Source source, int line) { + return new OutputEvent(Category.stdout, output, source, line); + } + + public static OutputEvent createStderrOutput(String output) { + return new OutputEvent(Category.stderr, output); + } + + /** + * Construct an stderr output event with source info. + */ + public static OutputEvent createStderrOutputWithSource(String output, Source source, int line) { + return new OutputEvent(Category.stderr, output, source, line); + } + + public static OutputEvent createTelemetryOutput(String output) { + return new OutputEvent(Category.telemetry, output); + } + } + + public static class BreakpointEvent extends DebugEvent { + public String reason; + public Types.Breakpoint breakpoint; + + /** + * Constructor. + */ + public BreakpointEvent(String reason, Types.Breakpoint breakpoint) { + super("breakpoint"); + this.reason = reason; + this.breakpoint = breakpoint; + } + } + + public static class HotCodeReplaceEvent extends DebugEvent { + public enum ChangeType { + ERROR, WARNING, STARTING, END, BUILD_COMPLETE + } + + public ChangeType changeType; + public String message; + + /** + * Constructor. + */ + public HotCodeReplaceEvent(ChangeType changeType, String message) { + super("hotcodereplace"); + this.changeType = changeType; + this.message = message; + } + } + + public static class UserNotificationEvent extends DebugEvent { + public enum NotificationType { + ERROR, WARNING, INFORMATION + } + + public NotificationType notificationType; + public String message; + + /** + * Constructor. + */ + public UserNotificationEvent(NotificationType notifyType, String message) { + super("usernotification"); + this.notificationType = notifyType; + this.message = message; + } + } + + public static enum InvalidatedAreas { + @SerializedName("all") + ALL, + @SerializedName("stacks") + STACKS, + @SerializedName("threads") + THREADS, + @SerializedName("variables") + VARIABLES; + } + + public static class InvalidatedEvent extends DebugEvent { + public InvalidatedAreas[] areas; + public long threadId; + public int frameId; + + public InvalidatedEvent() { + super("invalidated"); + } + + public InvalidatedEvent(InvalidatedAreas area) { + super("invalidated"); + this.areas = new InvalidatedAreas[]{area}; + } + + public InvalidatedEvent(InvalidatedAreas area, long threadId) { + super("invalidated"); + this.areas = new InvalidatedAreas[]{area}; + this.threadId = threadId; + } + + public InvalidatedEvent(InvalidatedAreas area, int frameId) { + super("invalidated"); + this.areas = new InvalidatedAreas[]{area}; + this.frameId = frameId; + } + } + + public static class ProcessIdNotification extends DebugEvent { + /** + * The process ID. + */ + public long processId = -1; + /** + * The process ID of the terminal shell if the process is running in a terminal shell. + */ + public long shellProcessId = -1; + + public ProcessIdNotification(long processId) { + super("processid"); + this.processId = processId; + } + + public ProcessIdNotification(long processId, long shellProcessId) { + super("processid"); + this.processId = processId; + this.shellProcessId = shellProcessId; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/protocol/IProtocolServer.java b/src/main/java/com/microsoft/java/debug/core/protocol/IProtocolServer.java new file mode 100755 index 0000000..4041824 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/protocol/IProtocolServer.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.protocol; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.java.debug.core.protocol.Events.DebugEvent; +import com.microsoft.java.debug.core.protocol.Messages.Request; +import com.microsoft.java.debug.core.protocol.Messages.Response; + +public interface IProtocolServer { + /** + * Send a request to the DA. + * + * @param request + * the request message. + * @return a CompletableFuture. + */ + CompletableFuture sendRequest(Request request); + + /** + * Send a request to the DA. The future will complete exceptionally if no response is received at the give time. + * + * @param request + * the request message. + * @param timeout + * the maximum time (in millis) to wait. + * @return a CompletableFuture. + */ + CompletableFuture sendRequest(Request request, long timeout); + + /** + * Send an event to the DA. + * @param event + * the event message. + */ + void sendEvent(DebugEvent event); + + /** + * Send a response to the DA. + * @param response + * the response message. + */ + void sendResponse(Response response); +} diff --git a/src/main/java/com/microsoft/java/debug/core/protocol/JsonUtils.java b/src/main/java/com/microsoft/java/debug/core/protocol/JsonUtils.java new file mode 100755 index 0000000..a2e925f --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/protocol/JsonUtils.java @@ -0,0 +1,141 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.protocol; + +import java.lang.reflect.Type; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; + +public class JsonUtils { + private static final Gson GSON = new Gson(); + + public static T fromJson(String json, Class classOfT) throws JsonSyntaxException { + return GSON.fromJson(json, classOfT); + } + + public static T fromJson(String json, Type typeOfT) throws JsonSyntaxException { + return GSON.fromJson(json, typeOfT); + } + + public static T fromJson(JsonElement json, Class classOfT) throws JsonSyntaxException { + return GSON.fromJson(json, classOfT); + } + + public T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException { + return GSON.fromJson(json, typeOfT); + } + + public static String toJson(Object src) { + return GSON.toJson(src); + } + + public static String toJson(Object src, Type typeOfSrc) { + return GSON.toJson(src, typeOfSrc); + } + + public static JsonElement toJsonTree(Object src, Type typeOfSrc) { + return GSON.toJsonTree(src, typeOfSrc); + } + + /** + * Get the integer value for the specified property from the json Object. + * @param args + * the json object + * @param property + * the key + * @param defaultValue + * if key doesn't exist in the json object, then return the default value + * @return the value as an integer number + */ + public static int getInt(JsonObject args, String property, int defaultValue) { + try { + return args.getAsInt(); + } catch (Exception e) { + // ignore and return default value; + } + return defaultValue; + } + + /** + * Get the string value for the specified property from the json Object. + * @param args + * the json object + * @param property + * the key + * @param defaultValue + * if key doesn't exist in the json object, then return the default value + * @return the value as a string + */ + public static String getString(JsonObject args, String property, String defaultValue) { + String value = null; + try { + JsonElement obj = args.get(property); + value = obj.getAsString(); + } catch (Exception e) { + // ignore and return default value; + } + if (value == null) { + return defaultValue; + } + value = value.trim(); + if (value.length() == 0) { + return defaultValue; + } + return value; + } + + /** + * Get the boolean value for the specified property from the json Object. + * @param args + * the json object + * @param property + * the key + * @param defaultValue + * if key doesn't exist in the json object, then return the default value + * @return the value as boolean + */ + public static boolean getBoolean(JsonObject args, String property, boolean defaultValue) { + try { + JsonElement obj = args.get(property); + return obj.getAsBoolean(); + } catch (Exception e) { + // ignore and return default value; + } + return defaultValue; + } + + /** + * Extracts a list of property values from a json array object. + * @param args + * the json array element + * @param property + * the key + * @return an string array + */ + public static String[] getStringArray(JsonElement args, String property) { + if (args instanceof JsonArray) { + JsonArray array = (JsonArray) args; + int size = array.size(); + String[] result = new String[size]; + for (int i = 0; i < size; i++) { + result[i] = array.get(i).getAsString(); + } + return result; + } else { + return new String[0]; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/protocol/Messages.java b/src/main/java/com/microsoft/java/debug/core/protocol/Messages.java new file mode 100755 index 0000000..467a4bf --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/protocol/Messages.java @@ -0,0 +1,147 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.protocol; + +import com.google.gson.JsonObject; + +/** + * The response types defined by VSCode Debug Protocol. + */ +public class Messages { + + public static class ProtocolMessage { + public int seq; + public String type; + + public ProtocolMessage(String type) { + this.type = type; + } + } + + public static class Request extends ProtocolMessage { + public String command; + public JsonObject arguments; + + /** + * Constructor. + */ + public Request(int id, String cmd, JsonObject arg) { + super("request"); + this.seq = id; + this.command = cmd; + this.arguments = arg; + } + + /** + * Constructor. + */ + public Request(String cmd, JsonObject arg) { + super("request"); + this.command = cmd; + this.arguments = arg; + } + } + + public static class Response extends ProtocolMessage { + public boolean success; + public String message; + public int request_seq; + public String command; + public Object body; + + public Response() { + super("response"); + } + + /** + * Constructor. + */ + public Response(String message) { + super("response"); + this.success = false; + this.message = message; + } + + /** + * Constructor. + */ + public Response(boolean success, String message) { + super("response"); + this.success = success; + this.message = message; + } + + /** + * Constructor. + */ + public Response(Response response) { + super("response"); + this.seq = response.seq; + this.success = response.success; + this.message = response.message; + this.request_seq = response.request_seq; + this.command = response.command; + this.body = response.body; + } + + /** + * Constructor. + */ + public Response(int requestSeq, String command) { + super("response"); + this.request_seq = requestSeq; + this.command = command; + } + + public Response(int requestSeq, String command, boolean success) { + this(requestSeq, command); + this.success = success; + } + + /** + * Constructor. + */ + public Response(int requestSeq, String command, boolean success, String message) { + this(requestSeq, command); + this.success = success; + this.message = message; + } + } + + public static class Event extends ProtocolMessage { + public String event; + public Object body; + + public Event() { + super("event"); + } + + /** + * Constructor. + */ + public Event(Event m) { + super("event"); + this.seq = m.seq; + this.event = m.event; + this.body = m.body; + } + + /** + * Constructor. + */ + public Event(String type, Object body) { + super("event"); + this.event = type; + this.body = body; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java b/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java new file mode 100755 index 0000000..b202718 --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java @@ -0,0 +1,499 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.protocol; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +import com.google.gson.annotations.SerializedName; +import com.microsoft.java.debug.core.protocol.Types.DataBreakpoint; +import com.microsoft.java.debug.core.protocol.Types.Source; + +/** + * The request arguments types defined by VSCode Debug Protocol. + */ +public class Requests { + + public static class ValueFormat { + public boolean hex; + } + + public static class Arguments { + + } + + public static class InitializeArguments extends Arguments { + public String clientID; + public String adapterID; + public String pathFormat; + public boolean linesStartAt1; + public boolean columnsStartAt1; + public boolean supportsVariableType; + public boolean supportsVariablePaging; + public boolean supportsRunInTerminalRequest; + } + + public static class ClassFilters { + /** + * Restricts the events generated by the request to those whose location is + * in a class whose name matches this restricted regular expression. Regular + * expressions are limited to exact matches and patterns that begin with '*' + * or end with '*'; for example, "*.Foo" or "java.*". + * + * This property corresponds to the ClassFilter (include filter). Multiple + * filters are applied with CUT-OFF AND. Only events that satisfied all + * filters are placed in the event queue, that means several include filters + * are handled as "A and B and C", not "A or B or C". + */ + public String[] allowClasses = new String[0]; + + /** + * Restricts the events generated by the request to those whose location is + * in a class whose name does not match this restricted regular expression, e.g. + * "java.*" or "*.Foo". + * + * This property corrsponds to the ClassExclusionFilter (exclude filter). + */ + public String[] skipClasses = new String[0]; + } + + public static class StepFilters extends ClassFilters { + /** + * Deprecated - please use {@link ClassFilters#skipClasses } instead. + */ + @Deprecated + public String[] classNameFilters; + public boolean skipSynthetics; + public boolean skipStaticInitializers; + public boolean skipConstructors; + } + + public static class LaunchBaseArguments extends Arguments { + public String type; + public String name; + public String request; + public String projectName; + public String[] sourcePaths = new String[0]; + public StepFilters stepFilters; + } + + public static enum CONSOLE { + internalConsole, + integratedTerminal, + externalTerminal; + } + + public static enum ShortenApproach { + @SerializedName("none") + NONE, + @SerializedName("jarmanifest") + JARMANIFEST, + @SerializedName("argfile") + ARGFILE; + } + + public static class LaunchArguments extends LaunchBaseArguments { + public String mainClass; + public String args = ""; + public String vmArgs = ""; + public String encoding = ""; + public String[] classPaths = new String[0]; + public String[] modulePaths = new String[0]; + public String cwd; + public Map env; + public boolean stopOnEntry; + public boolean noDebug = false; + public CONSOLE console = CONSOLE.integratedTerminal; + public ShortenApproach shortenCommandLine = ShortenApproach.NONE; + public String launcherScript; + public String javaExec; + } + + public static class AttachArguments extends LaunchBaseArguments { + public String hostName; + public int port; + public int timeout = 30000; // Default to 30s. + } + + public static class RunInTerminalRequestArguments extends Arguments { + public String kind; // Supported kind should be "integrated" or "external". + public String title; + public String cwd; // required. + public String[] args; // required. + public Map env; + + private RunInTerminalRequestArguments() { + // do nothing. + } + + /** + * Create a RunInTerminalRequestArguments instance. + * @param cmds + * List of command arguments. The first arguments is the command to run. + * @param cwd + * Working directory of the command. + * @return the request arguments instance. + */ + public static RunInTerminalRequestArguments createIntegratedTerminal(String[] cmds, String cwd) { + RunInTerminalRequestArguments requestArgs = new RunInTerminalRequestArguments(); + requestArgs.args = cmds; + requestArgs.cwd = cwd; + requestArgs.kind = "integrated"; + return requestArgs; + } + + /** + * Create a RunInTerminalRequestArguments instance. + * @param cmds + * List of command arguments. The first arguments is the command to run. + * @param cwd + * Working directory of the command. + * @param env + * Environment key-value pairs that are added to the default environment. + * @param title + * Optional title of the terminal. + * @return the request arguments instance. + */ + public static RunInTerminalRequestArguments createIntegratedTerminal(String[] cmds, String cwd, Map env, String title) { + RunInTerminalRequestArguments requestArgs = createIntegratedTerminal(cmds, cwd); + requestArgs.env = env; + requestArgs.title = title; + return requestArgs; + } + + /** + * Create a RunInTerminalRequestArguments instance. + * @param cmds + * List of command arguments. The first arguments is the command to run. + * @param cwd + * Working directory of the command. + * @return the request arguments instance. + */ + public static RunInTerminalRequestArguments createExternalTerminal(String[] cmds, String cwd) { + RunInTerminalRequestArguments requestArgs = new RunInTerminalRequestArguments(); + requestArgs.args = cmds; + requestArgs.cwd = cwd; + requestArgs.kind = "external"; + return requestArgs; + } + + /** + * Create a RunInTerminalRequestArguments instance. + * @param cmds + * List of command arguments. The first arguments is the command to run. + * @param cwd + * Working directory of the command. + * @param env + * Environment key-value pairs that are added to the default environment. + * @param title + * Optional title of the terminal. + * @return the request arguments instance. + */ + public static RunInTerminalRequestArguments createExternalTerminal(String[] cmds, String cwd, Map env, String title) { + RunInTerminalRequestArguments requestArgs = createExternalTerminal(cmds, cwd); + requestArgs.env = env; + requestArgs.title = title; + return requestArgs; + } + } + + public static class RestartArguments extends Arguments { + + } + + public static class DisconnectArguments extends Arguments { + // If client doesn't set terminateDebuggee attribute at the DisconnectRequest, + // the debugger would choose to terminate debuggee by default. + public boolean terminateDebuggee = true; + public boolean restart; + } + + public static class ConfigurationDoneArguments extends Arguments { + + } + + public static class SetBreakpointArguments extends Arguments { + public Source source; + public int[] lines = new int[0]; + public Types.SourceBreakpoint[] breakpoints = new Types.SourceBreakpoint[0]; + public boolean sourceModified = false; + } + + public static class StackTraceArguments extends Arguments { + public long threadId; + public int startFrame; + public int levels; + } + + public static class SetFunctionBreakpointsArguments extends Arguments { + public Types.FunctionBreakpoint[] breakpoints; + } + + public static class SetExceptionBreakpointsArguments extends Arguments { + public String[] filters = new String[0]; + } + + public static class ExceptionInfoArguments extends Arguments { + public long threadId; + } + + public static class ThreadsArguments extends Arguments { + + } + + public static class ContinueArguments extends Arguments { + public long threadId; + } + + public static class StepArguments extends Arguments { + public long threadId; + } + + public static class NextArguments extends StepArguments { + + } + + public static class StepInArguments extends StepArguments { + public int targetId; + } + + public static class StepOutArguments extends StepArguments { + + } + + public static class StepInTargetsArguments extends Arguments { + public int frameId; + } + + public static class PauseArguments extends Arguments { + public long threadId; + } + + public static class ThreadOperationArguments extends Arguments { + public long threadId; + } + + public static class ScopesArguments extends Arguments { + public int frameId; + } + + public static class VariablesArguments extends Arguments { + public int variablesReference = -1; + public String filter; + public int start; + public int count; + public ValueFormat format; + } + + public static class SetVariableArguments extends Arguments { + public int variablesReference; + public String name; + public String value; + public ValueFormat format; + } + + public static class RefreshVariablesArguments extends Arguments { + public boolean showStaticVariables = false; + public boolean showQualifiedNames = false; + public boolean showHex = false; + public boolean showLogicalStructure = true; + public boolean showToString = true; + } + + public static class SourceArguments extends Arguments { + public int sourceReference; + } + + public static class EvaluateArguments extends Arguments { + public String expression; + public int frameId; + public String context; + public ValueFormat format; + } + + public static class RedefineClassesArguments extends Arguments { + + } + + public static class RestartFrameArguments extends Arguments { + public int frameId; + } + + public static class CompletionsArguments extends Arguments { + public int frameId; + public String text; + public int line; + public int column; + } + + public static class DataBreakpointInfoArguments extends Arguments { + /** + * Reference to the Variable container if the data breakpoint is requested for a child of the container. + */ + public int variablesReference; + /** + * The name of the Variable's child to obtain data breakpoint information for. If variableReference isn’t provided, this can be an expression. + */ + public String name; + } + + public static class SetDataBreakpointsArguments extends Arguments { + /** + * The contents of this array replaces all existing data breakpoints. An empty array clears all data breakpoints. + */ + public DataBreakpoint[] breakpoints; + } + + public static class InlineValuesArguments extends Arguments { + public int frameId; + public InlineVariable[] variables; + } + + public static class InlineVariable { + public String expression; + public String declaringClass; + + @Override + public int hashCode() { + return Objects.hash(declaringClass, expression); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof InlineVariable)) { + return false; + } + InlineVariable other = (InlineVariable) obj; + return Objects.equals(declaringClass, other.declaringClass) && Objects.equals(expression, other.expression); + } + } + + /** + * Arguments for breakpointLocations request. + */ + public static class BreakpointLocationsArguments extends Arguments { + /** + * The source location of the breakpoints; either `source.path` or + * `source.reference` must be specified. + */ + public Source source; + + /** + * Start line of range to search possible breakpoint locations in. If only the + * line is specified, the request returns all possible locations in that line. + */ + public int line; + + /** + * Start column of range to search possible breakpoint locations in. If no + * start column is given, the first column in the start line is assumed. + */ + public int column; + + /** + * End line of range to search possible breakpoint locations in. If no end + * line is given, then the end line is assumed to be the start line. + */ + public int endLine; + + /** + * End column of range to search possible breakpoint locations in. If no end + * column is given, then it is assumed to be in the last column of the end + * line. + */ + public int endColumn; + } + + public static enum Command { + INITIALIZE("initialize", InitializeArguments.class), + LAUNCH("launch", LaunchArguments.class), + ATTACH("attach", AttachArguments.class), + DISCONNECT("disconnect", DisconnectArguments.class), + CONFIGURATIONDONE("configurationDone", ConfigurationDoneArguments.class), + NEXT("next", NextArguments.class), + CONTINUE("continue", ContinueArguments.class), + STEPIN("stepIn", StepInArguments.class), + STEPOUT("stepOut", StepOutArguments.class), + STEPIN_TARGETS("stepInTargets", + StepInTargetsArguments.class), + PAUSE("pause", PauseArguments.class), + STACKTRACE("stackTrace", StackTraceArguments.class), + RESTARTFRAME("restartFrame", RestartFrameArguments.class), + SCOPES("scopes", ScopesArguments.class), + VARIABLES("variables", VariablesArguments.class), + SETVARIABLE("setVariable", SetVariableArguments.class), + SOURCE("source", SourceArguments.class), + THREADS("threads", ThreadsArguments.class), + SETBREAKPOINTS("setBreakpoints", SetBreakpointArguments.class), + SETEXCEPTIONBREAKPOINTS("setExceptionBreakpoints", SetExceptionBreakpointsArguments.class), + SETFUNCTIONBREAKPOINTS("setFunctionBreakpoints", SetFunctionBreakpointsArguments.class), + EVALUATE("evaluate", EvaluateArguments.class), + COMPLETIONS("completions", CompletionsArguments.class), + RUNINTERMINAL("runInTerminal", RunInTerminalRequestArguments.class), + REDEFINECLASSES("redefineClasses", RedefineClassesArguments.class), + EXCEPTIONINFO("exceptionInfo", ExceptionInfoArguments.class), + DATABREAKPOINTINFO("dataBreakpointInfo", DataBreakpointInfoArguments.class), + SETDATABREAKPOINTS("setDataBreakpoints", SetDataBreakpointsArguments.class), + CONTINUEALL("continueAll", ThreadOperationArguments.class), + CONTINUEOTHERS("continueOthers", ThreadOperationArguments.class), + PAUSEALL("pauseAll", ThreadOperationArguments.class), + PAUSEOTHERS("pauseOthers", ThreadOperationArguments.class), + INLINEVALUES("inlineValues", InlineValuesArguments.class), + REFRESHVARIABLES("refreshVariables", RefreshVariablesArguments.class), + PROCESSID("processId", Arguments.class), + BREAKPOINTLOCATIONS("breakpointLocations", BreakpointLocationsArguments.class), + UNSUPPORTED("", Arguments.class); + + private String command; + private Class argumentType; + + Command(String command, Class argumentType) { + this.command = command; + this.argumentType = argumentType; + } + + public String getName() { + return this.command; + } + + @Override + public String toString() { + return this.command; + } + + public Class getArgumentType() { + return this.argumentType; + } + + /** + * Get the corresponding Command type by the command name. + * If the command is not defined in the enum type, return UNSUPPORTED. + * @param command + * the command name + * @return the Command type + */ + public static Command parse(String command) { + Command[] found = Arrays.stream(Command.values()).filter(cmd -> { + return cmd.toString().equals(command); + }).toArray(Command[]::new); + + if (found.length > 0) { + return found[0]; + } + return UNSUPPORTED; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java b/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java new file mode 100755 index 0000000..dedbd3c --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/protocol/Responses.java @@ -0,0 +1,376 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.protocol; + +import java.util.List; + +import com.microsoft.java.debug.core.protocol.Types.BreakpointLocation; +import com.microsoft.java.debug.core.protocol.Types.DataBreakpointAccessType; +import com.microsoft.java.debug.core.protocol.Types.ExceptionBreakMode; +import com.microsoft.java.debug.core.protocol.Types.ExceptionDetails; +import com.microsoft.java.debug.core.protocol.Types.StepInTarget; +import com.microsoft.java.debug.core.protocol.Types.Variable; + +/** + * The response content types defined by VSCode Debug Protocol. + */ +public class Responses { + /** + * subclasses of ResponseBody are serialized as the response body. Don't + * change their instance variables since that will break the OpenDebug + * protocol. + */ + public static class ResponseBody { + // empty + } + + public static class InitializeResponseBody extends ResponseBody { + public Types.Capabilities body; + + public InitializeResponseBody(Types.Capabilities capabilities) { + body = capabilities; + } + } + + public static class ProcessIdResponseBody extends ResponseBody { + /** + * The process ID. + */ + public long processId = -1; + /** + * The process ID of the terminal shell if the process is running in a terminal shell. + */ + public long shellProcessId = -1; + + public ProcessIdResponseBody(long processId) { + this.processId = processId; + } + + public ProcessIdResponseBody(long processId, long shellProcessId) { + this.processId = processId; + this.shellProcessId = shellProcessId; + } + } + + public static class RunInTerminalResponseBody extends ProcessIdResponseBody { + + public RunInTerminalResponseBody(long processId) { + super(processId); + } + + public RunInTerminalResponseBody(long processId, long shellProcessId) { + super(processId, shellProcessId); + } + } + + public static class ErrorResponseBody extends ResponseBody { + public Types.Message error; + + public ErrorResponseBody(Types.Message m) { + error = m; + } + } + + public static class StackTraceResponseBody extends ResponseBody { + public Types.StackFrame[] stackFrames; + + public int totalFrames; + + /** + * Constructs an StackTraceResponseBody with the given stack frame list. + * @param frames + * a {@link Types.StackFrame} list + * @param total + * the total frame number + */ + public StackTraceResponseBody(List frames, int total) { + if (frames == null) { + stackFrames = new Types.StackFrame[0]; + } else { + stackFrames = frames.toArray(new Types.StackFrame[0]); + } + + totalFrames = total; + } + } + + public static class ScopesResponseBody extends ResponseBody { + public Types.Scope[] scopes; + + /** + * Constructs a ScopesResponseBody with the Scope list. + * @param scps + * a {@link Types.Scope} list + */ + public ScopesResponseBody(List scps) { + if (scps == null) { + scopes = new Types.Scope[0]; + } else { + scopes = scps.toArray(new Types.Scope[0]); + } + } + } + + public static class VariablesResponseBody extends ResponseBody { + public Variable[] variables; + + /** + * Constructs a VariablesResponseBody with the given variable list. + * @param vars + * a {@link Variable} list + */ + public VariablesResponseBody(List vars) { + if (vars == null) { + variables = new Variable[0]; + } else { + variables = vars.toArray(new Variable[0]); + } + } + } + + public static class SetVariablesResponseBody extends ResponseBody { + public String value; + public String type; + public int variablesReference; + public int indexedVariables; + + /** + * Constructs a SetVariablesResponseBody with the given variable information. + */ + public SetVariablesResponseBody(String type, String value, int variablesReference, int indexedVariables) { + this.type = type; + this.value = value; + this.variablesReference = variablesReference; + this.indexedVariables = indexedVariables; + } + } + + public static class SourceResponseBody extends ResponseBody { + public String content; + public String mimeType = "text/x-java"; // Set mimeType to tell VSCode to recognize the source contents as java source. + + public SourceResponseBody(String content) { + this.content = content; + } + + public SourceResponseBody(String content, String mimeType) { + this.content = content; + this.mimeType = mimeType; + } + } + + public static class ThreadsResponseBody extends ResponseBody { + public Types.Thread[] threads; + + /** + * Constructs a ThreadsResponseBody with the given thread list. + * @param vars + * a {@link Types.Thread} list + */ + public ThreadsResponseBody(List vars) { + if (vars == null) { + threads = new Types.Thread[0]; + } else { + threads = vars.toArray(new Types.Thread[0]); + } + } + } + + public static class EvaluateResponseBody extends ResponseBody { + public String result; + public int variablesReference; + public String type; + public int indexedVariables; + + /** + * Constructor. + */ + public EvaluateResponseBody(String value, int ref, String type, int indexedVariables) { + this.result = value; + this.variablesReference = ref; + this.type = type; + this.indexedVariables = indexedVariables; + } + } + + public static class CompletionsResponseBody extends ResponseBody { + public Types.CompletionItem[] targets; + + /** + * Constructor. + */ + public CompletionsResponseBody(List items) { + if (items == null) { + targets = new Types.CompletionItem[0]; + } else { + targets = items.toArray(new Types.CompletionItem[0]); + } + } + } + + public static class SetBreakpointsResponseBody extends ResponseBody { + public Types.Breakpoint[] breakpoints; + + /** + * Constructs a SetBreakpointsResponssseBody with the given breakpoint list. + * @param bpts + * a {@link Types.Breakpoint} list + */ + public SetBreakpointsResponseBody(List bpts) { + if (bpts == null) { + breakpoints = new Types.Breakpoint[0]; + } else { + breakpoints = bpts.toArray(new Types.Breakpoint[0]); + } + } + } + + public static class SetDataBreakpointsResponseBody extends SetBreakpointsResponseBody { + public SetDataBreakpointsResponseBody(List bpts) { + super(bpts); + } + } + + public static class DataBreakpointInfoResponseBody extends ResponseBody { + /** + * An identifier for the data on which a data breakpoint can be registered with the setDataBreakpoints request + * or null if no data breakpoint is available. + */ + public String dataId; + /** + * UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. + */ + public String description; + /** + * Optional attribute listing the available access types for a potential data breakpoint. A UI frontend could surface this information. + */ + public DataBreakpointAccessType[] accessTypes; + /** + * Optional attribute indicating that a potential data breakpoint could be persisted across sessions. + */ + public boolean canPersist; + + public DataBreakpointInfoResponseBody(String dataId) { + this(dataId, null); + } + + public DataBreakpointInfoResponseBody(String dataId, String description) { + this(dataId, description, null); + } + + public DataBreakpointInfoResponseBody(String dataId, String description, + DataBreakpointAccessType[] accessTypes) { + this(dataId, description, accessTypes, false); + } + + /** + * Constructor. + */ + public DataBreakpointInfoResponseBody(String dataId, String description, DataBreakpointAccessType[] accessTypes, + boolean canPersist) { + this.dataId = dataId; + this.description = description; + this.accessTypes = accessTypes; + this.canPersist = canPersist; + } + } + + /** + * Response to breakpointLocations request. + * Contains possible locations for source breakpoints. + */ + public static class BreakpointLocationsResponseBody extends ResponseBody { + /** + * Sorted set of possible breakpoint locations. + */ + public BreakpointLocation[] breakpoints; + + public BreakpointLocationsResponseBody(BreakpointLocation[] breakpoints) { + this.breakpoints = breakpoints; + } + } + + public static class ContinueResponseBody extends ResponseBody { + public boolean allThreadsContinued; + + public ContinueResponseBody() { + this.allThreadsContinued = true; + } + + /** + * Constructs a ContinueResponseBody. + */ + public ContinueResponseBody(boolean allThreadsContinued) { + this.allThreadsContinued = allThreadsContinued; + } + } + + public static class ExceptionInfoResponse extends ResponseBody { + public String exceptionId; + public String description; + public ExceptionBreakMode breakMode; + public ExceptionDetails details; + + /** + * Constructs a ExceptionInfoResponse. + */ + public ExceptionInfoResponse(String exceptionId, String description, ExceptionBreakMode breakMode) { + this.exceptionId = exceptionId; + this.description = description; + this.breakMode = breakMode; + } + + /** + * Constructs a ExceptionInfoResponse. + */ + public ExceptionInfoResponse(String exceptionId, String description, ExceptionBreakMode breakMode, ExceptionDetails details) { + this(exceptionId, description, breakMode); + this.details = details; + } + } + + public static class RedefineClassesResponse extends ResponseBody { + public String[] changedClasses = new String[0]; + public String errorMessage = null; + + /** + * Constructor. + */ + public RedefineClassesResponse(String[] changedClasses) { + this(changedClasses, null); + } + + /** + * Constructor. + */ + public RedefineClassesResponse(String[] changedClasses, String errorMessage) { + this.changedClasses = changedClasses; + this.errorMessage = errorMessage; + } + } + + public static class InlineValuesResponse extends ResponseBody { + public Variable[] variables; + + public InlineValuesResponse(Variable[] variables) { + this.variables = variables; + } + } + + public static class StepInTargetsResponse extends ResponseBody { + public StepInTarget[] targets; + + public StepInTargetsResponse(StepInTarget[] targets) { + this.targets = targets; + } + } +} diff --git a/src/main/java/com/microsoft/java/debug/core/protocol/Types.java b/src/main/java/com/microsoft/java/debug/core/protocol/Types.java new file mode 100755 index 0000000..33308af --- /dev/null +++ b/src/main/java/com/microsoft/java/debug/core/protocol/Types.java @@ -0,0 +1,453 @@ +/******************************************************************************* +* Copyright (c) 2017-2022 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core.protocol; + +import java.nio.file.Paths; + +import com.google.gson.annotations.SerializedName; + +/** + * The data types defined by Debug Adapter Protocol. + */ +public class Types { + public static class Message { + public int id; + public String format; + + /** + * Constructs a message with the given information. + * + * @param id + * message id + * @param format + * a format string + */ + public Message(int id, String format) { + this.id = id; + this.format = format; + } + } + + public static class StackFrame { + public int id; + public Source source; + public int line; + public int column; + public String name; + public String presentationHint; + + + /** + * Constructs a StackFrame with the given information. + * + * @param id + * the stack frame id + * @param name + * the stack frame name + * @param src + * source info of the stack frame + * @param ln + * line number of the stack frame + * @param col + * column number of the stack frame + * @param presentationHint + * An optional hint for how to present this frame in the UI. + * Values: 'normal', 'label', 'subtle' + */ + public StackFrame(int id, String name, Source src, int ln, int col, String presentationHint) { + this.id = id; + this.name = name; + this.source = src; + this.line = ln; + this.column = col; + this.presentationHint = presentationHint; + } + } + + public static class Scope { + public String name; + public int variablesReference; + public boolean expensive; + + /** + * Constructor. + */ + public Scope(String name, int rf, boolean exp) { + this.name = name; + this.variablesReference = rf; + this.expensive = exp; + } + } + + public static class Variable { + public String name; + public String value; + public String type; + public int variablesReference; + public int namedVariables; + public int indexedVariables; + public String evaluateName; + public VariablePresentationHint presentationHint; + + /** + * Constructor. + */ + public Variable(String name, String val, String type, int rf, String evaluateName) { + this.name = name; + this.value = val; + this.type = type; + this.variablesReference = rf; + this.evaluateName = evaluateName; + } + + /** + * Constructor. + */ + public Variable(String name, String value) { + this.name = name; + this.value = value; + } + } + + public static class Thread { + public long id; + public String name; + + /** + * Constructor. + */ + public Thread(long l, String name) { + this.id = l; + if (name == null || name.length() == 0) { + this.name = String.format("Thread #%d", l); + } else { + this.name = name; + } + } + } + + public static class Source { + public String name; + public String path; + public int sourceReference; + + public Source() { + } + + /** + * Constructor. + */ + public Source(String name, String path, int rf) { + this.name = name; + this.path = path; + this.sourceReference = rf; + } + + /** + * Constructor. + */ + public Source(String path, int rf) { + this.name = Paths.get(path).getFileName().toString(); + this.path = path; + this.sourceReference = rf; + } + } + + public static class Breakpoint { + /** + * An optional identifier for the breakpoint. It is needed if breakpoint events are used to update or remove breakpoints. + */ + public int id; + /** + * If true breakpoint could be set (but not necessarily at the desired location). + */ + public boolean verified; + /** + * The start line of the actual range covered by the breakpoint. + */ + public int line; + /** + * An optional message about the state of the breakpoint. This is shown to the user and can be used to explain why a breakpoint could not be verified. + */ + public String message; + + public Breakpoint(boolean verified) { + this.verified = verified; + } + + public Breakpoint(int id, boolean verified) { + this.id = id; + this.verified = verified; + } + + /** + * Constructor. + */ + public Breakpoint(int id, boolean verified, int line, String message) { + this.id = id; + this.verified = verified; + this.line = line; + this.message = message; + } + } + + /** + * Properties of a breakpoint or logpoint passed to the setBreakpoints request. + */ + public static class SourceBreakpoint { + public int line; + public int column; + public String hitCondition; + public String condition; + public String logMessage; + + public SourceBreakpoint(int line, int column) { + this.line = line; + this.column = column; + } + + /** + * Constructor. + */ + public SourceBreakpoint(int line, String condition, String hitCondition) { + this.line = line; + this.condition = condition; + this.hitCondition = hitCondition; + } + + /** + * Constructor. + */ + public SourceBreakpoint(int line, String condition, String hitCondition, int column) { + this.line = line; + this.column = column; + this.condition = condition; + this.hitCondition = hitCondition; + } + } + + public static class FunctionBreakpoint { + public String name; + public String condition; + public String hitCondition; + + public FunctionBreakpoint() { + } + + public FunctionBreakpoint(String name) { + this.name = name; + } + } + + public static enum DataBreakpointAccessType { + @SerializedName("read") + READ("read"), + @SerializedName("write") + WRITE("write"), + @SerializedName("readWrite") + READWRITE("readWrite"); + + String label; + + DataBreakpointAccessType(String label) { + this.label = label; + } + + public String label() { + return label; + } + } + + public static class DataBreakpoint { + /** + * An id representing the data. This id is returned from the dataBreakpointInfo request. + */ + public String dataId; + /** + * The access type of the data. + */ + public DataBreakpointAccessType accessType; + /** + * An optional expression for conditional breakpoints. + */ + public String condition; + /** + * An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. + */ + public String hitCondition; + + public DataBreakpoint(String dataId) { + this.dataId = dataId; + } + + public DataBreakpoint(String dataId, DataBreakpointAccessType accessType) { + this.dataId = dataId; + this.accessType = accessType; + } + + /** + * Constructor. + */ + public DataBreakpoint(String dataId, DataBreakpointAccessType accessType, String condition, String hitCondition) { + this.dataId = dataId; + this.accessType = accessType; + this.condition = condition; + this.hitCondition = hitCondition; + } + } + + /** + * Properties of a breakpoint location returned from the breakpointLocations request. + */ + public static class BreakpointLocation { + /** + * Start line of breakpoint location. + */ + public int line; + + /** + * The start column of breakpoint location. + */ + public int column; + + /** + * The end line of breakpoint location if the location covers a range. + */ + public int endLine; + + /** + * The end column of breakpoint location if the location covers a range. + */ + public int endColumn; + + public BreakpointLocation() { + } + + public BreakpointLocation(int line, int column) { + this.line = line; + this.column = column; + } + + public BreakpointLocation(int line, int column, int endLine, int endColumn) { + this.line = line; + this.column = column; + this.endLine = endLine; + this.endColumn = endColumn; + } + } + + public static class CompletionItem { + public String label; + public String text; + public String type; + /** + * A string that should be used when comparing this item with other items. + */ + public String sortText; + + public int start; + public int number; + + public CompletionItem() { + } + + public CompletionItem(String label, String text) { + this.label = label; + this.text = text; + } + } + + public static class ExceptionBreakpointFilter { + public static final String UNCAUGHT_EXCEPTION_FILTER_NAME = "uncaught"; + public static final String CAUGHT_EXCEPTION_FILTER_NAME = "caught"; + public static final String UNCAUGHT_EXCEPTION_FILTER_LABEL = "Uncaught Exceptions"; + public static final String CAUGHT_EXCEPTION_FILTER_LABEL = "Caught Exceptions"; + + public String label; + public String filter; + + public ExceptionBreakpointFilter(String value, String label) { + this.filter = value; + this.label = label; + } + + public static final ExceptionBreakpointFilter UNCAUGHT_EXCEPTION_FILTER = + new ExceptionBreakpointFilter(UNCAUGHT_EXCEPTION_FILTER_NAME, UNCAUGHT_EXCEPTION_FILTER_LABEL); + public static final ExceptionBreakpointFilter CAUGHT_EXCEPTION_FILTER = + new ExceptionBreakpointFilter(CAUGHT_EXCEPTION_FILTER_NAME, CAUGHT_EXCEPTION_FILTER_LABEL); + } + + public static enum ExceptionBreakMode { + @SerializedName("never") + NEVER, + @SerializedName("always") + ALWAYS, + @SerializedName("unhandled") + UNHANDLED, + @SerializedName("userUnhandled") + USERUNHANDLED + } + + public static class ExceptionDetails { + public String message; + public String typeName; + public String fullTypeName; + public String evaluateName; + public String stackTrace; + public ExceptionDetails[] innerException; + } + + public static class VariablePresentationHint { + public boolean lazy; + + public VariablePresentationHint(boolean lazy) { + this.lazy = lazy; + } + } + + public static class Capabilities { + public boolean supportsConfigurationDoneRequest; + public boolean supportsHitConditionalBreakpoints; + public boolean supportsConditionalBreakpoints; + public boolean supportsEvaluateForHovers; + public boolean supportsCompletionsRequest; + public boolean supportsRestartFrame; + public boolean supportsSetVariable; + public boolean supportsRestartRequest; + public boolean supportTerminateDebuggee; + public boolean supportsDelayedStackTraceLoading; + public boolean supportsLogPoints; + public boolean supportsExceptionInfoRequest; + public ExceptionBreakpointFilter[] exceptionBreakpointFilters = new ExceptionBreakpointFilter[0]; + public boolean supportsDataBreakpoints; + public boolean supportsClipboardContext; + public boolean supportsFunctionBreakpoints; + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_BreakpointLocations + public boolean supportsBreakpointLocationsRequest; + public boolean supportsStepInTargetsRequest; + } + + public static class StepInTarget { + public int id; + public String label; + public int line; + public int column; + public int endLine; + public int endColumn; + + public StepInTarget(int id, String label) { + this.id = id; + this.label = label; + } + } + +} diff --git a/src/main/java/io/reactivex/BackpressureOverflowStrategy.java b/src/main/java/io/reactivex/BackpressureOverflowStrategy.java new file mode 100755 index 0000000..b137bf2 --- /dev/null +++ b/src/main/java/io/reactivex/BackpressureOverflowStrategy.java @@ -0,0 +1,28 @@ +/** + * 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; + +/** + * Options to deal with buffer overflow when using onBackpressureBuffer. + */ +public enum BackpressureOverflowStrategy { + /** Signal a MissingBackpressureException and terminate the sequence. */ + ERROR, + /** Drop the oldest value from the buffer. */ + DROP_OLDEST, + /** Drop the latest value from the buffer. */ + DROP_LATEST +} diff --git a/src/main/java/io/reactivex/BackpressureStrategy.java b/src/main/java/io/reactivex/BackpressureStrategy.java new file mode 100755 index 0000000..1639ddc --- /dev/null +++ b/src/main/java/io/reactivex/BackpressureStrategy.java @@ -0,0 +1,43 @@ +/** + * 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; + +/** + * Represents the options for applying backpressure to a source sequence. + */ +public enum BackpressureStrategy { + /** + * OnNext events are written without any buffering or dropping. + * Downstream has to deal with any overflow. + *

Useful when one applies one of the custom-parameter onBackpressureXXX operators. + */ + MISSING, + /** + * Signals a MissingBackpressureException in case the downstream can't keep up. + */ + ERROR, + /** + * Buffers all onNext values until the downstream consumes it. + */ + BUFFER, + /** + * Drops the most recent onNext value if the downstream can't keep up. + */ + DROP, + /** + * Keeps only the latest onNext value, overwriting any previous value if the + * downstream can't keep up. + */ + LATEST +} diff --git a/src/main/java/io/reactivex/Completable.java b/src/main/java/io/reactivex/Completable.java new file mode 100755 index 0000000..1994632 --- /dev/null +++ b/src/main/java/io/reactivex/Completable.java @@ -0,0 +1,2786 @@ +/** + * 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; + +import java.util.concurrent.*; + +import org.reactivestreams.Publisher; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.observers.*; +import io.reactivex.internal.operators.completable.*; +import io.reactivex.internal.operators.maybe.*; +import io.reactivex.internal.operators.mixed.*; +import io.reactivex.internal.operators.single.*; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; + +/** + * The {@code Completable} class represents a deferred computation without any value but + * only indication for completion or exception. + *

+ * {@code Completable} behaves similarly to {@link Observable} except that it can only emit either + * a completion or error signal (there is no {@code onNext} or {@code onSuccess} as with the other + * reactive types). + *

+ * The {@code Completable} class implements the {@link CompletableSource} base interface and the default consumer + * type it interacts with is the {@link CompletableObserver} via the {@link #subscribe(CompletableObserver)} method. + * The {@code Completable} operates with the following sequential protocol: + *


+ *     onSubscribe (onError | onComplete)?
+ * 
+ *

+ * Note that as with the {@code Observable} protocol, {@code onError} and {@code onComplete} are mutually exclusive events. + *

+ * Like {@link Observable}, a running {@code Completable} can be stopped through the {@link Disposable} instance + * provided to consumers through {@link SingleObserver#onSubscribe}. + *

+ * Like an {@code Observable}, a {@code Completable} is lazy, can be either "hot" or "cold", synchronous or + * asynchronous. {@code Completable} instances returned by the methods of this class are cold + * and there is a standard hot implementation in the form of a subject: + * {@link io.reactivex.subjects.CompletableSubject CompletableSubject}. + *

+ * The documentation for this class makes use of marble diagrams. The following legend explains these diagrams: + *

+ * + *

+ * See {@link Flowable} or {@link Observable} for the + * implementation of the Reactive Pattern for a stream or vector of values. + *

+ * Example: + *


+ * Disposable d = Completable.complete()
+ *    .delay(10, TimeUnit.SECONDS, Schedulers.io())
+ *    .subscribeWith(new DisposableCompletableObserver() {
+ *        @Override
+ *        public void onStart() {
+ *            System.out.println("Started");
+ *        }
+ *
+ *        @Override
+ *        public void onError(Throwable error) {
+ *            error.printStackTrace();
+ *        }
+ *
+ *        @Override
+ *        public void onComplete() {
+ *            System.out.println("Done!");
+ *        }
+ *    });
+ * 
+ * Thread.sleep(5000);
+ * 
+ * d.dispose();
+ * 
+ *

+ * Note that by design, subscriptions via {@link #subscribe(CompletableObserver)} can't be disposed + * from the outside (hence the + * {@code void} return of the {@link #subscribe(CompletableObserver)} method) and it is the + * responsibility of the implementor of the {@code CompletableObserver} to allow this to happen. + * RxJava supports such usage with the standard + * {@link io.reactivex.observers.DisposableCompletableObserver DisposableCompletableObserver} instance. + * For convenience, the {@link #subscribeWith(CompletableObserver)} method is provided as well to + * allow working with a {@code CompletableObserver} (or subclass) instance to be applied with in + * a fluent manner (such as in the example above). + * + * @see io.reactivex.observers.DisposableCompletableObserver + */ +public abstract class Completable implements CompletableSource { + /** + * Returns a Completable which terminates as soon as one of the source Completables + * terminates (normally or with an error) and disposes all other Completables. + *

+ * + *

+ *
Scheduler:
+ *
{@code ambArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the array of source Completables. A subscription to each source will + * occur in the same order as in this array. + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable ambArray(final CompletableSource... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + if (sources.length == 0) { + return complete(); + } + if (sources.length == 1) { + return wrap(sources[0]); + } + + return RxJavaPlugins.onAssembly(new CompletableAmb(sources, null)); + } + + /** + * Returns a Completable which terminates as soon as one of the source Completables + * terminates (normally or with an error) and disposes all other Completables. + *

+ * + *

+ *
Scheduler:
+ *
{@code amb} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the array of source Completables. A subscription to each source will + * occur in the same order as in this Iterable. + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable amb(final Iterable sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + + return RxJavaPlugins.onAssembly(new CompletableAmb(null, sources)); + } + + /** + * Returns a Completable instance that completes immediately when subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code complete} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return a Completable instance that completes immediately + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable complete() { + return RxJavaPlugins.onAssembly(CompletableEmpty.INSTANCE); + } + + /** + * Returns a Completable which completes only when all sources complete, one after another. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the sources to concatenate + * @return the Completable instance which completes only when all sources complete + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable concatArray(CompletableSource... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + if (sources.length == 0) { + return complete(); + } else + if (sources.length == 1) { + return wrap(sources[0]); + } + return RxJavaPlugins.onAssembly(new CompletableConcatArray(sources)); + } + + /** + * Returns a Completable which completes only when all sources complete, one after another. + *

+ * + *

+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the sources to concatenate + * @return the Completable instance which completes only when all sources complete + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable concat(Iterable sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + + return RxJavaPlugins.onAssembly(new CompletableConcatIterable(sources)); + } + + /** + * Returns a Completable which completes only when all sources complete, one after another. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Completable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the sources to concatenate + * @return the Completable instance which completes only when all sources complete + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public static Completable concat(Publisher sources) { + return concat(sources, 2); + } + + /** + * Returns a Completable which completes only when all sources complete, one after another. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Completable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the sources to concatenate + * @param prefetch the number of sources to prefetch from the sources + * @return the Completable instance which completes only when all sources complete + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public static Completable concat(Publisher sources, int prefetch) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new CompletableConcat(sources, prefetch)); + } + + /** + * Provides an API (via a cold Completable) that bridges the reactive world with the callback-style world. + *

+ * + *

+ * Example: + *


+     * Completable.create(emitter -> {
+     *     Callback listener = new Callback() {
+     *         @Override
+     *         public void onEvent(Event e) {
+     *             emitter.onComplete();
+     *         }
+     *
+     *         @Override
+     *         public void onFailure(Exception e) {
+     *             emitter.onError(e);
+     *         }
+     *     };
+     *
+     *     AutoCloseable c = api.someMethod(listener);
+     *
+     *     emitter.setCancellable(c::close);
+     *
+     * });
+     * 
+ *
+ *
Scheduler:
+ *
{@code create} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param source the emitter that is called when a CompletableObserver subscribes to the returned {@code Completable} + * @return the new Completable instance + * @see CompletableOnSubscribe + * @see Cancellable + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable create(CompletableOnSubscribe source) { + ObjectHelper.requireNonNull(source, "source is null"); + return RxJavaPlugins.onAssembly(new CompletableCreate(source)); + } + + /** + * Constructs a Completable instance by wrapping the given source callback + * without any safeguards; you should manage the lifecycle and response + * to downstream disposal. + *

+ * + *

+ *
Scheduler:
+ *
{@code unsafeCreate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param source the callback which will receive the CompletableObserver instances + * when the Completable is subscribed to. + * @return the created Completable instance + * @throws NullPointerException if source is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable unsafeCreate(CompletableSource source) { + ObjectHelper.requireNonNull(source, "source is null"); + if (source instanceof Completable) { + throw new IllegalArgumentException("Use of unsafeCreate(Completable)!"); + } + return RxJavaPlugins.onAssembly(new CompletableFromUnsafeSource(source)); + } + + /** + * Defers the subscription to a Completable instance returned by a supplier. + *

+ * + *

+ *
Scheduler:
+ *
{@code defer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param completableSupplier the supplier that returns the Completable that will be subscribed to. + * @return the Completable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable defer(final Callable completableSupplier) { + ObjectHelper.requireNonNull(completableSupplier, "completableSupplier"); + return RxJavaPlugins.onAssembly(new CompletableDefer(completableSupplier)); + } + + /** + * Creates a Completable which calls the given error supplier for each subscriber + * and emits its returned Throwable. + *

+ * + *

+ * If the errorSupplier returns null, the child CompletableObservers will receive a + * NullPointerException. + *

+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param errorSupplier the error supplier, not null + * @return the new Completable instance + * @throws NullPointerException if errorSupplier is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable error(final Callable errorSupplier) { + ObjectHelper.requireNonNull(errorSupplier, "errorSupplier is null"); + return RxJavaPlugins.onAssembly(new CompletableErrorSupplier(errorSupplier)); + } + + /** + * Creates a Completable instance that emits the given Throwable exception to subscribers. + *

+ * + *

+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param error the Throwable instance to emit, not null + * @return the new Completable instance + * @throws NullPointerException if error is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable error(final Throwable error) { + ObjectHelper.requireNonNull(error, "error is null"); + return RxJavaPlugins.onAssembly(new CompletableError(error)); + } + + /** + * Returns a Completable instance that runs the given Action for each subscriber and + * emits either an unchecked exception or simply completes. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromAction} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Action} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link CompletableObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Completable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + *
+ *
+ * @param run the runnable to run for each subscriber + * @return the new Completable instance + * @throws NullPointerException if run is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable fromAction(final Action run) { + ObjectHelper.requireNonNull(run, "run is null"); + return RxJavaPlugins.onAssembly(new CompletableFromAction(run)); + } + + /** + * Returns a Completable which when subscribed, executes the callable function, ignores its + * normal result and emits onError or onComplete only. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromCallable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link CompletableObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Completable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + *
+ *
+ * @param callable the callable instance to execute for each subscriber + * @return the new Completable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable fromCallable(final Callable callable) { + ObjectHelper.requireNonNull(callable, "callable is null"); + return RxJavaPlugins.onAssembly(new CompletableFromCallable(callable)); + } + + /** + * Returns a Completable instance that reacts to the termination of the given Future in a blocking fashion. + *

+ * + *

+ * Note that if any of the observers to this Completable call dispose, this Completable will cancel the future. + *

+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param future the future to react to + * @return the new Completable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable fromFuture(final Future future) { + ObjectHelper.requireNonNull(future, "future is null"); + return fromAction(Functions.futureAction(future)); + } + + /** + * Returns a Completable instance that when subscribed to, subscribes to the {@code Maybe} instance and + * emits a completion event if the maybe emits {@code onSuccess}/{@code onComplete} or forwards any + * {@code onError} events. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.17 - beta + * @param the value type of the {@link MaybeSource} element + * @param maybe the Maybe instance to subscribe to, not null + * @return the new Completable instance + * @throws NullPointerException if single is null + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable fromMaybe(final MaybeSource maybe) { + ObjectHelper.requireNonNull(maybe, "maybe is null"); + return RxJavaPlugins.onAssembly(new MaybeIgnoreElementCompletable(maybe)); + } + + /** + * Returns a Completable instance that runs the given Runnable for each subscriber and + * emits either its exception or simply completes. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromRunnable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Runnable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link CompletableObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Completable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + *
+ *
+ * @param run the runnable to run for each subscriber + * @return the new Completable instance + * @throws NullPointerException if run is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable fromRunnable(final Runnable run) { + ObjectHelper.requireNonNull(run, "run is null"); + return RxJavaPlugins.onAssembly(new CompletableFromRunnable(run)); + } + + /** + * Returns a Completable instance that subscribes to the given Observable, ignores all values and + * emits only the terminal event. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the type of the Observable + * @param observable the Observable instance to subscribe to, not null + * @return the new Completable instance + * @throws NullPointerException if flowable is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable fromObservable(final ObservableSource observable) { + ObjectHelper.requireNonNull(observable, "observable is null"); + return RxJavaPlugins.onAssembly(new CompletableFromObservable(observable)); + } + + /** + * Returns a Completable instance that subscribes to the given publisher, ignores all values and + * emits only the terminal event. + *

+ * + *

+ * The {@link Publisher} must follow the + * Reactive Streams specification. + * Violating the specification may result in undefined behavior. + *

+ * If possible, use {@link #create(CompletableOnSubscribe)} to create a + * source-like {@code Completable} instead. + *

+ * Note that even though {@link Publisher} appears to be a functional interface, it + * is not recommended to implement it through a lambda as the specification requires + * state management that is not achievable with a stateless lambda. + *

+ *
Backpressure:
+ *
The returned {@code Completable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well.
+ *
Scheduler:
+ *
{@code fromPublisher} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the type of the publisher + * @param publisher the Publisher instance to subscribe to, not null + * @return the new Completable instance + * @throws NullPointerException if publisher is null + * @see #create(CompletableOnSubscribe) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable fromPublisher(final Publisher publisher) { + ObjectHelper.requireNonNull(publisher, "publisher is null"); + return RxJavaPlugins.onAssembly(new CompletableFromPublisher(publisher)); + } + + /** + * Returns a Completable instance that when subscribed to, subscribes to the Single instance and + * emits a completion event if the single emits onSuccess or forwards any onError events. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the Single + * @param single the Single instance to subscribe to, not null + * @return the new Completable instance + * @throws NullPointerException if single is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable fromSingle(final SingleSource single) { + ObjectHelper.requireNonNull(single, "single is null"); + return RxJavaPlugins.onAssembly(new CompletableFromSingle(single)); + } + + /** + * Returns a Completable instance that subscribes to all sources at once and + * completes only when all source Completables complete or one of them emits an error. + *

+ * + *

+ *
Scheduler:
+ *
{@code mergeArray} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. + * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(CompletableSource...)} to merge sources and terminate only when all source {@code CompletableSource}s + * have completed or failed with an error. + *
+ *
+ * @param sources the iterable sequence of sources. + * @return the new Completable instance + * @throws NullPointerException if sources is null + * @see #mergeArrayDelayError(CompletableSource...) + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable mergeArray(CompletableSource... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + if (sources.length == 0) { + return complete(); + } else + if (sources.length == 1) { + return wrap(sources[0]); + } + return RxJavaPlugins.onAssembly(new CompletableMergeArray(sources)); + } + + /** + * Returns a Completable instance that subscribes to all sources at once and + * completes only when all source Completables complete or one of them emits an error. + *

+ * + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. + * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code CompletableSource}s + * have completed or failed with an error. + *
+ *
+ * @param sources the iterable sequence of sources. + * @return the new Completable instance + * @throws NullPointerException if sources is null + * @see #mergeDelayError(Iterable) + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable merge(Iterable sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new CompletableMergeIterable(sources)); + } + + /** + * Returns a Completable instance that subscribes to all sources at once and + * completes only when all source Completables complete or one of them emits an error. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Completable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. + * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher)} to merge sources and terminate only when all source {@code CompletableSource}s + * have completed or failed with an error. + *
+ *
+ * @param sources the iterable sequence of sources. + * @return the new Completable instance + * @throws NullPointerException if sources is null + * @see #mergeDelayError(Publisher) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + public static Completable merge(Publisher sources) { + return merge0(sources, Integer.MAX_VALUE, false); + } + + /** + * Returns a Completable instance that keeps subscriptions to a limited number of sources at once and + * completes only when all source Completables complete or one of them emits an error. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Completable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code CompletableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Completable} terminates with that {@code Throwable} and all other source {@code CompletableSource}s are disposed. + * If more than one {@code CompletableSource} signals an error, the resulting {@code Completable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Completable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, int)} to merge sources and terminate only when all source {@code CompletableSource}s + * have completed or failed with an error. + *
+ *
+ * @param sources the iterable sequence of sources. + * @param maxConcurrency the maximum number of concurrent subscriptions + * @return the new Completable instance + * @throws NullPointerException if sources is null + * @throws IllegalArgumentException if maxConcurrency is less than 1 + * @see #mergeDelayError(Publisher, int) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public static Completable merge(Publisher sources, int maxConcurrency) { + return merge0(sources, maxConcurrency, false); + } + + /** + * Returns a Completable instance that keeps subscriptions to a limited number of sources at once and + * completes only when all source Completables terminate in one way or another, combining any exceptions + * thrown by either the sources Observable or the inner Completable instances. + *
+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well. + *
Scheduler:
+ *
{@code merge0} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the iterable sequence of sources. + * @param maxConcurrency the maximum number of concurrent subscriptions + * @param delayErrors delay all errors from the main source and from the inner Completables? + * @return the new Completable instance + * @throws NullPointerException if sources is null + * @throws IllegalArgumentException if maxConcurrency is less than 1 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + private static Completable merge0(Publisher sources, int maxConcurrency, boolean delayErrors) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + return RxJavaPlugins.onAssembly(new CompletableMerge(sources, maxConcurrency, delayErrors)); + } + + /** + * Returns a CompletableConsumable that subscribes to all Completables in the source array and delays + * any error emitted by either the sources observable or any of the inner Completables until all of + * them terminate in a way or another. + *

+ * + *

+ *
Scheduler:
+ *
{@code mergeArrayDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the array of Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable mergeArrayDelayError(CompletableSource... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new CompletableMergeDelayErrorArray(sources)); + } + + /** + * Returns a Completable that subscribes to all Completables in the source sequence and delays + * any error emitted by either the sources observable or any of the inner Completables until all of + * them terminate in a way or another. + *

+ * + *

+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the sequence of Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable mergeDelayError(Iterable sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new CompletableMergeDelayErrorIterable(sources)); + } + + /** + * Returns a Completable that subscribes to all Completables in the source sequence and delays + * any error emitted by either the sources observable or any of the inner Completables until all of + * them terminate in a way or another. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Completable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the sequence of Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + public static Completable mergeDelayError(Publisher sources) { + return merge0(sources, Integer.MAX_VALUE, true); + } + + /** + * Returns a Completable that subscribes to a limited number of inner Completables at once in + * the source sequence and delays any error emitted by either the sources + * observable or any of the inner Completables until all of + * them terminate in a way or another. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Completable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the sequence of Completables + * @param maxConcurrency the maximum number of concurrent subscriptions to Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public static Completable mergeDelayError(Publisher sources, int maxConcurrency) { + return merge0(sources, maxConcurrency, true); + } + + /** + * Returns a Completable that never calls onError or onComplete. + *

+ * + *

+ *
Scheduler:
+ *
{@code never} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the singleton instance that never calls onError or onComplete + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable never() { + return RxJavaPlugins.onAssembly(CompletableNever.INSTANCE); + } + + /** + * Returns a Completable instance that fires its onComplete event after the given delay elapsed. + *

+ * + *

+ *
Scheduler:
+ *
{@code timer} does operate by default on the {@code computation} {@link Scheduler}.
+ *
+ * @param delay the delay time + * @param unit the delay unit + * @return the new Completable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Completable timer(long delay, TimeUnit unit) { + return timer(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Completable instance that fires its onComplete event after the given delay elapsed + * by using the supplied scheduler. + *

+ * + *

+ *
Scheduler:
+ *
{@code timer} operates on the {@link Scheduler} you specify.
+ *
+ * @param delay the delay time + * @param unit the delay unit + * @param scheduler the scheduler where to emit the complete event + * @return the new Completable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Completable timer(final long delay, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new CompletableTimer(delay, unit, scheduler)); + } + + /** + * Creates a NullPointerException instance and sets the given Throwable as its initial cause. + * @param ex the Throwable instance to use as cause, not null (not verified) + * @return the created NullPointerException + */ + private static NullPointerException toNpe(Throwable ex) { + NullPointerException npe = new NullPointerException("Actually not, but can't pass out an exception otherwise..."); + npe.initCause(ex); + return npe; + } + + /** + * Returns a Completable instance which manages a resource along + * with a custom Completable instance while the subscription is active. + *

+ * + *

+ * This overload disposes eagerly before the terminal event is emitted. + *

+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the resource type + * @param resourceSupplier the supplier that returns a resource to be managed. + * @param completableFunction the function that given a resource returns a Completable instance that will be subscribed to + * @param disposer the consumer that disposes the resource created by the resource supplier + * @return the new Completable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable using(Callable resourceSupplier, + Function completableFunction, + Consumer disposer) { + return using(resourceSupplier, completableFunction, disposer, true); + } + + /** + * Returns a Completable instance which manages a resource along + * with a custom Completable instance while the subscription is active and performs eager or lazy + * resource disposition. + *

+ * + *

+ * If this overload performs a lazy disposal after the terminal event is emitted. + * Exceptions thrown at this time will be delivered to RxJavaPlugins only. + *

+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the resource type + * @param resourceSupplier the supplier that returns a resource to be managed + * @param completableFunction the function that given a resource returns a non-null + * Completable instance that will be subscribed to + * @param disposer the consumer that disposes the resource created by the resource supplier + * @param eager if true, the resource is disposed before the terminal event is emitted, if false, the + * resource is disposed after the terminal event has been emitted + * @return the new Completable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable using( + final Callable resourceSupplier, + final Function completableFunction, + final Consumer disposer, + final boolean eager) { + ObjectHelper.requireNonNull(resourceSupplier, "resourceSupplier is null"); + ObjectHelper.requireNonNull(completableFunction, "completableFunction is null"); + ObjectHelper.requireNonNull(disposer, "disposer is null"); + + return RxJavaPlugins.onAssembly(new CompletableUsing(resourceSupplier, completableFunction, disposer, eager)); + } + + /** + * Wraps the given CompletableSource into a Completable + * if not already Completable. + *

+ * + *

+ *
Scheduler:
+ *
{@code wrap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param source the source to wrap + * @return the source or its wrapper Completable + * @throws NullPointerException if source is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Completable wrap(CompletableSource source) { + ObjectHelper.requireNonNull(source, "source is null"); + if (source instanceof Completable) { + return RxJavaPlugins.onAssembly((Completable)source); + } + return RxJavaPlugins.onAssembly(new CompletableFromUnsafeSource(source)); + } + + /** + * Returns a Completable that emits the a terminated event of either this Completable + * or the other Completable whichever fires first. + *

+ * + *

+ *
Scheduler:
+ *
{@code ambWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param other the other Completable, not null. A subscription to this provided source will occur after subscribing + * to the current source. + * @return the new Completable instance + * @throws NullPointerException if other is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable ambWith(CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return ambArray(this, other); + } + + /** + * Returns an Observable which will subscribe to this Completable and once that is completed then + * will subscribe to the {@code next} ObservableSource. An error event from this Completable will be + * propagated to the downstream subscriber and will result in skipping the subscription of the + * Observable. + *

+ * + *

+ *
Scheduler:
+ *
{@code andThen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the next ObservableSource + * @param next the Observable to subscribe after this Completable is completed, not null + * @return Observable that composes this Completable and next + * @throws NullPointerException if next is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable andThen(ObservableSource next) { + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new CompletableAndThenObservable(this, next)); + } + + /** + * Returns a Flowable which will subscribe to this Completable and once that is completed then + * will subscribe to the {@code next} Flowable. An error event from this Completable will be + * propagated to the downstream subscriber and will result in skipping the subscription of the + * Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well.
+ *
Scheduler:
+ *
{@code andThen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the next Publisher + * @param next the Publisher to subscribe after this Completable is completed, not null + * @return Flowable that composes this Completable and next + * @throws NullPointerException if next is null + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable andThen(Publisher next) { + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new CompletableAndThenPublisher(this, next)); + } + + /** + * Returns a Single which will subscribe to this Completable and once that is completed then + * will subscribe to the {@code next} SingleSource. An error event from this Completable will be + * propagated to the downstream subscriber and will result in skipping the subscription of the + * Single. + *

+ * + *

+ *
Scheduler:
+ *
{@code andThen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the next SingleSource + * @param next the Single to subscribe after this Completable is completed, not null + * @return Single that composes this Completable and next + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single andThen(SingleSource next) { + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new SingleDelayWithCompletable(next, this)); + } + + /** + * Returns a {@link Maybe} which will subscribe to this Completable and once that is completed then + * will subscribe to the {@code next} MaybeSource. An error event from this Completable will be + * propagated to the downstream subscriber and will result in skipping the subscription of the + * Maybe. + *

+ * + *

+ *
Scheduler:
+ *
{@code andThen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the next MaybeSource + * @param next the Maybe to subscribe after this Completable is completed, not null + * @return Maybe that composes this Completable and next + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe andThen(MaybeSource next) { + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new MaybeDelayWithCompletable(next, this)); + } + + /** + * Returns a Completable that first runs this Completable + * and then the other completable. + *

+ * + *

+ * This is an alias for {@link #concatWith(CompletableSource)}. + *

+ *
Scheduler:
+ *
{@code andThen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param next the other Completable, not null + * @return the new Completable instance + * @throws NullPointerException if other is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable andThen(CompletableSource next) { + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new CompletableAndThenCompletable(this, next)); + } + + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * + *

+ * This allows fluent conversion to any other type. + *

+ *
Scheduler:
+ *
{@code as} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.7 - experimental + * @param the resulting object type + * @param converter the function that receives the current Completable instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final R as(@NonNull CompletableConverter converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + + /** + * Subscribes to and awaits the termination of this Completable instance in a blocking manner and + * rethrows any exception emitted. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingAwait} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * @throws RuntimeException wrapping an InterruptedException if the current thread is interrupted + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingAwait() { + BlockingMultiObserver observer = new BlockingMultiObserver(); + subscribe(observer); + observer.blockingGet(); + } + + /** + * Subscribes to and awaits the termination of this Completable instance in a blocking manner + * with a specific timeout and rethrows any exception emitted within the timeout window. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingAwait} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * @param timeout the timeout value + * @param unit the timeout unit + * @return true if the this Completable instance completed normally within the time limit, + * false if the timeout elapsed before this Completable terminated. + * @throws RuntimeException wrapping an InterruptedException if the current thread is interrupted + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final boolean blockingAwait(long timeout, TimeUnit unit) { + ObjectHelper.requireNonNull(unit, "unit is null"); + BlockingMultiObserver observer = new BlockingMultiObserver(); + subscribe(observer); + return observer.blockingAwait(timeout, unit); + } + + /** + * Subscribes to this Completable instance and blocks until it terminates, then returns null or + * the emitted exception if any. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingGet} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the throwable if this terminated with an error, null otherwise + * @throws RuntimeException that wraps an InterruptedException if the wait is interrupted + */ + @Nullable + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Throwable blockingGet() { + BlockingMultiObserver observer = new BlockingMultiObserver(); + subscribe(observer); + return observer.blockingGetError(); + } + + /** + * Subscribes to this Completable instance and blocks until it terminates or the specified timeout + * elapses, then returns null for normal termination or the emitted exception if any. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingGet} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param timeout the timeout value + * @param unit the time unit + * @return the throwable if this terminated with an error, null otherwise + * @throws RuntimeException that wraps an InterruptedException if the wait is interrupted or + * TimeoutException if the specified timeout elapsed before it + */ + @Nullable + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Throwable blockingGet(long timeout, TimeUnit unit) { + ObjectHelper.requireNonNull(unit, "unit is null"); + BlockingMultiObserver observer = new BlockingMultiObserver(); + subscribe(observer); + return observer.blockingGetError(timeout, unit); + } + + /** + * Subscribes to this Completable only once, when the first CompletableObserver + * subscribes to the result Completable, caches its terminal event + * and relays/replays it to observers. + *

+ * + *

+ * Note that this operator doesn't allow disposing the connection + * of the upstream source. + *

+ *
Scheduler:
+ *
{@code cache} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.4 - experimental + * @return the new Completable instance + * @since 2.1 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable cache() { + return RxJavaPlugins.onAssembly(new CompletableCache(this)); + } + + /** + * Calls the given transformer function with this instance and returns the function's resulting + * Completable. + *

+ * + *

+ *
Scheduler:
+ *
{@code compose} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param transformer the transformer function, not null + * @return the Completable returned by the function + * @throws NullPointerException if transformer is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable compose(CompletableTransformer transformer) { + return wrap(ObjectHelper.requireNonNull(transformer, "transformer is null").apply(this)); + } + + /** + * Concatenates this Completable with another Completable. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param other the other Completable, not null + * @return the new Completable which subscribes to this and then the other Completable + * @throws NullPointerException if other is null + * @see #andThen(MaybeSource) + * @see #andThen(ObservableSource) + * @see #andThen(SingleSource) + * @see #andThen(Publisher) + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatWith(CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new CompletableAndThenCompletable(this, other)); + } + + /** + * Returns a Completable which delays the emission of the completion event by the given time. + *

+ * + *

+ *
Scheduler:
+ *
{@code delay} does operate by default on the {@code computation} {@link Scheduler}.
+ *
+ * @param delay the delay time + * @param unit the delay unit + * @return the new Completable instance + * @throws NullPointerException if unit is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Completable delay(long delay, TimeUnit unit) { + return delay(delay, unit, Schedulers.computation(), false); + } + + /** + * Returns a Completable which delays the emission of the completion event by the given time while + * running on the specified scheduler. + *

+ * + *

+ *
Scheduler:
+ *
{@code delay} operates on the {@link Scheduler} you specify.
+ *
+ * @param delay the delay time + * @param unit the delay unit + * @param scheduler the scheduler to run the delayed completion on + * @return the new Completable instance + * @throws NullPointerException if unit or scheduler is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable delay(long delay, TimeUnit unit, Scheduler scheduler) { + return delay(delay, unit, scheduler, false); + } + + /** + * Returns a Completable which delays the emission of the completion event, and optionally the error as well, by the given time while + * running on the specified scheduler. + *

+ * + *

+ *
Scheduler:
+ *
{@code delay} operates on the {@link Scheduler} you specify.
+ *
+ * @param delay the delay time + * @param unit the delay unit + * @param scheduler the scheduler to run the delayed completion on + * @param delayError delay the error emission as well? + * @return the new Completable instance + * @throws NullPointerException if unit or scheduler is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable delay(final long delay, final TimeUnit unit, final Scheduler scheduler, final boolean delayError) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new CompletableDelay(this, delay, unit, scheduler, delayError)); + } + + /** + * Returns a Completable that delays the subscription to the source CompletableSource by a given amount of time. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code delaySubscription} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay the time to delay the subscription + * @param unit the time unit of {@code delay} + * @return a Completable that delays the subscription to the source CompletableSource by the given amount + * @since 2.2.3 - experimental + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @Experimental + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Completable delaySubscription(long delay, TimeUnit unit) { + return delaySubscription(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Completable that delays the subscription to the source CompletableSource by a given amount of time, + * both waiting and subscribing on a given Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay the time to delay the subscription + * @param unit the time unit of {@code delay} + * @param scheduler the Scheduler on which the waiting and subscription will happen + * @return a Completable that delays the subscription to the source CompletableSource by a given + * amount, waiting and subscribing on the given Scheduler + * @since 2.2.3 - experimental + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @Experimental + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable delaySubscription(long delay, TimeUnit unit, Scheduler scheduler) { + return Completable.timer(delay, unit, scheduler).andThen(this); + } + + /** + * Returns a Completable which calls the given onComplete callback if this Completable completes. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnComplete} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onComplete the callback to call when this emits an onComplete event + * @return the new Completable instance + * @throws NullPointerException if onComplete is null + * @see #doFinally(Action) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable doOnComplete(Action onComplete) { + return doOnLifecycle(Functions.emptyConsumer(), Functions.emptyConsumer(), + onComplete, Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, Functions.EMPTY_ACTION); + } + + /** + * Calls the shared {@code Action} if a CompletableObserver subscribed to the current + * Completable disposes the common Disposable it received via onSubscribe. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnDispose} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onDispose the action to call when the child subscriber disposes the subscription + * @return the new Completable instance + * @throws NullPointerException if onDispose is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable doOnDispose(Action onDispose) { + return doOnLifecycle(Functions.emptyConsumer(), Functions.emptyConsumer(), + Functions.EMPTY_ACTION, Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, onDispose); + } + + /** + * Returns a Completable which calls the given onError callback if this Completable emits an error. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onError the error callback + * @return the new Completable instance + * @throws NullPointerException if onError is null + * @see #doFinally(Action) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable doOnError(Consumer onError) { + return doOnLifecycle(Functions.emptyConsumer(), onError, + Functions.EMPTY_ACTION, Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, Functions.EMPTY_ACTION); + } + + /** + * Returns a Completable which calls the given onEvent callback with the (throwable) for an onError + * or (null) for an onComplete signal from this Completable before delivering said signal to the downstream. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnEvent} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onEvent the event callback + * @return the new Completable instance + * @throws NullPointerException if onEvent is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable doOnEvent(final Consumer onEvent) { + ObjectHelper.requireNonNull(onEvent, "onEvent is null"); + return RxJavaPlugins.onAssembly(new CompletableDoOnEvent(this, onEvent)); + } + + /** + * Returns a Completable instance that calls the various callbacks on the specific + * lifecycle events. + *
+ *
Scheduler:
+ *
{@code doOnLifecycle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onSubscribe the consumer called when a CompletableSubscriber subscribes. + * @param onError the consumer called when this emits an onError event + * @param onComplete the runnable called just before when this Completable completes normally + * @param onAfterTerminate the runnable called after this Completable completes normally + * @param onDispose the runnable called when the child disposes the subscription + * @return the new Completable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + private Completable doOnLifecycle( + final Consumer onSubscribe, + final Consumer onError, + final Action onComplete, + final Action onTerminate, + final Action onAfterTerminate, + final Action onDispose) { + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + ObjectHelper.requireNonNull(onTerminate, "onTerminate is null"); + ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"); + ObjectHelper.requireNonNull(onDispose, "onDispose is null"); + return RxJavaPlugins.onAssembly(new CompletablePeek(this, onSubscribe, onError, onComplete, onTerminate, onAfterTerminate, onDispose)); + } + + /** + * Returns a Completable instance that calls the given onSubscribe callback with the disposable + * that child subscribers receive on subscription. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onSubscribe the callback called when a child subscriber subscribes + * @return the new Completable instance + * @throws NullPointerException if onSubscribe is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable doOnSubscribe(Consumer onSubscribe) { + return doOnLifecycle(onSubscribe, Functions.emptyConsumer(), + Functions.EMPTY_ACTION, Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, Functions.EMPTY_ACTION); + } + + /** + * Returns a Completable instance that calls the given onTerminate callback just before this Completable + * completes normally or with an exception. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onTerminate the callback to call just before this Completable terminates + * @return the new Completable instance + * @see #doFinally(Action) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable doOnTerminate(final Action onTerminate) { + return doOnLifecycle(Functions.emptyConsumer(), Functions.emptyConsumer(), + Functions.EMPTY_ACTION, onTerminate, + Functions.EMPTY_ACTION, Functions.EMPTY_ACTION); + } + + /** + * Returns a Completable instance that calls the given onTerminate callback after this Completable + * completes normally or with an exception. + *

+ * + *

+ *
Scheduler:
+ *
{@code doAfterTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onAfterTerminate the callback to call after this Completable terminates + * @return the new Completable instance + * @see #doFinally(Action) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable doAfterTerminate(final Action onAfterTerminate) { + return doOnLifecycle( + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, + onAfterTerminate, + Functions.EMPTY_ACTION); + } + /** + * Calls the specified action after this Completable signals onError or onComplete or gets disposed by + * the downstream. + *

+ * + *

+ * In case of a race between a terminal event and a dispose call, the provided {@code onFinally} action + * is executed once per subscription. + *

+ * Note that the {@code onFinally} action is shared between subscriptions and as such + * should be thread-safe. + *

+ *
Scheduler:
+ *
{@code doFinally} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.1 - experimental + * @param onFinally the action called when this Completable terminates or gets disposed + * @return the new Completable instance + * @since 2.1 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable doFinally(Action onFinally) { + ObjectHelper.requireNonNull(onFinally, "onFinally is null"); + return RxJavaPlugins.onAssembly(new CompletableDoFinally(this, onFinally)); + } + + /** + * This method requires advanced knowledge about building operators, please consider + * other standard composition methods first; + * Returns a {@code Completable} which, when subscribed to, invokes the {@link CompletableOperator#apply(CompletableObserver) apply(CompletableObserver)} method + * of the provided {@link CompletableOperator} for each individual downstream {@link Completable} and allows the + * insertion of a custom operator by accessing the downstream's {@link CompletableObserver} during this subscription phase + * and providing a new {@code CompletableObserver}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + *

+ * + *

+ * Generally, such a new {@code CompletableObserver} will wrap the downstream's {@code CompletableObserver} and forwards the + * {@code onError} and {@code onComplete} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code dispose} and {@code isDisposed} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. + *

+ * Example: + *


+     * // Step 1: Create the consumer type that will be returned by the CompletableOperator.apply():
+     * 
+     * public final class CustomCompletableObserver implements CompletableObserver, Disposable {
+     *
+     *     // The downstream's CompletableObserver that will receive the onXXX events
+     *     final CompletableObserver downstream;
+     *
+     *     // The connection to the upstream source that will call this class' onXXX methods
+     *     Disposable upstream;
+     *
+     *     // The constructor takes the downstream subscriber and usually any other parameters
+     *     public CustomCompletableObserver(CompletableObserver downstream) {
+     *         this.downstream = downstream;
+     *     }
+     *
+     *     // In the subscription phase, the upstream sends a Disposable to this class
+     *     // and subsequently this class has to send a Disposable to the downstream.
+     *     // Note that relaying the upstream's Disposable directly is not allowed in RxJava
+     *     @Override
+     *     public void onSubscribe(Disposable d) {
+     *         if (upstream != null) {
+     *             d.dispose();
+     *         } else {
+     *             upstream = d;
+     *             downstream.onSubscribe(this);
+     *         }
+     *     }
+     *
+     *     // Some operators may handle the upstream's error while others
+     *     // could just forward it to the downstream.
+     *     @Override
+     *     public void onError(Throwable throwable) {
+     *         downstream.onError(throwable);
+     *     }
+     *
+     *     // When the upstream completes, usually the downstream should complete as well.
+     *     // In completable, this could also mean doing some side-effects
+     *     @Override
+     *     public void onComplete() {
+     *         System.out.println("Sequence completed");
+     *         downstream.onComplete();
+     *     }
+     *
+     *     // Some operators may use their own resources which should be cleaned up if
+     *     // the downstream disposes the flow before it completed. Operators without
+     *     // resources can simply forward the dispose to the upstream.
+     *     // In some cases, a disposed flag may be set by this method so that other parts
+     *     // of this class may detect the dispose and stop sending events
+     *     // to the downstream.
+     *     @Override
+     *     public void dispose() {
+     *         upstream.dispose();
+     *     }
+     *
+     *     // Some operators may simply forward the call to the upstream while others
+     *     // can return the disposed flag set in dispose().
+     *     @Override
+     *     public boolean isDisposed() {
+     *         return upstream.isDisposed();
+     *     }
+     * }
+     *
+     * // Step 2: Create a class that implements the CompletableOperator interface and
+     * //         returns the custom consumer type from above in its apply() method.
+     * //         Such class may define additional parameters to be submitted to
+     * //         the custom consumer type.
+     *
+     * final class CustomCompletableOperator implements CompletableOperator {
+     *     @Override
+     *     public CompletableObserver apply(CompletableObserver upstream) {
+     *         return new CustomCompletableObserver(upstream);
+     *     }
+     * }
+     *
+     * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it
+     * //         or reusing an existing one.
+     *
+     * Completable.complete()
+     * .lift(new CustomCompletableOperator())
+     * .test()
+     * .assertResult();
+     * 
+ *

+ * Creating custom operators can be complicated and it is recommended one consults the + * RxJava wiki: Writing operators page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. + *

+ * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Completable} + * class and creating a {@link CompletableTransformer} with it is recommended. + *

+ * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code CompletableObserver} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Completable}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return a {@code CompletableObserver} that should immediately dispose the upstream's {@code Disposable} in its + * {@code onSubscribe} method. Again, using a {@code CompletableTransformer} and extending the {@code Completable} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. + *

+ *
Scheduler:
+ *
{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link CompletableOperator} may use a {@code Scheduler} to support its own asynchronous behavior.
+ *
+ * + * @param onLift the {@link CompletableOperator} that receives the downstream's {@code CompletableObserver} and should return + * a {@code CompletableObserver} with custom behavior to be used as the consumer for the current + * {@code Completable}. + * @return the new Completable instance + * @see RxJava wiki: Writing operators + * @see #compose(CompletableTransformer) + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable lift(final CompletableOperator onLift) { + ObjectHelper.requireNonNull(onLift, "onLift is null"); + return RxJavaPlugins.onAssembly(new CompletableLift(this, onLift)); + } + + /** + * Maps the signal types of this Completable into a {@link Notification} of the same kind + * and emits it as a single success value to downstream. + *

+ * + *

+ *
Scheduler:
+ *
{@code materialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the intended target element type of the notification + * @return the new Single instance + * @since 2.2.4 - experimental + * @see Single#dematerialize(Function) + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> materialize() { + return RxJavaPlugins.onAssembly(new CompletableMaterialize(this)); + } + + /** + * Returns a Completable which subscribes to this and the other Completable and completes + * when both of them complete or one emits an error. + *

+ * + *

+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param other the other Completable instance + * @return the new Completable instance + * @throws NullPointerException if other is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable mergeWith(CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return mergeArray(this, other); + } + + /** + * Returns a Completable which emits the terminal events from the thread of the specified scheduler. + *

+ * + *

+ *
Scheduler:
+ *
{@code observeOn} operates on a {@link Scheduler} you specify.
+ *
+ * @param scheduler the scheduler to emit terminal events on + * @return the new Completable instance + * @throws NullPointerException if scheduler is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable observeOn(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new CompletableObserveOn(this, scheduler)); + } + + /** + * Returns a Completable instance that if this Completable emits an error, it will emit an onComplete + * and swallow the throwable. + *

+ * + *

+ *
Scheduler:
+ *
{@code onErrorComplete} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Completable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable onErrorComplete() { + return onErrorComplete(Functions.alwaysTrue()); + } + + /** + * Returns a Completable instance that if this Completable emits an error and the predicate returns + * true, it will emit an onComplete and swallow the throwable. + *

+ * + *

+ *
Scheduler:
+ *
{@code onErrorComplete} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param predicate the predicate to call when an Throwable is emitted which should return true + * if the Throwable should be swallowed and replaced with an onComplete. + * @return the new Completable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable onErrorComplete(final Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + + return RxJavaPlugins.onAssembly(new CompletableOnErrorComplete(this, predicate)); + } + + /** + * Returns a Completable instance that when encounters an error from this Completable, calls the + * specified mapper function that returns another Completable instance for it and resumes the + * execution with it. + *

+ * + *

+ *
Scheduler:
+ *
{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param errorMapper the mapper function that takes the error and should return a Completable as + * continuation. + * @return the new Completable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable onErrorResumeNext(final Function errorMapper) { + ObjectHelper.requireNonNull(errorMapper, "errorMapper is null"); + return RxJavaPlugins.onAssembly(new CompletableResumeNext(this, errorMapper)); + } + + /** + * Nulls out references to the upstream producer and downstream CompletableObserver if + * the sequence is terminated or downstream calls dispose(). + *

+ * + *

+ *
Scheduler:
+ *
{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.5 - experimental + * @return a Completable which nulls out references to the upstream producer and downstream CompletableObserver if + * the sequence is terminated or downstream calls dispose() + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable onTerminateDetach() { + return RxJavaPlugins.onAssembly(new CompletableDetach(this)); + } + + /** + * Returns a Completable that repeatedly subscribes to this Completable until disposed. + *

+ * + *

+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Completable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable repeat() { + return fromPublisher(toFlowable().repeat()); + } + + /** + * Returns a Completable that subscribes repeatedly at most the given times to this Completable. + *

+ * + *

+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param times the number of times the resubscription should happen + * @return the new Completable instance + * @throws IllegalArgumentException if times is less than zero + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable repeat(long times) { + return fromPublisher(toFlowable().repeat(times)); + } + + /** + * Returns a Completable that repeatedly subscribes to this Completable so long as the given + * stop supplier returns false. + *

+ * + *

+ *
Scheduler:
+ *
{@code repeatUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param stop the supplier that should return true to stop resubscribing. + * @return the new Completable instance + * @throws NullPointerException if stop is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable repeatUntil(BooleanSupplier stop) { + return fromPublisher(toFlowable().repeatUntil(stop)); + } + + /** + * Returns a Completable instance that repeats when the Publisher returned by the handler + * emits an item or completes when this Publisher emits a completed event. + *

+ * + *

+ *
Scheduler:
+ *
{@code repeatWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param handler the function that transforms the stream of values indicating the completion of + * this Completable and returns a Publisher that emits items for repeating or completes to indicate the + * repetition should stop + * @return the new Completable instance + * @throws NullPointerException if stop is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable repeatWhen(Function, ? extends Publisher> handler) { + return fromPublisher(toFlowable().repeatWhen(handler)); + } + + /** + * Returns a Completable that retries this Completable as long as it emits an onError event. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Completable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable retry() { + return fromPublisher(toFlowable().retry()); + } + + /** + * Returns a Completable that retries this Completable in case of an error as long as the predicate + * returns true. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param predicate the predicate called when this emits an error with the repeat count and the latest exception + * and should return true to retry. + * @return the new Completable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable retry(BiPredicate predicate) { + return fromPublisher(toFlowable().retry(predicate)); + } + + /** + * Returns a Completable that when this Completable emits an error, retries at most the given + * number of times before giving up and emitting the last error. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param times the number of times to resubscribe if the current Completable fails + * @return the new Completable instance + * @throws IllegalArgumentException if times is negative + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable retry(long times) { + return fromPublisher(toFlowable().retry(times)); + } + + /** + * Returns a Completable that when this Completable emits an error, retries at most times + * or until the predicate returns false, whichever happens first and emitting the last error. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.8 - experimental + * @param times the number of times to resubscribe if the current Completable fails + * @param predicate the predicate that is called with the latest throwable and should return + * true to indicate the returned Completable should resubscribe to this Completable. + * @return the new Completable instance + * @throws NullPointerException if predicate is null + * @throws IllegalArgumentException if times is negative + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable retry(long times, Predicate predicate) { + return fromPublisher(toFlowable().retry(times, predicate)); + } + + /** + * Returns a Completable that when this Completable emits an error, calls the given predicate with + * the latest exception to decide whether to resubscribe to this or not. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param predicate the predicate that is called with the latest throwable and should return + * true to indicate the returned Completable should resubscribe to this Completable. + * @return the new Completable instance + * @throws NullPointerException if predicate is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable retry(Predicate predicate) { + return fromPublisher(toFlowable().retry(predicate)); + } + + /** + * Returns a Completable which given a Publisher and when this Completable emits an error, delivers + * that error through a Flowable and the Publisher should signal a value indicating a retry in response + * or a terminal event indicating a termination. + *

+ * + *

+ * Note that the inner {@code Publisher} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signalling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code Publisher} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + *

+ * The following example demonstrates how to retry an asynchronous source with a delay: + *


+     * Completable.timer(1, TimeUnit.SECONDS)
+     *     .doOnSubscribe(s -> System.out.println("subscribing"))
+     *     .doOnComplete(() -> { throw new RuntimeException(); })
+     *     .retryWhen(errors -> {
+     *         AtomicInteger counter = new AtomicInteger();
+     *         return errors
+     *                   .takeWhile(e -> counter.getAndIncrement() != 3)
+     *                   .flatMap(e -> {
+     *                       System.out.println("delay retry by " + counter.get() + " second(s)");
+     *                       return Flowable.timer(counter.get(), TimeUnit.SECONDS);
+     *                   });
+     *     })
+     *     .blockingAwait();
+     * 
+ *
+ *
Scheduler:
+ *
{@code retryWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param handler the handler that receives a Flowable delivering Throwables and should return a Publisher that + * emits items to indicate retries or emits terminal events to indicate termination. + * @return the new Completable instance + * @throws NullPointerException if handler is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable retryWhen(Function, ? extends Publisher> handler) { + return fromPublisher(toFlowable().retryWhen(handler)); + } + + /** + * Returns a Completable which first runs the other Completable + * then this completable if the other completed normally. + *

+ * + *

+ *
Scheduler:
+ *
{@code startWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param other the other completable to run first + * @return the new Completable instance + * @throws NullPointerException if other is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable startWith(CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return concatArray(other, this); + } + + /** + * Returns an Observable which first delivers the events + * of the other Observable then runs this CompletableConsumable. + *

+ * + *

+ *
Scheduler:
+ *
{@code startWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param other the other Observable to run first + * @return the new Observable instance + * @throws NullPointerException if other is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable startWith(Observable other) { + ObjectHelper.requireNonNull(other, "other is null"); + return other.concatWith(this.toObservable()); + } + /** + * Returns a Flowable which first delivers the events + * of the other Publisher then runs this Completable. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer + * and expects the other {@code Publisher} to honor it as well.
+ *
Scheduler:
+ *
{@code startWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param other the other Publisher to run first + * @return the new Flowable instance + * @throws NullPointerException if other is null + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable startWith(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return this.toFlowable().startWith(other); + } + + /** + * Hides the identity of this Completable and its Disposable. + *

+ * + *

+ * Allows preventing certain identity-based optimizations (fusion). + *

+ *
Scheduler:
+ *
{@code hide} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.5 - experimental + * @return the new Completable instance + * @since 2.1 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable hide() { + return RxJavaPlugins.onAssembly(new CompletableHide(this)); + } + + /** + * Subscribes to this CompletableConsumable and returns a Disposable which can be used to dispose + * the subscription. + *

+ * + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the Disposable that allows disposing the subscription + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe() { + EmptyCompletableObserver observer = new EmptyCompletableObserver(); + subscribe(observer); + return observer; + } + + @SchedulerSupport(SchedulerSupport.NONE) + @Override + public final void subscribe(CompletableObserver observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); + try { + + observer = RxJavaPlugins.onSubscribe(this, observer); + + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null CompletableObserver. Please check the handler provided to RxJavaPlugins.setOnCompletableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); + + subscribeActual(observer); + } catch (NullPointerException ex) { // NOPMD + throw ex; + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + throw toNpe(ex); + } + } + + /** + * Implement this method to handle the incoming {@link CompletableObserver}s and + * perform the business logic in your operator. + *

There is no need to call any of the plugin hooks on the current {@code Completable} instance or + * the {@code CompletableObserver}; all hooks and basic safeguards have been + * applied by {@link #subscribe(CompletableObserver)} before this method gets called. + * @param observer the CompletableObserver instance, never null + */ + protected abstract void subscribeActual(CompletableObserver observer); + + /** + * Subscribes a given CompletableObserver (subclass) to this Completable and returns the given + * CompletableObserver as is. + *

+ * + *

Usage example: + *


+     * Completable source = Completable.complete().delay(1, TimeUnit.SECONDS);
+     * CompositeDisposable composite = new CompositeDisposable();
+     *
+     * DisposableCompletableObserver ds = new DisposableCompletableObserver() {
+     *     // ...
+     * };
+     *
+     * composite.add(source.subscribeWith(ds));
+     * 
+ *
+ *
Scheduler:
+ *
{@code subscribeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the type of the CompletableObserver to use and return + * @param observer the CompletableObserver (subclass) to use and return, not null + * @return the input {@code observer} + * @throws NullPointerException if {@code observer} is null + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final E subscribeWith(E observer) { + subscribe(observer); + return observer; + } + + /** + * Subscribes to this Completable and calls back either the onError or onComplete functions. + *

+ * + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onComplete the runnable that is called if the Completable completes normally + * @param onError the consumer that is called if this Completable emits an error + * @return the Disposable that can be used for disposing the subscription asynchronously + * @throws NullPointerException if either callback is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(final Action onComplete, final Consumer onError) { + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + + CallbackCompletableObserver observer = new CallbackCompletableObserver(onError, onComplete); + subscribe(observer); + return observer; + } + + /** + * Subscribes to this Completable and calls the given Action when this Completable + * completes normally. + *

+ * + *

+ * If the Completable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onComplete the runnable called when this Completable completes normally + * @return the Disposable that allows disposing the subscription + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(final Action onComplete) { + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + + CallbackCompletableObserver observer = new CallbackCompletableObserver(onComplete); + subscribe(observer); + return observer; + } + + /** + * Returns a Completable which subscribes the child subscriber on the specified scheduler, making + * sure the subscription side-effects happen on that specific thread of the scheduler. + *

+ * + *

+ *
Scheduler:
+ *
{@code subscribeOn} operates on a {@link Scheduler} you specify.
+ *
+ * @param scheduler the Scheduler to subscribe on + * @return the new Completable instance + * @throws NullPointerException if scheduler is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable subscribeOn(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + + return RxJavaPlugins.onAssembly(new CompletableSubscribeOn(this, scheduler)); + } + + /** + * Terminates the downstream if this or the other {@code Completable} + * terminates (wins the termination race) while disposing the connection to the losing source. + *

+ * + *

+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If both this and the other sources signal an error, only one of the errors + * is signaled to the downstream and the other error is signaled to the global + * error handler via {@link RxJavaPlugins#onError(Throwable)}.
+ *
+ *

History: 2.1.17 - experimental + * @param other the other completable source to observe for the terminal signals + * @return the new Completable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable takeUntil(CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + + return RxJavaPlugins.onAssembly(new CompletableTakeUntilCompletable(this, other)); + } + + /** + * Returns a Completable that runs this Completable and emits a TimeoutException in case + * this Completable doesn't complete within the given time. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeout} signals the TimeoutException on the {@code computation} {@link Scheduler}.
+ *
+ * @param timeout the timeout value + * @param unit the timeout unit + * @return the new Completable instance + * @throws NullPointerException if unit is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Completable timeout(long timeout, TimeUnit unit) { + return timeout0(timeout, unit, Schedulers.computation(), null); + } + + /** + * Returns a Completable that runs this Completable and switches to the other Completable + * in case this Completable doesn't complete within the given time. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeout} subscribes to the other CompletableSource on + * the {@code computation} {@link Scheduler}.
+ *
+ * @param timeout the timeout value + * @param unit the timeout unit + * @param other the other Completable instance to switch to in case of a timeout + * @return the new Completable instance + * @throws NullPointerException if unit or other is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Completable timeout(long timeout, TimeUnit unit, CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(timeout, unit, Schedulers.computation(), other); + } + + /** + * Returns a Completable that runs this Completable and emits a TimeoutException in case + * this Completable doesn't complete within the given time while "waiting" on the specified + * Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeout} signals the TimeoutException on the {@link Scheduler} you specify.
+ *
+ * @param timeout the timeout value + * @param unit the timeout unit + * @param scheduler the scheduler to use to wait for completion + * @return the new Completable instance + * @throws NullPointerException if unit or scheduler is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable timeout(long timeout, TimeUnit unit, Scheduler scheduler) { + return timeout0(timeout, unit, scheduler, null); + } + + /** + * Returns a Completable that runs this Completable and switches to the other Completable + * in case this Completable doesn't complete within the given time while "waiting" on + * the specified scheduler. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeout} subscribes to the other CompletableSource on + * the {@link Scheduler} you specify.
+ *
+ * @param timeout the timeout value + * @param unit the timeout unit + * @param scheduler the scheduler to use to wait for completion + * @param other the other Completable instance to switch to in case of a timeout + * @return the new Completable instance + * @throws NullPointerException if unit, scheduler or other is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable timeout(long timeout, TimeUnit unit, Scheduler scheduler, CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(timeout, unit, scheduler, other); + } + + /** + * Returns a Completable that runs this Completable and optionally switches to the other Completable + * in case this Completable doesn't complete within the given time while "waiting" on + * the specified scheduler. + *
+ *
Scheduler:
+ *
You specify the {@link Scheduler} this operator runs on.
+ *
+ * @param timeout the timeout value + * @param unit the timeout unit + * @param scheduler the scheduler to use to wait for completion + * @param other the other Completable instance to switch to in case of a timeout, + * if null a TimeoutException is emitted instead + * @return the new Completable instance + * @throws NullPointerException if unit or scheduler + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + private Completable timeout0(long timeout, TimeUnit unit, Scheduler scheduler, CompletableSource other) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new CompletableTimeout(this, timeout, unit, scheduler, other)); + } + + /** + * Allows fluent conversion to another type via a function callback. + *

+ * + *

+ *
Scheduler:
+ *
{@code to} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the output type + * @param converter the function called with this which should return some other value. + * @return the converted value + * @throws NullPointerException if converter is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final U to(Function converter) { + try { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Returns a Flowable which when subscribed to subscribes to this Completable and + * relays the terminal events to the subscriber. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code toFlowable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @return the new Flowable instance + */ + @CheckReturnValue + @SuppressWarnings("unchecked") + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable toFlowable() { + if (this instanceof FuseToFlowable) { + return ((FuseToFlowable)this).fuseToFlowable(); + } + return RxJavaPlugins.onAssembly(new CompletableToFlowable(this)); + } + + /** + * Converts this Completable into a {@link Maybe}. + *

+ * + *

+ *
Scheduler:
+ *
{@code toMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type + * @return a {@link Maybe} that only calls {@code onComplete} or {@code onError}, based on which one is + * called by the source Completable. + */ + @CheckReturnValue + @SuppressWarnings("unchecked") + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe toMaybe() { + if (this instanceof FuseToMaybe) { + return ((FuseToMaybe)this).fuseToMaybe(); + } + return RxJavaPlugins.onAssembly(new MaybeFromCompletable(this)); + } + + /** + * Returns an Observable which when subscribed to subscribes to this Completable and + * relays the terminal events to the subscriber. + *

+ * + *

+ *
Scheduler:
+ *
{@code toObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @return the new Observable created + */ + @CheckReturnValue + @SuppressWarnings("unchecked") + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable toObservable() { + if (this instanceof FuseToObservable) { + return ((FuseToObservable)this).fuseToObservable(); + } + return RxJavaPlugins.onAssembly(new CompletableToObservable(this)); + } + + /** + * Converts this Completable into a Single which when this Completable completes normally, + * calls the given supplier and emits its returned value through onSuccess. + *

+ * + *

+ *
Scheduler:
+ *
{@code toSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param completionValueSupplier the value supplier called when this Completable completes normally + * @return the new Single instance + * @throws NullPointerException if completionValueSupplier is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single toSingle(final Callable completionValueSupplier) { + ObjectHelper.requireNonNull(completionValueSupplier, "completionValueSupplier is null"); + return RxJavaPlugins.onAssembly(new CompletableToSingle(this, completionValueSupplier, null)); + } + + /** + * Converts this Completable into a Single which when this Completable completes normally, + * emits the given value through onSuccess. + *

+ * + *

+ *
Scheduler:
+ *
{@code toSingleDefault} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param completionValue the value to emit when this Completable completes normally + * @return the new Single instance + * @throws NullPointerException if completionValue is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single toSingleDefault(final T completionValue) { + ObjectHelper.requireNonNull(completionValue, "completionValue is null"); + return RxJavaPlugins.onAssembly(new CompletableToSingle(this, null, completionValue)); + } + + /** + * Returns a Completable which makes sure when a subscriber disposes the subscription, the + * dispose is called on the specified scheduler. + *

+ * + *

+ *
Scheduler:
+ *
{@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.
+ *
+ * @param scheduler the target scheduler where to execute the disposing + * @return the new Completable instance + * @throws NullPointerException if scheduler is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Completable unsubscribeOn(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new CompletableDisposeOn(this, scheduler)); + } + // ------------------------------------------------------------------------- + // Fluent test support, super handy and reduces test preparation boilerplate + // ------------------------------------------------------------------------- + + /** + * Creates a TestObserver and subscribes + * it to this Completable. + *

+ * + *

+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new TestObserver instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final TestObserver test() { + TestObserver to = new TestObserver(); + subscribe(to); + return to; + } + + /** + * Creates a TestObserver optionally in cancelled state, then subscribes it to this Completable. + * @param cancelled if true, the TestObserver will be cancelled before subscribing to this + * Completable. + *

+ * + *

+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new TestObserver instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final TestObserver test(boolean cancelled) { + TestObserver to = new TestObserver(); + + if (cancelled) { + to.cancel(); + } + subscribe(to); + return to; + } +} diff --git a/src/main/java/io/reactivex/CompletableConverter.java b/src/main/java/io/reactivex/CompletableConverter.java new file mode 100755 index 0000000..1bea863 --- /dev/null +++ b/src/main/java/io/reactivex/CompletableConverter.java @@ -0,0 +1,34 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Completable#as} operator to turn a Completable into another + * value fluently. + *

History: 2.1.7 - experimental + * @param the output type + * @since 2.2 + */ +public interface CompletableConverter { + /** + * Applies a function to the upstream Completable and returns a converted value of type {@code R}. + * + * @param upstream the upstream Completable instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Completable upstream); +} diff --git a/src/main/java/io/reactivex/CompletableEmitter.java b/src/main/java/io/reactivex/CompletableEmitter.java new file mode 100755 index 0000000..ffbd9ba --- /dev/null +++ b/src/main/java/io/reactivex/CompletableEmitter.java @@ -0,0 +1,98 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Cancellable; + +/** + * Abstraction over an RxJava {@link CompletableObserver} that allows associating + * a resource with it. + *

+ * All methods are safe to call from multiple threads, but note that there is no guarantee + * whose terminal event will win and get delivered to the downstream. + *

+ * Calling {@link #onComplete()} multiple times has no effect. + * Calling {@link #onError(Throwable)} multiple times or after {@code onComplete} will route the + * exception into the global error handler via {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + *

+ * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls + * {@link #onError(Throwable)}, {@link #onComplete()} or when {@link #tryOnError(Throwable)} succeeds. + *

+ * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + *

+ * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + */ +public interface CompletableEmitter { + + /** + * Signal the completion. + */ + void onComplete(); + + /** + * Signal an exception. + * @param t the exception, not null + */ + void onError(@NonNull Throwable t); + + /** + * Sets a Disposable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param d the disposable, null is allowed + */ + void setDisposable(@Nullable Disposable d); + + /** + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param c the cancellable resource, null is allowed + */ + void setCancellable(@Nullable Cancellable c); + + /** + * Returns true if the downstream disposed the sequence or the + * emitter was terminated via {@link #onError(Throwable)}, + * {@link #onComplete} or a successful {@link #tryOnError(Throwable)}. + *

This method is thread-safe. + * @return true if the downstream disposed the sequence or the emitter was terminated + */ + boolean isDisposed(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

+ * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); +} diff --git a/src/main/java/io/reactivex/CompletableObserver.java b/src/main/java/io/reactivex/CompletableObserver.java new file mode 100755 index 0000000..eac7c94 --- /dev/null +++ b/src/main/java/io/reactivex/CompletableObserver.java @@ -0,0 +1,68 @@ +/** + * 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; + +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; + +/** + * Provides a mechanism for receiving push-based notification of a valueless completion or an error. + *

+ * When a {@code CompletableObserver} is subscribed to a {@link CompletableSource} through the {@link CompletableSource#subscribe(CompletableObserver)} method, + * the {@code CompletableSource} calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows + * disposing the sequence at any time. A well-behaved + * {@code CompletableSource} will call a {@code CompletableObserver}'s {@link #onError(Throwable)} + * or {@link #onComplete()} method exactly once as they are considered mutually exclusive terminal signals. + *

+ * Calling the {@code CompletableObserver}'s method must happen in a serialized fashion, that is, they must not + * be invoked concurrently by multiple threads in an overlapping fashion and the invocation pattern must + * adhere to the following protocol: + *

    onSubscribe (onError | onComplete)?
+ *

+ * Subscribing a {@code CompletableObserver} to multiple {@code CompletableSource}s is not recommended. If such reuse + * happens, it is the duty of the {@code CompletableObserver} implementation to be ready to receive multiple calls to + * its methods and ensure proper concurrent behavior of its business logic. + *

+ * Calling {@link #onSubscribe(Disposable)} or {@link #onError(Throwable)} with a + * {@code null} argument is forbidden. + *

+ * The implementations of the {@code onXXX} methods should avoid throwing runtime exceptions other than the following cases: + *

    + *
  • If the argument is {@code null}, the methods can throw a {@code NullPointerException}. + * Note though that RxJava prevents {@code null}s to enter into the flow and thus there is generally no + * need to check for nulls in flows assembled from standard sources and intermediate operators. + *
  • + *
  • If there is a fatal error (such as {@code VirtualMachineError}).
  • + *
+ * @since 2.0 + */ +public interface CompletableObserver { + /** + * Called once by the Completable to set a Disposable on this instance which + * then can be used to cancel the subscription at any time. + * @param d the Disposable instance to call dispose on for cancellation, not null + */ + void onSubscribe(@NonNull Disposable d); + + /** + * Called once the deferred computation completes normally. + */ + void onComplete(); + + /** + * Called once if the deferred computation 'throws' an exception. + * @param e the exception, not null. + */ + void onError(@NonNull Throwable e); +} diff --git a/src/main/java/io/reactivex/CompletableOnSubscribe.java b/src/main/java/io/reactivex/CompletableOnSubscribe.java new file mode 100755 index 0000000..0610a9b --- /dev/null +++ b/src/main/java/io/reactivex/CompletableOnSubscribe.java @@ -0,0 +1,31 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * A functional interface that has a {@code subscribe()} method that receives + * an instance of a {@link CompletableEmitter} instance that allows pushing + * an event in a cancellation-safe manner. + */ +public interface CompletableOnSubscribe { + + /** + * Called for each CompletableObserver that subscribes. + * @param emitter the safe emitter instance, never null + * @throws Exception on error + */ + void subscribe(@NonNull CompletableEmitter emitter) throws Exception; +} + diff --git a/src/main/java/io/reactivex/CompletableOperator.java b/src/main/java/io/reactivex/CompletableOperator.java new file mode 100755 index 0000000..749e41f --- /dev/null +++ b/src/main/java/io/reactivex/CompletableOperator.java @@ -0,0 +1,30 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Interface to map/wrap a downstream observer to an upstream observer. + */ +public interface CompletableOperator { + /** + * Applies a function to the child CompletableObserver and returns a new parent CompletableObserver. + * @param observer the child CompletableObservable instance + * @return the parent CompletableObserver instance + * @throws Exception on failure + */ + @NonNull + CompletableObserver apply(@NonNull CompletableObserver observer) throws Exception; +} diff --git a/src/main/java/io/reactivex/CompletableSource.java b/src/main/java/io/reactivex/CompletableSource.java new file mode 100755 index 0000000..145b040 --- /dev/null +++ b/src/main/java/io/reactivex/CompletableSource.java @@ -0,0 +1,31 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Represents a basic {@link Completable} source base interface, + * consumable via an {@link CompletableObserver}. + * + * @since 2.0 + */ +public interface CompletableSource { + + /** + * Subscribes the given CompletableObserver to this CompletableSource instance. + * @param co the CompletableObserver, not null + * @throws NullPointerException if {@code co} is null + */ + void subscribe(@NonNull CompletableObserver co); +} diff --git a/src/main/java/io/reactivex/CompletableTransformer.java b/src/main/java/io/reactivex/CompletableTransformer.java new file mode 100755 index 0000000..f656991 --- /dev/null +++ b/src/main/java/io/reactivex/CompletableTransformer.java @@ -0,0 +1,30 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the compose operator to turn a Completable into another + * Completable fluently. + */ +public interface CompletableTransformer { + /** + * Applies a function to the upstream Completable and returns a CompletableSource. + * @param upstream the upstream Completable instance + * @return the transformed CompletableSource instance + */ + @NonNull + CompletableSource apply(@NonNull Completable upstream); +} diff --git a/src/main/java/io/reactivex/Emitter.java b/src/main/java/io/reactivex/Emitter.java new file mode 100755 index 0000000..0d95e80 --- /dev/null +++ b/src/main/java/io/reactivex/Emitter.java @@ -0,0 +1,46 @@ +/** + * 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; + +import io.reactivex.annotations.NonNull; + +/** + * Base interface for emitting signals in a push-fashion in various generator-like source + * operators (create, generate). + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently. Calling them from multiple threads is not supported and leads to an + * undefined behavior. + * + * @param the value type emitted + */ +public interface Emitter { + + /** + * Signal a normal value. + * @param value the value to signal, not null + */ + void onNext(@NonNull T value); + + /** + * Signal a Throwable exception. + * @param error the Throwable to signal, not null + */ + void onError(@NonNull Throwable error); + + /** + * Signal a completion. + */ + void onComplete(); +} diff --git a/src/main/java/io/reactivex/Flowable.java b/src/main/java/io/reactivex/Flowable.java new file mode 100755 index 0000000..5c040c7 --- /dev/null +++ b/src/main/java/io/reactivex/Flowable.java @@ -0,0 +1,18692 @@ +/** + * 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; + +import java.util.*; +import java.util.concurrent.*; + +import org.reactivestreams.*; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.flowables.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.operators.flowable.*; +import io.reactivex.internal.operators.mixed.*; +import io.reactivex.internal.operators.observable.*; +import io.reactivex.internal.schedulers.ImmediateThinScheduler; +import io.reactivex.internal.subscribers.*; +import io.reactivex.internal.util.*; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.*; +import io.reactivex.subscribers.*; + +/** + * The Flowable class that implements the Reactive Streams + * Pattern and offers factory methods, intermediate operators and the ability to consume reactive dataflows. + *

+ * Reactive Streams operates with {@link Publisher}s which {@code Flowable} extends. Many operators + * therefore accept general {@code Publisher}s directly and allow direct interoperation with other + * Reactive Streams implementations. + *

+ * The Flowable hosts the default buffer size of 128 elements for operators, accessible via {@link #bufferSize()}, + * that can be overridden globally via the system parameter {@code rx2.buffer-size}. Most operators, however, have + * overloads that allow setting their internal buffer size explicitly. + *

+ * The documentation for this class makes use of marble diagrams. The following legend explains these diagrams: + *

+ * + *

+ * The {@code Flowable} follows the protocol + *


+ *      onSubscribe onNext* (onError | onComplete)?
+ * 
+ * where the stream can be disposed through the {@link Subscription} instance provided to consumers through + * {@link Subscriber#onSubscribe(Subscription)}. + * Unlike the {@code Observable.subscribe()} of version 1.x, {@link #subscribe(Subscriber)} does not allow external cancellation + * of a subscription and the {@link Subscriber} instance is expected to expose such capability if needed. + *

+ * Flowables support backpressure and require {@link Subscriber}s to signal demand via {@link Subscription#request(long)}. + *

+ * Example: + *


+ * Disposable d = Flowable.just("Hello world!")
+ *     .delay(1, TimeUnit.SECONDS)
+ *     .subscribeWith(new DisposableSubscriber<String>() {
+ *         @Override public void onStart() {
+ *             System.out.println("Start!");
+ *             request(1);
+ *         }
+ *         @Override public void onNext(String t) {
+ *             System.out.println(t);
+ *             request(1);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *         }
+ *     });
+ *
+ * Thread.sleep(500);
+ * // the sequence can now be cancelled via dispose()
+ * d.dispose();
+ * 
+ *

+ * The Reactive Streams specification is relatively strict when defining interactions between {@code Publisher}s and {@code Subscriber}s, so much so + * that there is a significant performance penalty due certain timing requirements and the need to prepare for invalid + * request amounts via {@link Subscription#request(long)}. + * Therefore, RxJava has introduced the {@link FlowableSubscriber} interface that indicates the consumer can be driven with relaxed rules. + * All RxJava operators are implemented with these relaxed rules in mind. + * If the subscribing {@code Subscriber} does not implement this interface, for example, due to it being from another Reactive Streams compliant + * library, the Flowable will automatically apply a compliance wrapper around it. + *

+ * {@code Flowable} is an abstract class, but it is not advised to implement sources and custom operators by extending the class directly due + * to the large amounts of Reactive Streams + * rules to be followed to the letter. See the wiki for + * some guidance if such custom implementations are necessary. + *

+ * The recommended way of creating custom {@code Flowable}s is by using the {@link #create(FlowableOnSubscribe, BackpressureStrategy)} factory method: + *


+ * Flowable<String> source = Flowable.create(new FlowableOnSubscribe<String>() {
+ *     @Override
+ *     public void subscribe(FlowableEmitter<String> emitter) throws Exception {
+ *
+ *         // signal an item
+ *         emitter.onNext("Hello");
+ *
+ *         // could be some blocking operation
+ *         Thread.sleep(1000);
+ *
+ *         // the consumer might have cancelled the flow
+ *         if (emitter.isCancelled() {
+ *             return;
+ *         }
+ *
+ *         emitter.onNext("World");
+ *
+ *         Thread.sleep(1000);
+ *
+ *         // the end-of-sequence has to be signaled, otherwise the
+ *         // consumers may never finish
+ *         emitter.onComplete();
+ *     }
+ * }, BackpressureStrategy.BUFFER);
+ *
+ * System.out.println("Subscribe!");
+ *
+ * source.subscribe(System.out::println);
+ *
+ * System.out.println("Done!");
+ * 
+ *

+ * RxJava reactive sources, such as {@code Flowable}, are generally synchronous and sequential in nature. In the ReactiveX design, the location (thread) + * where operators run is orthogonal to when the operators can work with data. This means that asynchrony and parallelism + * has to be explicitly expressed via operators such as {@link #subscribeOn(Scheduler)}, {@link #observeOn(Scheduler)} and {@link #parallel()}. In general, + * operators featuring a {@link Scheduler} parameter are introducing this type of asynchrony into the flow. + *

+ * For more information see the ReactiveX + * documentation. + * + * @param + * the type of the items emitted by the Flowable + * @see Observable + * @see ParallelFlowable + * @see DisposableSubscriber + */ +public abstract class Flowable implements Publisher { + /** The default buffer size. */ + static final int BUFFER_SIZE; + static { + BUFFER_SIZE = Math.max(1, Integer.getInteger("rx2.buffer-size", 128)); + } + + /** + * Mirrors the one Publisher in an Iterable of several Publishers that first either emits an item or sends + * a termination notification. + *

+ * + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Publisher}'s backpressure behavior.
+ *
Scheduler:
+ *
{@code amb} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element type + * @param sources + * an Iterable of Publishers sources competing to react first. A subscription to each Publisher will + * occur in the same order as in this Iterable. + * @return a Flowable that emits the same sequence as whichever of the source Publishers first + * emitted an item or sent a termination notification + * @see ReactiveX operators documentation: Amb + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable amb(Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new FlowableAmb(null, sources)); + } + + /** + * Mirrors the one Publisher in an array of several Publishers that first either emits an item or sends + * a termination notification. + *

+ * + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Publisher}'s backpressure behavior.
+ *
Scheduler:
+ *
{@code ambArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element type + * @param sources + * an array of Publisher sources competing to react first. A subscription to each Publisher will + * occur in the same order as in this Iterable. + * @return a Flowable that emits the same sequence as whichever of the source Publishers first + * emitted an item or sent a termination notification + * @see ReactiveX operators documentation: Amb + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable ambArray(Publisher... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + int len = sources.length; + if (len == 0) { + return empty(); + } else + if (len == 1) { + return fromPublisher(sources[0]); + } + return RxJavaPlugins.onAssembly(new FlowableAmb(sources, null)); + } + + /** + * Returns the default internal buffer size used by most async operators. + *

The value can be overridden via system parameter {@code rx2.buffer-size} + * before the Flowable class is loaded. + * @return the default internal buffer size. + */ + public static int bufferSize() { + return BUFFER_SIZE; + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatest(Publisher[] sources, Function combiner) { + return combineLatest(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If there are no source Publishers provided, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatest(Function combiner, Publisher... sources) { + return combineLatest(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Flowable + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatest(Publisher[] sources, Function combiner, int bufferSize) { + ObjectHelper.requireNonNull(sources, "sources is null"); + if (sources.length == 0) { + return empty(); + } + ObjectHelper.requireNonNull(combiner, "combiner is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableCombineLatest(sources, combiner, bufferSize, false)); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatest(Iterable> sources, + Function combiner) { + return combineLatest(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting any items and + * without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Flowable + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatest(Iterable> sources, + Function combiner, int bufferSize) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableCombineLatest(sources, combiner, bufferSize, false)); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatestDelayError(Publisher[] sources, + Function combiner) { + return combineLatestDelayError(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function and delays any error from the sources until + * all source Publishers terminate. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If there are no source Publishers provided, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatestDelayError(Function combiner, + Publisher... sources) { + return combineLatestDelayError(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publisher, where this + * aggregation is defined by a specified function and delays any error from the sources until + * all source Publishers terminate. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If there are no source Publishers provided, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Publisher + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatestDelayError(Function combiner, + int bufferSize, Publisher... sources) { + return combineLatestDelayError(sources, combiner, bufferSize); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function and delays any error from the sources until + * all source Publishers terminate. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Flowable + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatestDelayError(Publisher[] sources, + Function combiner, int bufferSize) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + if (sources.length == 0) { + return empty(); + } + return RxJavaPlugins.onAssembly(new FlowableCombineLatest(sources, combiner, bufferSize, true)); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function and delays any error from the sources until + * all source Publishers terminate. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatestDelayError(Iterable> sources, + Function combiner) { + return combineLatestDelayError(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source Publishers by emitting an item that aggregates the latest values of each of + * the source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function and delays any error from the sources until + * all source Publishers terminate. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source Publishers + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Flowable + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable combineLatestDelayError(Iterable> sources, + Function combiner, int bufferSize) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableCombineLatest(sources, combiner, bufferSize, true)); + } + + /** + * Combines two source Publishers by emitting an item that aggregates the latest values of each of the + * source Publishers each time an item is received from either of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the combined output type + * @param source1 + * the first source Publisher + * @param source2 + * the second source Publisher + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable combineLatest( + Publisher source1, Publisher source2, + BiFunction combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + Function f = Functions.toFunction(combiner); + return combineLatest(f, source1, source2); + } + + /** + * Combines three source Publishers by emitting an item that aggregates the latest values of each of the + * source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the combined output type + * @param source1 + * the first source Publisher + * @param source2 + * the second source Publisher + * @param source3 + * the third source Publisher + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable combineLatest( + Publisher source1, Publisher source2, + Publisher source3, + Function3 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return combineLatest(Functions.toFunction(combiner), source1, source2, source3); + } + + /** + * Combines four source Publishers by emitting an item that aggregates the latest values of each of the + * source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the combined output type + * @param source1 + * the first source Publisher + * @param source2 + * the second source Publisher + * @param source3 + * the third source Publisher + * @param source4 + * the fourth source Publisher + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable combineLatest( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4, + Function4 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return combineLatest(Functions.toFunction(combiner), source1, source2, source3, source4); + } + + /** + * Combines five source Publishers by emitting an item that aggregates the latest values of each of the + * source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the combined output type + * @param source1 + * the first source Publisher + * @param source2 + * the second source Publisher + * @param source3 + * the third source Publisher + * @param source4 + * the fourth source Publisher + * @param source5 + * the fifth source Publisher + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable combineLatest( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4, + Publisher source5, + Function5 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + return combineLatest(Functions.toFunction(combiner), source1, source2, source3, source4, source5); + } + + /** + * Combines six source Publishers by emitting an item that aggregates the latest values of each of the + * source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the element type of the sixth source + * @param the combined output type + * @param source1 + * the first source Publisher + * @param source2 + * the second source Publisher + * @param source3 + * the third source Publisher + * @param source4 + * the fourth source Publisher + * @param source5 + * the fifth source Publisher + * @param source6 + * the sixth source Publisher + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable combineLatest( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4, + Publisher source5, Publisher source6, + Function6 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + return combineLatest(Functions.toFunction(combiner), source1, source2, source3, source4, source5, source6); + } + + /** + * Combines seven source Publishers by emitting an item that aggregates the latest values of each of the + * source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the element type of the sixth source + * @param the element type of the seventh source + * @param the combined output type + * @param source1 + * the first source Publisher + * @param source2 + * the second source Publisher + * @param source3 + * the third source Publisher + * @param source4 + * the fourth source Publisher + * @param source5 + * the fifth source Publisher + * @param source6 + * the sixth source Publisher + * @param source7 + * the seventh source Publisher + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable combineLatest( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4, + Publisher source5, Publisher source6, + Publisher source7, + Function7 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + return combineLatest(Functions.toFunction(combiner), source1, source2, source3, source4, source5, source6, source7); + } + + /** + * Combines eight source Publishers by emitting an item that aggregates the latest values of each of the + * source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the element type of the sixth source + * @param the element type of the seventh source + * @param the element type of the eighth source + * @param the combined output type + * @param source1 + * the first source Publisher + * @param source2 + * the second source Publisher + * @param source3 + * the third source Publisher + * @param source4 + * the fourth source Publisher + * @param source5 + * the fifth source Publisher + * @param source6 + * the sixth source Publisher + * @param source7 + * the seventh source Publisher + * @param source8 + * the eighth source Publisher + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable combineLatest( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4, + Publisher source5, Publisher source6, + Publisher source7, Publisher source8, + Function8 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + return combineLatest(Functions.toFunction(combiner), source1, source2, source3, source4, source5, source6, source7, source8); + } + + /** + * Combines nine source Publishers by emitting an item that aggregates the latest values of each of the + * source Publishers each time an item is received from any of the source Publishers, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated until that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Publisher} honors backpressure from downstream. The source {@code Publisher}s + * are requested in a bounded manner, however, their backpressure is not enforced (the operator won't signal + * {@code MissingBackpressureException}) and may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the element type of the sixth source + * @param the element type of the seventh source + * @param the element type of the eighth source + * @param the element type of the ninth source + * @param the combined output type + * @param source1 + * the first source Publisher + * @param source2 + * the second source Publisher + * @param source3 + * the third source Publisher + * @param source4 + * the fourth source Publisher + * @param source5 + * the fifth source Publisher + * @param source6 + * the sixth source Publisher + * @param source7 + * the seventh source Publisher + * @param source8 + * the eighth source Publisher + * @param source9 + * the ninth source Publisher + * @param combiner + * the aggregation function used to combine the items emitted by the source Publishers + * @return a Flowable that emits items that are the result of combining the items emitted by the source + * Publishers by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable combineLatest( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4, + Publisher source5, Publisher source6, + Publisher source7, Publisher source8, + Publisher source9, + Function9 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + ObjectHelper.requireNonNull(source9, "source9 is null"); + return combineLatest(Functions.toFunction(combiner), source1, source2, source3, source4, source5, source6, source7, source8, source9); + } + + /** + * Concatenates elements of each Publisher provided via an Iterable sequence into a single sequence + * of elements without interleaving them. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the common value type of the sources + * @param sources the Iterable sequence of Publishers + * @return the new Flowable instance + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concat(Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + // unlike general sources, fromIterable can only throw on a boundary because it is consumed only there + return fromIterable(sources).concatMapDelayError((Function)Functions.identity(), 2, false); + } + + /** + * Returns a Flowable that emits the items emitted by each of the Publishers emitted by the source + * Publisher, one after the other, without interleaving them. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Publisher} + * sources are expected to honor backpressure as well. If the outer violates this, a + * {@code MissingBackpressureException} is signaled. If any of the inner {@code Publisher}s violates + * this, it may throw an {@code IllegalStateException} when an inner {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * a Publisher that emits Publishers + * @return a Flowable that emits items all of the items emitted by the Publishers emitted by + * {@code Publishers}, one after the other, without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concat(Publisher> sources) { + return concat(sources, bufferSize()); + } + + /** + * Returns a Flowable that emits the items emitted by each of the Publishers emitted by the source + * Publisher, one after the other, without interleaving them. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Publisher} + * sources are expected to honor backpressure as well. If the outer violates this, a + * {@code MissingBackpressureException} is signaled. If any of the inner {@code Publisher}s violates + * this, it may throw an {@code IllegalStateException} when an inner {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * a Publisher that emits Publishers + * @param prefetch + * the number of Publishers to prefetch from the sources sequence. + * @return a Flowable that emits items all of the items emitted by the Publishers emitted by + * {@code Publishers}, one after the other, without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concat(Publisher> sources, int prefetch) { + return fromPublisher(sources).concatMap((Function)Functions.identity(), prefetch); + } + + /** + * Returns a Flowable that emits the items emitted by two Publishers, one after the other, without + * interleaving them. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * a Publisher to be concatenated + * @param source2 + * a Publisher to be concatenated + * @return a Flowable that emits items emitted by the two source Publishers, one after the other, + * without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concat(Publisher source1, Publisher source2) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return concatArray(source1, source2); + } + + /** + * Returns a Flowable that emits the items emitted by three Publishers, one after the other, without + * interleaving them. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * a Publisher to be concatenated + * @param source2 + * a Publisher to be concatenated + * @param source3 + * a Publisher to be concatenated + * @return a Flowable that emits items emitted by the three source Publishers, one after the other, + * without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concat( + Publisher source1, Publisher source2, + Publisher source3) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return concatArray(source1, source2, source3); + } + + /** + * Returns a Flowable that emits the items emitted by four Publishers, one after the other, without + * interleaving them. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * a Publisher to be concatenated + * @param source2 + * a Publisher to be concatenated + * @param source3 + * a Publisher to be concatenated + * @param source4 + * a Publisher to be concatenated + * @return a Flowable that emits items emitted by the four source Publishers, one after the other, + * without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concat( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return concatArray(source1, source2, source3, source4); + } + + /** + * Concatenates a variable number of Publisher sources. + *

+ * Note: named this way because of overload conflict with concat(Publisher<Publisher>). + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concatArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the array of sources + * @param the common base value type + * @return the new Publisher instance + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatArray(Publisher... sources) { + if (sources.length == 0) { + return empty(); + } else + if (sources.length == 1) { + return fromPublisher(sources[0]); + } + return RxJavaPlugins.onAssembly(new FlowableConcatArray(sources, false)); + } + + /** + * Concatenates a variable number of Publisher sources and delays errors from any of them + * till all terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concatArrayDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the array of sources + * @param the common base value type + * @return the new Flowable instance + * @throws NullPointerException if sources is null + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatArrayDelayError(Publisher... sources) { + if (sources.length == 0) { + return empty(); + } else + if (sources.length == 1) { + return fromPublisher(sources[0]); + } + return RxJavaPlugins.onAssembly(new FlowableConcatArray(sources, true)); + } + + /** + * Concatenates an array of Publishers eagerly into a single stream of values. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source Publishers. The operator buffers the values emitted by these Publishers and then drains them + * in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, the operator will signal a + * {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources an array of Publishers that need to be eagerly concatenated + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatArrayEager(Publisher... sources) { + return concatArrayEager(bufferSize(), bufferSize(), sources); + } + + /** + * Concatenates an array of Publishers eagerly into a single stream of values. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source Publishers. The operator buffers the values emitted by these Publishers and then drains them + * in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, the operator will signal a + * {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources an array of Publishers that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscriptions at a time, Integer.MAX_VALUE + * is interpreted as an indication to subscribe to all sources at once + * @param prefetch the number of elements to prefetch from each Publisher source + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Flowable concatArrayEager(int maxConcurrency, int prefetch, Publisher... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapEager(new FlowableFromArray(sources), Functions.identity(), maxConcurrency, prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Concatenates an array of {@link Publisher}s eagerly into a single stream of values + * and delaying any errors until all sources terminate. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source {@code Publisher}s. The operator buffers the values emitted by these {@code Publisher}s + * and then drains them in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, the operator will signal a + * {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources an array of {@code Publisher}s that need to be eagerly concatenated + * @return the new Flowable instance with the specified concatenation behavior + * @since 2.2.1 - experimental + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable concatArrayEagerDelayError(Publisher... sources) { + return concatArrayEagerDelayError(bufferSize(), bufferSize(), sources); + } + + /** + * Concatenates an array of {@link Publisher}s eagerly into a single stream of values + * and delaying any errors until all sources terminate. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source {@code Publisher}s. The operator buffers the values emitted by these {@code Publisher}s + * and then drains them in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, the operator will signal a + * {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources an array of {@code Publisher}s that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscriptions at a time, Integer.MAX_VALUE + * is interpreted as indication to subscribe to all sources at once + * @param prefetch the number of elements to prefetch from each {@code Publisher} source + * @return the new Flowable instance with the specified concatenation behavior + * @since 2.2.1 - experimental + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable concatArrayEagerDelayError(int maxConcurrency, int prefetch, Publisher... sources) { + return fromArray(sources).concatMapEagerDelayError((Function)Functions.identity(), maxConcurrency, prefetch, true); + } + + /** + * Concatenates the Iterable sequence of Publishers into a single sequence by subscribing to each Publisher, + * one after the other, one at a time and delays any errors till the all inner Publishers terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Publisher} + * sources are expected to honor backpressure as well. If the outer violates this, a + * {@code MissingBackpressureException} is signaled. If any of the inner {@code Publisher}s violates + * this, it may throw an {@code IllegalStateException} when an inner {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources the Iterable sequence of Publishers + * @return the new Publisher with the concatenating behavior + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatDelayError(Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return fromIterable(sources).concatMapDelayError((Function)Functions.identity()); + } + + /** + * Concatenates the Publisher sequence of Publishers into a single sequence by subscribing to each inner Publisher, + * one after the other, one at a time and delays any errors till the all inner and the outer Publishers terminate. + * + *
+ *
Backpressure:
+ *
{@code concatDelayError} fully supports backpressure.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources the Publisher sequence of Publishers + * @return the new Publisher with the concatenating behavior + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatDelayError(Publisher> sources) { + return concatDelayError(sources, bufferSize(), true); + } + + /** + * Concatenates the Publisher sequence of Publishers into a single sequence by subscribing to each inner Publisher, + * one after the other, one at a time and delays any errors till the all inner and the outer Publishers terminate. + * + *
+ *
Backpressure:
+ *
{@code concatDelayError} fully supports backpressure.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources the Publisher sequence of Publishers + * @param prefetch the number of elements to prefetch from the outer Publisher + * @param tillTheEnd if true exceptions from the outer and all inner Publishers are delayed to the end + * if false, exception from the outer Publisher is delayed till the current Publisher terminates + * @return the new Publisher with the concatenating behavior + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatDelayError(Publisher> sources, int prefetch, boolean tillTheEnd) { + return fromPublisher(sources).concatMapDelayError((Function)Functions.identity(), prefetch, tillTheEnd); + } + + /** + * Concatenates a Publisher sequence of Publishers eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * emitted source Publishers as they are observed. The operator buffers the values emitted by these + * Publishers and then drains them in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream and both the outer and inner Publishers are + * expected to support backpressure. Violating this assumption, the operator will + * signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of Publishers that need to be eagerly concatenated + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatEager(Publisher> sources) { + return concatEager(sources, bufferSize(), bufferSize()); + } + + /** + * Concatenates a Publisher sequence of Publishers eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * emitted source Publishers as they are observed. The operator buffers the values emitted by these + * Publishers and then drains them in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream and both the outer and inner Publishers are + * expected to support backpressure. Violating this assumption, the operator will + * signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of Publishers that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrently running inner Publishers; Integer.MAX_VALUE + * is interpreted as all inner Publishers can be active at the same time + * @param prefetch the number of elements to prefetch from each inner Publisher source + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Flowable concatEager(Publisher> sources, int maxConcurrency, int prefetch) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapEagerPublisher(sources, Functions.identity(), maxConcurrency, prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Concatenates a sequence of Publishers eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source Publishers. The operator buffers the values emitted by these Publishers and then drains them + * in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream and the inner Publishers are + * expected to support backpressure. Violating this assumption, the operator will + * signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of Publishers that need to be eagerly concatenated + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatEager(Iterable> sources) { + return concatEager(sources, bufferSize(), bufferSize()); + } + + /** + * Concatenates a sequence of Publishers eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source Publishers. The operator buffers the values emitted by these Publishers and then drains them + * in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream and both the outer and inner Publishers are + * expected to support backpressure. Violating this assumption, the operator will + * signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of Publishers that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrently running inner Publishers; Integer.MAX_VALUE + * is interpreted as all inner Publishers can be active at the same time + * @param prefetch the number of elements to prefetch from each inner Publisher source + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Flowable concatEager(Iterable> sources, int maxConcurrency, int prefetch) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapEager(new FlowableFromIterable(sources), Functions.identity(), maxConcurrency, prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Provides an API (via a cold Flowable) that bridges the reactive world with the callback-style, + * generally non-backpressured world. + *

+ * Example: + *


+     * Flowable.<Event>create(emitter -> {
+     *     Callback listener = new Callback() {
+     *         @Override
+     *         public void onEvent(Event e) {
+     *             emitter.onNext(e);
+     *             if (e.isLast()) {
+     *                 emitter.onComplete();
+     *             }
+     *         }
+     *
+     *         @Override
+     *         public void onFailure(Exception e) {
+     *             emitter.onError(e);
+     *         }
+     *     };
+     *
+     *     AutoCloseable c = api.someMethod(listener);
+     *
+     *     emitter.setCancellable(c::close);
+     *
+     * }, BackpressureStrategy.BUFFER);
+     * 
+ *

+ * You should call the FlowableEmitter onNext, onError and onComplete methods in a serialized fashion. The + * rest of its methods are thread-safe. + *

+ *
Backpressure:
+ *
The backpressure behavior is determined by the {@code mode} parameter.
+ *
Scheduler:
+ *
{@code create} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type + * @param source the emitter that is called when a Subscriber subscribes to the returned {@code Flowable} + * @param mode the backpressure mode to apply if the downstream Subscriber doesn't request (fast) enough + * @return the new Flowable instance + * @see FlowableOnSubscribe + * @see BackpressureStrategy + * @see Cancellable + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable create(FlowableOnSubscribe source, BackpressureStrategy mode) { + ObjectHelper.requireNonNull(source, "source is null"); + ObjectHelper.requireNonNull(mode, "mode is null"); + return RxJavaPlugins.onAssembly(new FlowableCreate(source, mode)); + } + + /** + * Returns a Flowable that calls a Publisher factory to create a Publisher for each new Subscriber + * that subscribes. That is, for each subscriber, the actual Publisher that subscriber observes is + * determined by the factory function. + *

+ * + *

+ * The defer Subscriber allows you to defer or delay emitting items from a Publisher until such time as a + * Subscriber subscribes to the Publisher. This allows a {@link Subscriber} to easily obtain updates or a + * refreshed version of the sequence. + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the {@code Publisher} + * returned by the {@code supplier}.
+ *
Scheduler:
+ *
{@code defer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param supplier + * the Publisher factory function to invoke for each {@link Subscriber} that subscribes to the + * resulting Publisher + * @param + * the type of the items emitted by the Publisher + * @return a Flowable whose {@link Subscriber}s' subscriptions trigger an invocation of the given + * Publisher factory function + * @see ReactiveX operators documentation: Defer + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable defer(Callable> supplier) { + ObjectHelper.requireNonNull(supplier, "supplier is null"); + return RxJavaPlugins.onAssembly(new FlowableDefer(supplier)); + } + + /** + * Returns a Flowable that emits no items to the {@link Subscriber} and immediately invokes its + * {@link Subscriber#onComplete onComplete} method. + *

+ * + *

+ *
Backpressure:
+ *
This source doesn't produce any elements and effectively ignores downstream backpressure.
+ *
Scheduler:
+ *
{@code empty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of the items (ostensibly) emitted by the Publisher + * @return a Flowable that emits no items to the {@link Subscriber} but immediately invokes the + * {@link Subscriber}'s {@link Subscriber#onComplete() onComplete} method + * @see ReactiveX operators documentation: Empty + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable empty() { + return RxJavaPlugins.onAssembly((Flowable) FlowableEmpty.INSTANCE); + } + + /** + * Returns a Flowable that invokes a {@link Subscriber}'s {@link Subscriber#onError onError} method when the + * Subscriber subscribes to it. + *

+ * + *

+ *
Backpressure:
+ *
This source doesn't produce any elements and effectively ignores downstream backpressure.
+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param supplier + * a Callable factory to return a Throwable for each individual Subscriber + * @param + * the type of the items (ostensibly) emitted by the Publisher + * @return a Flowable that invokes the {@link Subscriber}'s {@link Subscriber#onError onError} method when + * the Subscriber subscribes to it + * @see ReactiveX operators documentation: Throw + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable error(Callable supplier) { + ObjectHelper.requireNonNull(supplier, "supplier is null"); + return RxJavaPlugins.onAssembly(new FlowableError(supplier)); + } + + /** + * Returns a Flowable that invokes a {@link Subscriber}'s {@link Subscriber#onError onError} method when the + * Subscriber subscribes to it. + *

+ * + *

+ *
Backpressure:
+ *
This source doesn't produce any elements and effectively ignores downstream backpressure.
+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param throwable + * the particular Throwable to pass to {@link Subscriber#onError onError} + * @param + * the type of the items (ostensibly) emitted by the Publisher + * @return a Flowable that invokes the {@link Subscriber}'s {@link Subscriber#onError onError} method when + * the Subscriber subscribes to it + * @see ReactiveX operators documentation: Throw + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable error(final Throwable throwable) { + ObjectHelper.requireNonNull(throwable, "throwable is null"); + return error(Functions.justCallable(throwable)); + } + + /** + * Converts an Array into a Publisher that emits the items in the Array. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and iterates the given {@code array} + * on demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code fromArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param items + * the array of elements + * @param + * the type of items in the Array and the type of items to be emitted by the resulting Publisher + * @return a Flowable that emits each item in the source Array + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable fromArray(T... items) { + ObjectHelper.requireNonNull(items, "items is null"); + if (items.length == 0) { + return empty(); + } + if (items.length == 1) { + return just(items[0]); + } + return RxJavaPlugins.onAssembly(new FlowableFromArray(items)); + } + + /** + * Returns a Flowable that, when a Subscriber subscribes to it, invokes a function you specify and then + * emits the value returned from that function. + *

+ * + *

+ * This allows you to defer the execution of the function you specify until a Subscriber subscribes to the + * Publisher. That is to say, it makes the function "lazy." + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code fromCallable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link Subscriber#onError(Throwable)}, + * except when the downstream has canceled this {@code Flowable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + *
+ *
+ * + * @param supplier + * a function, the execution of which should be deferred; {@code fromCallable} will invoke this + * function only when a Subscriber subscribes to the Publisher that {@code fromCallable} returns + * @param + * the type of the item emitted by the Publisher + * @return a Flowable whose {@link Subscriber}s' subscriptions trigger an invocation of the given function + * @see #defer(Callable) + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable fromCallable(Callable supplier) { + ObjectHelper.requireNonNull(supplier, "supplier is null"); + return RxJavaPlugins.onAssembly(new FlowableFromCallable(supplier)); + } + + /** + * Converts a {@link Future} into a Publisher. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a Publisher that emits the + * return value of the {@link Future#get} method of that object by passing the object into the {@code from} + * method. + *

+ * Important note: This Publisher is blocking on the thread it gets subscribed on; you cannot cancel it. + *

+ * Unlike 1.x, canceling the Flowable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futurePublisher.doOnCancel(() -> future.cancel(true));}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting Publisher + * @return a Flowable that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable fromFuture(Future future) { + ObjectHelper.requireNonNull(future, "future is null"); + return RxJavaPlugins.onAssembly(new FlowableFromFuture(future, 0L, null)); + } + + /** + * Converts a {@link Future} into a Publisher, with a timeout on the Future. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a Publisher that emits the + * return value of the {@link Future#get} method of that object by passing the object into the {@code fromFuture} + * method. + *

+ * Unlike 1.x, canceling the Flowable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futurePublisher.doOnCancel(() -> future.cancel(true));}. + *

+ * Important note: This Publisher is blocking on the thread it gets subscribed on; you cannot cancel it. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param timeout + * the maximum time to wait before calling {@code get} + * @param unit + * the {@link TimeUnit} of the {@code timeout} argument + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting Publisher + * @return a Flowable that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable fromFuture(Future future, long timeout, TimeUnit unit) { + ObjectHelper.requireNonNull(future, "future is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + return RxJavaPlugins.onAssembly(new FlowableFromFuture(future, timeout, unit)); + } + + /** + * Converts a {@link Future} into a Publisher, with a timeout on the Future. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a Publisher that emits the + * return value of the {@link Future#get} method of that object by passing the object into the {@code from} + * method. + *

+ * Unlike 1.x, canceling the Flowable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futurePublisher.doOnCancel(() -> future.cancel(true));}. + *

+ * Important note: This Publisher is blocking; you cannot cancel it. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param timeout + * the maximum time to wait before calling {@code get} + * @param unit + * the {@link TimeUnit} of the {@code timeout} argument + * @param scheduler + * the {@link Scheduler} to wait for the Future on. Use a Scheduler such as + * {@link Schedulers#io()} that can block and wait on the Future + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting Publisher + * @return a Flowable that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @SuppressWarnings({ "unchecked", "cast" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Flowable fromFuture(Future future, long timeout, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return fromFuture((Future)future, timeout, unit).subscribeOn(scheduler); + } + + /** + * Converts a {@link Future}, operating on a specified {@link Scheduler}, into a Publisher. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a Publisher that emits the + * return value of the {@link Future#get} method of that object by passing the object into the {@code from} + * method. + *

+ * Unlike 1.x, canceling the Flowable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futurePublisher.doOnCancel(() -> future.cancel(true));}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param future + * the source {@link Future} + * @param scheduler + * the {@link Scheduler} to wait for the Future on. Use a Scheduler such as + * {@link Schedulers#io()} that can block and wait on the Future + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting Publisher + * @return a Flowable that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @SuppressWarnings({ "cast", "unchecked" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Flowable fromFuture(Future future, Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return fromFuture((Future)future).subscribeOn(scheduler); + } + + /** + * Converts an {@link Iterable} sequence into a Publisher that emits the items in the sequence. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and iterates the given {@code iterable} + * on demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code fromIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source + * the source {@link Iterable} sequence + * @param + * the type of items in the {@link Iterable} sequence and the type of items to be emitted by the + * resulting Publisher + * @return a Flowable that emits each item in the source {@link Iterable} sequence + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable fromIterable(Iterable source) { + ObjectHelper.requireNonNull(source, "source is null"); + return RxJavaPlugins.onAssembly(new FlowableFromIterable(source)); + } + + /** + * Converts an arbitrary Reactive Streams Publisher into a Flowable if not already a + * Flowable. + *

+ * The {@link Publisher} must follow the + * Reactive Streams specification. + * Violating the specification may result in undefined behavior. + *

+ * If possible, use {@link #create(FlowableOnSubscribe, BackpressureStrategy)} to create a + * source-like {@code Flowable} instead. + *

+ * Note that even though {@link Publisher} appears to be a functional interface, it + * is not recommended to implement it through a lambda as the specification requires + * state management that is not achievable with a stateless lambda. + *

+ *
Backpressure:
+ *
The operator is a pass-through for backpressure and its behavior is determined by the + * backpressure behavior of the wrapped publisher.
+ *
Scheduler:
+ *
{@code fromPublisher} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the flow + * @param source the Publisher to convert + * @return the new Flowable instance + * @throws NullPointerException if the {@code source} {@code Publisher} is null + * @see #create(FlowableOnSubscribe, BackpressureStrategy) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable fromPublisher(final Publisher source) { + if (source instanceof Flowable) { + return RxJavaPlugins.onAssembly((Flowable)source); + } + ObjectHelper.requireNonNull(source, "source is null"); + + return RxJavaPlugins.onAssembly(new FlowableFromPublisher(source)); + } + + /** + * Returns a cold, synchronous, stateless and backpressure-aware generator of values. + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure.
+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the generated value type + * @param generator the Consumer called whenever a particular downstream Subscriber has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event. Signaling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable generate(final Consumer> generator) { + ObjectHelper.requireNonNull(generator, "generator is null"); + return generate(Functions.nullSupplier(), + FlowableInternalHelper.simpleGenerator(generator), + Functions.emptyConsumer()); + } + + /** + * Returns a cold, synchronous, stateful and backpressure-aware generator of values. + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure.
+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the per-Subscriber state + * @param the generated value type + * @param initialState the Callable to generate the initial state for each Subscriber + * @param generator the Consumer called with the current state whenever a particular downstream Subscriber has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event. Signaling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable generate(Callable initialState, final BiConsumer> generator) { + ObjectHelper.requireNonNull(generator, "generator is null"); + return generate(initialState, FlowableInternalHelper.simpleBiGenerator(generator), + Functions.emptyConsumer()); + } + + /** + * Returns a cold, synchronous, stateful and backpressure-aware generator of values. + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure.
+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the per-Subscriber state + * @param the generated value type + * @param initialState the Callable to generate the initial state for each Subscriber + * @param generator the Consumer called with the current state whenever a particular downstream Subscriber has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event. Signaling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @param disposeState the Consumer that is called with the current state when the generator + * terminates the sequence or it gets canceled + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable generate(Callable initialState, final BiConsumer> generator, + Consumer disposeState) { + ObjectHelper.requireNonNull(generator, "generator is null"); + return generate(initialState, FlowableInternalHelper.simpleBiGenerator(generator), disposeState); + } + + /** + * Returns a cold, synchronous, stateful and backpressure-aware generator of values. + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure.
+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the per-Subscriber state + * @param the generated value type + * @param initialState the Callable to generate the initial state for each Subscriber + * @param generator the Function called with the current state whenever a particular downstream Subscriber has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event and should return a (new) state for + * the next invocation. Signaling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @return the new Flowable instance + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable generate(Callable initialState, BiFunction, S> generator) { + return generate(initialState, generator, Functions.emptyConsumer()); + } + + /** + * Returns a cold, synchronous, stateful and backpressure-aware generator of values. + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure.
+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the per-Subscriber state + * @param the generated value type + * @param initialState the Callable to generate the initial state for each Subscriber + * @param generator the Function called with the current state whenever a particular downstream Subscriber has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event and should return a (new) state for + * the next invocation. Signaling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @param disposeState the Consumer that is called with the current state when the generator + * terminates the sequence or it gets canceled + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable generate(Callable initialState, BiFunction, S> generator, Consumer disposeState) { + ObjectHelper.requireNonNull(initialState, "initialState is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(disposeState, "disposeState is null"); + return RxJavaPlugins.onAssembly(new FlowableGenerate(initialState, generator, disposeState)); + } + + /** + * Returns a Flowable that emits a {@code 0L} after the {@code initialDelay} and ever-increasing numbers + * after each {@code period} of time thereafter. + *

+ * + *

+ *
Backpressure:
+ *
The operator generates values based on time and ignores downstream backpressure which + * may lead to {@code MissingBackpressureException} at some point in the chain. + * Consumers should consider applying one of the {@code onBackpressureXXX} operators as well.
+ *
Scheduler:
+ *
{@code interval} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param initialDelay + * the initial delay time to wait before emitting the first value of 0L + * @param period + * the period of time between emissions of the subsequent numbers + * @param unit + * the time unit for both {@code initialDelay} and {@code period} + * @return a Flowable that emits a 0L after the {@code initialDelay} and ever-increasing numbers after + * each {@code period} of time thereafter + * @see ReactiveX operators documentation: Interval + * @since 1.0.12 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Flowable interval(long initialDelay, long period, TimeUnit unit) { + return interval(initialDelay, period, unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits a {@code 0L} after the {@code initialDelay} and ever-increasing numbers + * after each {@code period} of time thereafter, on a specified {@link Scheduler}. + *

+ * + *

+ *
Backpressure:
+ *
The operator generates values based on time and ignores downstream backpressure which + * may lead to {@code MissingBackpressureException} at some point in the chain. + * Consumers should consider applying one of the {@code onBackpressureXXX} operators as well.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param initialDelay + * the initial delay time to wait before emitting the first value of 0L + * @param period + * the period of time between emissions of the subsequent numbers + * @param unit + * the time unit for both {@code initialDelay} and {@code period} + * @param scheduler + * the Scheduler on which the waiting happens and items are emitted + * @return a Flowable that emits a 0L after the {@code initialDelay} and ever-increasing numbers after + * each {@code period} of time thereafter, while running on the given Scheduler + * @see ReactiveX operators documentation: Interval + * @since 1.0.12 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Flowable interval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableInterval(Math.max(0L, initialDelay), Math.max(0L, period), unit, scheduler)); + } + + /** + * Returns a Flowable that emits a sequential number every specified interval of time. + *

+ * + *

+ *
Backpressure:
+ *
The operator signals a {@code MissingBackpressureException} if the downstream + * is not ready to receive the next value.
+ *
Scheduler:
+ *
{@code interval} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param period + * the period size in time units (see below) + * @param unit + * time units to use for the interval size + * @return a Flowable that emits a sequential number each time interval + * @see ReactiveX operators documentation: Interval + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Flowable interval(long period, TimeUnit unit) { + return interval(period, period, unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits a sequential number every specified interval of time, on a + * specified Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator generates values based on time and ignores downstream backpressure which + * may lead to {@code MissingBackpressureException} at some point in the chain. + * Consumers should consider applying one of the {@code onBackpressureXXX} operators as well.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param period + * the period size in time units (see below) + * @param unit + * time units to use for the interval size + * @param scheduler + * the Scheduler to use for scheduling the items + * @return a Flowable that emits a sequential number each time interval + * @see ReactiveX operators documentation: Interval + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Flowable interval(long period, TimeUnit unit, Scheduler scheduler) { + return interval(period, period, unit, scheduler); + } + + /** + * Signals a range of long values, the first after some initial delay and the rest periodically after. + *

+ * The sequence completes immediately after the last value (start + count - 1) has been reached. + *

+ *
Backpressure:
+ *
The operator signals a {@code MissingBackpressureException} if the downstream can't keep up.
+ *
Scheduler:
+ *
{@code intervalRange} by default operates on the {@link Schedulers#computation() computation} {@link Scheduler}.
+ *
+ * @param start that start value of the range + * @param count the number of values to emit in total, if zero, the operator emits an onComplete after the initial delay. + * @param initialDelay the initial delay before signaling the first value (the start) + * @param period the period between subsequent values + * @param unit the unit of measure of the initialDelay and period amounts + * @return the new Flowable instance + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Flowable intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit) { + return intervalRange(start, count, initialDelay, period, unit, Schedulers.computation()); + } + + /** + * Signals a range of long values, the first after some initial delay and the rest periodically after. + *

+ * The sequence completes immediately after the last value (start + count - 1) has been reached. + *

+ *
Backpressure:
+ *
The operator signals a {@code MissingBackpressureException} if the downstream can't keep up.
+ *
Scheduler:
+ *
you provide the {@link Scheduler}.
+ *
+ * @param start that start value of the range + * @param count the number of values to emit in total, if zero, the operator emits an onComplete after the initial delay. + * @param initialDelay the initial delay before signaling the first value (the start) + * @param period the period between subsequent values + * @param unit the unit of measure of the initialDelay and period amounts + * @param scheduler the target scheduler where the values and terminal signals will be emitted + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Flowable intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + if (count < 0L) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } + if (count == 0L) { + return Flowable.empty().delay(initialDelay, unit, scheduler); + } + + long end = start + (count - 1); + if (start > 0 && end < 0) { + throw new IllegalArgumentException("Overflow! start + count is bigger than Long.MAX_VALUE"); + } + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + + return RxJavaPlugins.onAssembly(new FlowableIntervalRange(start, end, Math.max(0L, initialDelay), Math.max(0L, period), unit, scheduler)); + } + + /** + * Returns a Flowable that signals the given (constant reference) item and then completes. + *

+ * + *

+ * Note that the item is taken and re-emitted as is and not computed by any means by {@code just}. Use {@link #fromCallable(Callable)} + * to generate a single item on demand (when {@code Subscriber}s subscribe to it). + *

+ * See the multi-parameter overloads of {@code just} to emit more than one (constant reference) items one after the other. + * Use {@link #fromArray(Object...)} to emit an arbitrary number of items that are known upfront. + *

+ * To emit the items of an {@link Iterable} sequence (such as a {@link List}), use {@link #fromIterable(Iterable)}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the item to emit + * @param + * the type of that item + * @return a Flowable that emits {@code value} as a single item and then completes + * @see ReactiveX operators documentation: Just + * @see #just(Object, Object) + * @see #fromCallable(Callable) + * @see #fromArray(Object...) + * @see #fromIterable(Iterable) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item) { + ObjectHelper.requireNonNull(item, "item is null"); + return RxJavaPlugins.onAssembly(new FlowableJust(item)); + } + + /** + * Converts two items into a Publisher that emits those items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param + * the type of these items + * @return a Flowable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item1, T item2) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + + return fromArray(item1, item2); + } + + /** + * Converts three items into a Publisher that emits those items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param + * the type of these items + * @return a Flowable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item1, T item2, T item3) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + + return fromArray(item1, item2, item3); + } + + /** + * Converts four items into a Publisher that emits those items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param + * the type of these items + * @return a Flowable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item1, T item2, T item3, T item4) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + + return fromArray(item1, item2, item3, item4); + } + + /** + * Converts five items into a Publisher that emits those items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param + * the type of these items + * @return a Flowable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item1, T item2, T item3, T item4, T item5) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + + return fromArray(item1, item2, item3, item4, item5); + } + + /** + * Converts six items into a Publisher that emits those items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param + * the type of these items + * @return a Flowable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6); + } + + /** + * Converts seven items into a Publisher that emits those items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param item7 + * seventh item + * @param + * the type of these items + * @return a Flowable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6, item7); + } + + /** + * Converts eight items into a Publisher that emits those items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param item7 + * seventh item + * @param item8 + * eighth item + * @param + * the type of these items + * @return a Flowable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6, item7, item8); + } + + /** + * Converts nine items into a Publisher that emits those items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param item7 + * seventh item + * @param item8 + * eighth item + * @param item9 + * ninth item + * @param + * the type of these items + * @return a Flowable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9); + } + + /** + * Converts ten items into a Publisher that emits those items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals each value on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param item7 + * seventh item + * @param item8 + * eighth item + * @param item9 + * ninth item + * @param item10 + * tenth item + * @param + * the type of these items + * @return a Flowable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9, T item10) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); + ObjectHelper.requireNonNull(item10, "item10 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10); + } + + /** + * Flattens an Iterable of Publishers into one Publisher, without any transformation, while limiting the + * number of concurrent subscriptions to these Publishers. + *

+ * + *

+ * You can combine the items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable, int, int)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of Publishers + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @param bufferSize + * the number of items to prefetch from each inner Publisher + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * Publishers in the Iterable + * @throws IllegalArgumentException + * if {@code maxConcurrency} is less than or equal to 0 + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Iterable, int, int) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Iterable> sources, int maxConcurrency, int bufferSize) { + return fromIterable(sources).flatMap((Function)Functions.identity(), false, maxConcurrency, bufferSize); + } + + /** + * Flattens an Iterable of Publishers into one Publisher, without any transformation, while limiting the + * number of concurrent subscriptions to these Publishers. + *

+ * + *

+ * You can combine the items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeArray} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(int, int, Publisher[])} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the array of Publishers + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @param bufferSize + * the number of items to prefetch from each inner Publisher + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * Publishers in the Iterable + * @throws IllegalArgumentException + * if {@code maxConcurrency} is less than or equal to 0 + * @see ReactiveX operators documentation: Merge + * @see #mergeArrayDelayError(int, int, Publisher...) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeArray(int maxConcurrency, int bufferSize, Publisher... sources) { + return fromArray(sources).flatMap((Function)Functions.identity(), false, maxConcurrency, bufferSize); + } + + /** + * Flattens an Iterable of Publishers into one Publisher, without any transformation. + *

+ * + *

+ * You can combine the items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of Publishers + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * Publishers in the Iterable + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Iterable) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Iterable> sources) { + return fromIterable(sources).flatMap((Function)Functions.identity()); + } + + /** + * Flattens an Iterable of Publishers into one Publisher, without any transformation, while limiting the + * number of concurrent subscriptions to these Publishers. + *

+ * + *

+ * You can combine the items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable, int)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of Publishers + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * Publishers in the Iterable + * @throws IllegalArgumentException + * if {@code maxConcurrency} is less than or equal to 0 + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Iterable, int) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Iterable> sources, int maxConcurrency) { + return fromIterable(sources).flatMap((Function)Functions.identity(), maxConcurrency); + } + + /** + * Flattens a Publisher that emits Publishers into a single Publisher that emits the items emitted by + * those Publishers, without any transformation. + *

+ * + *

+ * You can combine the items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed + * in unbounded mode (i.e., no backpressure is applied to it). The inner {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * a Publisher that emits Publishers + * @return a Flowable that emits items that are the result of flattening the Publishers emitted by the + * {@code source} Publisher + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Publisher) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Publisher> sources) { + return merge(sources, bufferSize()); + } + + /** + * Flattens a Publisher that emits Publishers into a single Publisher that emits the items emitted by + * those Publishers, without any transformation, while limiting the maximum number of concurrent + * subscriptions to these Publishers. + *

+ * + *

+ * You can combine the items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, int)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * a Publisher that emits Publishers + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @return a Flowable that emits items that are the result of flattening the Publishers emitted by the + * {@code source} Publisher + * @throws IllegalArgumentException + * if {@code maxConcurrency} is less than or equal to 0 + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Publisher, int) + * @since 1.1.0 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Publisher> sources, int maxConcurrency) { + return fromPublisher(sources).flatMap((Function)Functions.identity(), maxConcurrency); + } + + /** + * Flattens an Array of Publishers into one Publisher, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeArray} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(Publisher...)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the array of Publishers + * @return a Flowable that emits all of the items emitted by the Publishers in the Array + * @see ReactiveX operators documentation: Merge + * @see #mergeArrayDelayError(Publisher...) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeArray(Publisher... sources) { + return fromArray(sources).flatMap((Function)Functions.identity(), sources.length); + } + + /** + * Flattens two Publishers into a single Publisher, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, Publisher)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param source1 + * a Publisher to be merged + * @param source2 + * a Publisher to be merged + * @return a Flowable that emits all of the items emitted by the source Publishers + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Publisher, Publisher) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Publisher source1, Publisher source2) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return fromArray(source1, source2).flatMap((Function)Functions.identity(), false, 2); + } + + /** + * Flattens three Publishers into a single Publisher, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, Publisher, Publisher)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param source1 + * a Publisher to be merged + * @param source2 + * a Publisher to be merged + * @param source3 + * a Publisher to be merged + * @return a Flowable that emits all of the items emitted by the source Publishers + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Publisher, Publisher, Publisher) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Publisher source1, Publisher source2, Publisher source3) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return fromArray(source1, source2, source3).flatMap((Function)Functions.identity(), false, 3); + } + + /** + * Flattens four Publishers into a single Publisher, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code Publisher}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code Publisher}s are canceled. + * If more than one {@code Publisher} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been canceled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, Publisher, Publisher, Publisher)} to merge sources and terminate only when all source {@code Publisher}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param source1 + * a Publisher to be merged + * @param source2 + * a Publisher to be merged + * @param source3 + * a Publisher to be merged + * @param source4 + * a Publisher to be merged + * @return a Flowable that emits all of the items emitted by the source Publishers + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Publisher, Publisher, Publisher, Publisher) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return fromArray(source1, source2, source3, source4).flatMap((Function)Functions.identity(), false, 4); + } + + /** + * Flattens an Iterable of Publishers into one Publisher, in a way that allows a Subscriber to receive all + * successfully emitted items from each of the source Publishers without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged Publishers notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged Publishers have finished emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. All inner {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of Publishers + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * Publishers in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Iterable> sources) { + return fromIterable(sources).flatMap((Function)Functions.identity(), true); + } + + /** + * Flattens an Iterable of Publishers into one Publisher, in a way that allows a Subscriber to receive all + * successfully emitted items from each of the source Publishers without being interrupted by an error + * notification from one of them, while limiting the number of concurrent subscriptions to these Publishers. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged Publishers notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged Publishers have finished emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. All inner {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of Publishers + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @param bufferSize + * the number of items to prefetch from each inner Publisher + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * Publishers in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Iterable> sources, int maxConcurrency, int bufferSize) { + return fromIterable(sources).flatMap((Function)Functions.identity(), true, maxConcurrency, bufferSize); + } + + /** + * Flattens an array of Publishers into one Publisher, in a way that allows a Subscriber to receive all + * successfully emitted items from each of the source Publishers without being interrupted by an error + * notification from one of them, while limiting the number of concurrent subscriptions to these Publishers. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged Publishers notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged Publishers have finished emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. All source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeArrayDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the array of Publishers + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @param bufferSize + * the number of items to prefetch from each inner Publisher + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * Publishers in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeArrayDelayError(int maxConcurrency, int bufferSize, Publisher... sources) { + return fromArray(sources).flatMap((Function)Functions.identity(), true, maxConcurrency, bufferSize); + } + + /** + * Flattens an Iterable of Publishers into one Publisher, in a way that allows a Subscriber to receive all + * successfully emitted items from each of the source Publishers without being interrupted by an error + * notification from one of them, while limiting the number of concurrent subscriptions to these Publishers. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged Publishers notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged Publishers have finished emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. All inner {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of Publishers + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * Publishers in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Iterable> sources, int maxConcurrency) { + return fromIterable(sources).flatMap((Function)Functions.identity(), true, maxConcurrency); + } + + /** + * Flattens a Publisher that emits Publishers into one Publisher, in a way that allows a Subscriber to + * receive all successfully emitted items from all of the source Publishers without being interrupted by + * an error notification from one of them. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged Publishers notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged Publishers have finished emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed + * in unbounded mode (i.e., no backpressure is applied to it). The inner {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * a Publisher that emits Publishers + * @return a Flowable that emits all of the items emitted by the Publishers emitted by the + * {@code source} Publisher + * @see ReactiveX operators documentation: Merge + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Publisher> sources) { + return mergeDelayError(sources, bufferSize()); + } + + /** + * Flattens a Publisher that emits Publishers into one Publisher, in a way that allows a Subscriber to + * receive all successfully emitted items from all of the source Publishers without being interrupted by + * an error notification from one of them, while limiting the + * number of concurrent subscriptions to these Publishers. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged Publishers notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged Publishers have finished emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * a Publisher that emits Publishers + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @return a Flowable that emits all of the items emitted by the Publishers emitted by the + * {@code source} Publisher + * @see ReactiveX operators documentation: Merge + * @since 2.0 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Publisher> sources, int maxConcurrency) { + return fromPublisher(sources).flatMap((Function)Functions.identity(), true, maxConcurrency); + } + + /** + * Flattens an array of Publishers into one Flowable, in a way that allows a Subscriber to receive all + * successfully emitted items from each of the source Publishers without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged Publishers notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged Publishers have finished emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both the outer and inner {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeArrayDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of Publishers + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * Publishers in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeArrayDelayError(Publisher... sources) { + return fromArray(sources).flatMap((Function)Functions.identity(), true, sources.length); + } + + /** + * Flattens two Publishers into one Publisher, in a way that allows a Subscriber to receive all + * successfully emitted items from each of the source Publishers without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(Publisher, Publisher)} except that if any of the merged Publishers + * notify of an error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from + * propagating that error notification until all of the merged Publishers have finished emitting items. + *

+ * + *

+ * Even if both merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * a Publisher to be merged + * @param source2 + * a Publisher to be merged + * @return a Flowable that emits all of the items that are emitted by the two source Publishers + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Publisher source1, Publisher source2) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return fromArray(source1, source2).flatMap((Function)Functions.identity(), true, 2); + } + + /** + * Flattens three Publishers into one Publisher, in a way that allows a Subscriber to receive all + * successfully emitted items from all of the source Publishers without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(Publisher, Publisher, Publisher)} except that if any of the merged + * Publishers notify of an error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged Publishers have finished emitting + * items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * a Publisher to be merged + * @param source2 + * a Publisher to be merged + * @param source3 + * a Publisher to be merged + * @return a Flowable that emits all of the items that are emitted by the source Publishers + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Publisher source1, Publisher source2, Publisher source3) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return fromArray(source1, source2, source3).flatMap((Function)Functions.identity(), true, 3); + } + + /** + * Flattens four Publishers into one Publisher, in a way that allows a Subscriber to receive all + * successfully emitted items from all of the source Publishers without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(Publisher, Publisher, Publisher, Publisher)} except that if any of + * the merged Publishers notify of an error via {@link Subscriber#onError onError}, {@code mergeDelayError} + * will refrain from propagating that error notification until all of the merged Publishers have finished + * emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * a Publisher to be merged + * @param source2 + * a Publisher to be merged + * @param source3 + * a Publisher to be merged + * @param source4 + * a Publisher to be merged + * @return a Flowable that emits all of the items that are emitted by the source Publishers + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return fromArray(source1, source2, source3, source4).flatMap((Function)Functions.identity(), true, 4); + } + + /** + * Returns a Flowable that never sends any items or notifications to a {@link Subscriber}. + *

+ * + *

+ * This Publisher is useful primarily for testing purposes. + *

+ *
Backpressure:
+ *
This source doesn't produce any elements and effectively ignores downstream backpressure.
+ *
Scheduler:
+ *
{@code never} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items (not) emitted by the Publisher + * @return a Flowable that never emits any items or sends any notifications to a {@link Subscriber} + * @see ReactiveX operators documentation: Never + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable never() { + return RxJavaPlugins.onAssembly((Flowable) FlowableNever.INSTANCE); + } + + /** + * Returns a Flowable that emits a sequence of Integers within a specified range. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals values on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code range} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param start + * the value of the first Integer in the sequence + * @param count + * the number of sequential Integers to generate + * @return a Flowable that emits a range of sequential Integers + * @throws IllegalArgumentException + * if {@code count} is less than zero, or if {@code start} + {@code count} − 1 exceeds + * {@code Integer.MAX_VALUE} + * @see ReactiveX operators documentation: Range + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable range(int start, int count) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } else + if (count == 0) { + return empty(); + } else + if (count == 1) { + return just(start); + } else + if ((long)start + (count - 1) > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Integer overflow"); + } + return RxJavaPlugins.onAssembly(new FlowableRange(start, count)); + } + + /** + * Returns a Flowable that emits a sequence of Longs within a specified range. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and signals values on-demand (i.e., when requested).
+ *
Scheduler:
+ *
{@code rangeLong} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param start + * the value of the first Long in the sequence + * @param count + * the number of sequential Longs to generate + * @return a Flowable that emits a range of sequential Longs + * @throws IllegalArgumentException + * if {@code count} is less than zero, or if {@code start} + {@code count} − 1 exceeds + * {@code Long.MAX_VALUE} + * @see ReactiveX operators documentation: Range + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable rangeLong(long start, long count) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } + + if (count == 0) { + return empty(); + } + + if (count == 1) { + return just(start); + } + + long end = start + (count - 1); + if (start > 0 && end < 0) { + throw new IllegalArgumentException("Overflow! start + count is bigger than Long.MAX_VALUE"); + } + + return RxJavaPlugins.onAssembly(new FlowableRangeLong(start, count)); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two Publisher sequences are the + * same by comparing the items emitted by each Publisher pairwise. + *

+ * + *

+ *
Backpressure:
+ *
This operator honors downstream backpressure and expects both of its sources + * to honor backpressure as well. If violated, the operator will emit a MissingBackpressureException.
+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first Publisher to compare + * @param source2 + * the second Publisher to compare + * @param + * the type of items emitted by each Publisher + * @return a Flowable that emits a Boolean value that indicates whether the two sequences are the same + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(Publisher source1, Publisher source2) { + return sequenceEqual(source1, source2, ObjectHelper.equalsPredicate(), bufferSize()); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two Publisher sequences are the + * same by comparing the items emitted by each Publisher pairwise based on the results of a specified + * equality function. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator signals a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first Publisher to compare + * @param source2 + * the second Publisher to compare + * @param isEqual + * a function used to compare items emitted by each Publisher + * @param + * the type of items emitted by each Publisher + * @return a Single that emits a Boolean value that indicates whether the two Publisher sequences + * are the same according to the specified function + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(Publisher source1, Publisher source2, + BiPredicate isEqual) { + return sequenceEqual(source1, source2, isEqual, bufferSize()); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two Publisher sequences are the + * same by comparing the items emitted by each Publisher pairwise based on the results of a specified + * equality function. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s are expected to honor + * backpressure; if violated, the operator signals a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first Publisher to compare + * @param source2 + * the second Publisher to compare + * @param isEqual + * a function used to compare items emitted by each Publisher + * @param bufferSize + * the number of items to prefetch from the first and second source Publisher + * @param + * the type of items emitted by each Publisher + * @return a Single that emits a Boolean value that indicates whether the two Publisher sequences + * are the same according to the specified function + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(Publisher source1, Publisher source2, + BiPredicate isEqual, int bufferSize) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(isEqual, "isEqual is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableSequenceEqualSingle(source1, source2, isEqual, bufferSize)); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two Publisher sequences are the + * same by comparing the items emitted by each Publisher pairwise. + *

+ * + *

+ *
Backpressure:
+ *
This operator honors downstream backpressure and expects both of its sources + * to honor backpressure as well. If violated, the operator will emit a MissingBackpressureException.
+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first Publisher to compare + * @param source2 + * the second Publisher to compare + * @param bufferSize + * the number of items to prefetch from the first and second source Publisher + * @param + * the type of items emitted by each Publisher + * @return a Single that emits a Boolean value that indicates whether the two sequences are the same + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(Publisher source1, Publisher source2, int bufferSize) { + return sequenceEqual(source1, source2, ObjectHelper.equalsPredicate(), bufferSize); + } + + /** + * Converts a Publisher that emits Publishers into a Publisher that emits the items emitted by the + * most recently emitted of those Publishers. + *

+ * + *

+ * {@code switchOnNext} subscribes to a Publisher that emits Publishers. Each time it observes one of + * these emitted Publishers, the Publisher returned by {@code switchOnNext} begins emitting the items + * emitted by that Publisher. When a new Publisher is emitted, {@code switchOnNext} stops emitting items + * from the earlier-emitted Publisher and begins emitting items from the new one. + *

+ * The resulting Publisher completes if both the outer Publisher and the last inner Publisher, if any, complete. + * If the outer Publisher signals an onError, the inner Publisher is canceled and the error delivered in-sequence. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Publisher}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code switchOnNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the item type + * @param sources + * the source Publisher that emits Publishers + * @param bufferSize + * the number of items to prefetch from the inner Publishers + * @return a Flowable that emits the items emitted by the Publisher most recently emitted by the source + * Publisher + * @see ReactiveX operators documentation: Switch + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable switchOnNext(Publisher> sources, int bufferSize) { + return fromPublisher(sources).switchMap((Function)Functions.identity(), bufferSize); + } + + /** + * Converts a Publisher that emits Publishers into a Publisher that emits the items emitted by the + * most recently emitted of those Publishers. + *

+ * + *

+ * {@code switchOnNext} subscribes to a Publisher that emits Publishers. Each time it observes one of + * these emitted Publishers, the Publisher returned by {@code switchOnNext} begins emitting the items + * emitted by that Publisher. When a new Publisher is emitted, {@code switchOnNext} stops emitting items + * from the earlier-emitted Publisher and begins emitting items from the new one. + *

+ * The resulting Publisher completes if both the outer Publisher and the last inner Publisher, if any, complete. + * If the outer Publisher signals an onError, the inner Publisher is canceled and the error delivered in-sequence. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Publisher}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code switchOnNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the item type + * @param sources + * the source Publisher that emits Publishers + * @return a Flowable that emits the items emitted by the Publisher most recently emitted by the source + * Publisher + * @see ReactiveX operators documentation: Switch + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable switchOnNext(Publisher> sources) { + return fromPublisher(sources).switchMap((Function)Functions.identity()); + } + + /** + * Converts a Publisher that emits Publishers into a Publisher that emits the items emitted by the + * most recently emitted of those Publishers and delays any exception until all Publishers terminate. + *

+ * + *

+ * {@code switchOnNext} subscribes to a Publisher that emits Publishers. Each time it observes one of + * these emitted Publishers, the Publisher returned by {@code switchOnNext} begins emitting the items + * emitted by that Publisher. When a new Publisher is emitted, {@code switchOnNext} stops emitting items + * from the earlier-emitted Publisher and begins emitting items from the new one. + *

+ * The resulting Publisher completes if both the main Publisher and the last inner Publisher, if any, complete. + * If the main Publisher signals an onError, the termination of the last inner Publisher will emit that error as is + * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signaled. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Publisher}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code switchOnNextDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the item type + * @param sources + * the source Publisher that emits Publishers + * @return a Flowable that emits the items emitted by the Publisher most recently emitted by the source + * Publisher + * @see ReactiveX operators documentation: Switch + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable switchOnNextDelayError(Publisher> sources) { + return switchOnNextDelayError(sources, bufferSize()); + } + + /** + * Converts a Publisher that emits Publishers into a Publisher that emits the items emitted by the + * most recently emitted of those Publishers and delays any exception until all Publishers terminate. + *

+ * + *

+ * {@code switchOnNext} subscribes to a Publisher that emits Publishers. Each time it observes one of + * these emitted Publishers, the Publisher returned by {@code switchOnNext} begins emitting the items + * emitted by that Publisher. When a new Publisher is emitted, {@code switchOnNext} stops emitting items + * from the earlier-emitted Publisher and begins emitting items from the new one. + *

+ * The resulting Publisher completes if both the main Publisher and the last inner Publisher, if any, complete. + * If the main Publisher signals an onError, the termination of the last inner Publisher will emit that error as is + * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signaled. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Publisher}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code switchOnNextDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the item type + * @param sources + * the source Publisher that emits Publishers + * @param prefetch + * the number of items to prefetch from the inner Publishers + * @return a Flowable that emits the items emitted by the Publisher most recently emitted by the source + * Publisher + * @see ReactiveX operators documentation: Switch + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable switchOnNextDelayError(Publisher> sources, int prefetch) { + return fromPublisher(sources).switchMapDelayError(Functions.>identity(), prefetch); + } + + /** + * Returns a Flowable that emits {@code 0L} after a specified delay, and then completes. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. If the downstream needs a slower rate + * it should slow the timer or use something like {@link #onBackpressureDrop}.
+ *
Scheduler:
+ *
{@code timer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the initial delay before emitting a single {@code 0L} + * @param unit + * time units to use for {@code delay} + * @return a Flowable that emits {@code 0L} after a specified delay, and then completes + * @see ReactiveX operators documentation: Timer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Flowable timer(long delay, TimeUnit unit) { + return timer(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits {@code 0L} after a specified delay, on a specified Scheduler, and then + * completes. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. If the downstream needs a slower rate + * it should slow the timer or use something like {@link #onBackpressureDrop}.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the initial delay before emitting a single 0L + * @param unit + * time units to use for {@code delay} + * @param scheduler + * the {@link Scheduler} to use for scheduling the item + * @return a Flowable that emits {@code 0L} after a specified delay, on a specified Scheduler, and then + * completes + * @see ReactiveX operators documentation: Timer + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Flowable timer(long delay, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + + return RxJavaPlugins.onAssembly(new FlowableTimer(Math.max(0L, delay), unit, scheduler)); + } + + /** + * Create a Flowable by wrapping a Publisher which has to be implemented according + * to the Reactive Streams specification by handling backpressure and + * cancellation correctly; no safeguards are provided by the Flowable itself. + *
+ *
Backpressure:
+ *
This operator is a pass-through for backpressure and the behavior is determined by the + * provided Publisher implementation.
+ *
Scheduler:
+ *
{@code unsafeCreate} by default doesn't operate on any particular {@link Scheduler}.
+ *
+ * @param the value type emitted + * @param onSubscribe the Publisher instance to wrap + * @return the new Flowable instance + * @throws IllegalArgumentException if {@code onSubscribe} is a subclass of {@code Flowable}; such + * instances don't need conversion and is possibly a port remnant from 1.x or one should use {@link #hide()} + * instead. + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.NONE) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable unsafeCreate(Publisher onSubscribe) { + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + if (onSubscribe instanceof Flowable) { + throw new IllegalArgumentException("unsafeCreate(Flowable) should be upgraded"); + } + return RxJavaPlugins.onAssembly(new FlowableFromPublisher(onSubscribe)); + } + + /** + * Constructs a Publisher that creates a dependent resource object which is disposed of on cancellation. + *

+ * + *

+ *
Backpressure:
+ *
The operator is a pass-through for backpressure and otherwise depends on the + * backpressure support of the Publisher returned by the {@code resourceFactory}.
+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the generated Publisher + * @param the type of the resource associated with the output sequence + * @param resourceSupplier + * the factory function to create a resource object that depends on the Publisher + * @param sourceSupplier + * the factory function to create a Publisher + * @param resourceDisposer + * the function that will dispose of the resource + * @return the Publisher whose lifetime controls the lifetime of the dependent resource object + * @see ReactiveX operators documentation: Using + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable using(Callable resourceSupplier, + Function> sourceSupplier, Consumer resourceDisposer) { + return using(resourceSupplier, sourceSupplier, resourceDisposer, true); + } + + /** + * Constructs a Publisher that creates a dependent resource object which is disposed of just before + * termination if you have set {@code disposeEagerly} to {@code true} and cancellation does not occur + * before termination. Otherwise, resource disposal will occur on cancellation. Eager disposal is + * particularly appropriate for a synchronous Publisher that reuses resources. {@code disposeAction} will + * only be called once per subscription. + *

+ * + *

+ *
Backpressure:
+ *
The operator is a pass-through for backpressure and otherwise depends on the + * backpressure support of the Publisher returned by the {@code resourceFactory}.
+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the generated Publisher + * @param the type of the resource associated with the output sequence + * @param resourceSupplier + * the factory function to create a resource object that depends on the Publisher + * @param sourceSupplier + * the factory function to create a Publisher + * @param resourceDisposer + * the function that will dispose of the resource + * @param eager + * if {@code true} then disposal will happen either on cancellation or just before emission of + * a terminal event ({@code onComplete} or {@code onError}). + * @return the Publisher whose lifetime controls the lifetime of the dependent resource object + * @see ReactiveX operators documentation: Using + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable using(Callable resourceSupplier, + Function> sourceSupplier, + Consumer resourceDisposer, boolean eager) { + ObjectHelper.requireNonNull(resourceSupplier, "resourceSupplier is null"); + ObjectHelper.requireNonNull(sourceSupplier, "sourceSupplier is null"); + ObjectHelper.requireNonNull(resourceDisposer, "resourceDisposer is null"); + return RxJavaPlugins.onAssembly(new FlowableUsing(resourceSupplier, sourceSupplier, resourceDisposer, eager)); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * items emitted, in sequence, by an Iterable of other Publishers. + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by each of the source Publishers; + * the second item emitted by the new Publisher will be the result of the function applied to the second + * item emitted by each of those Publishers; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@code onNext} as many times as + * the number of {@code onNext} invocations of the source Publisher that emits the fewest items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(Arrays.asList(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2)), (a) -> a)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common value type + * @param the zipped result type + * @param sources + * an Iterable of source Publishers + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip(Iterable> sources, Function zipper) { + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new FlowableZip(null, sources, zipper, bufferSize(), false)); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * n items emitted, in sequence, by the n Publishers emitted by a specified Publisher. + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by each of the Publishers emitted + * by the source Publisher; the second item emitted by the new Publisher will be the result of the + * function applied to the second item emitted by each of those Publishers; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@code onNext} as many times as + * the number of {@code onNext} invocations of the source Publisher that emits the fewest items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while cancel the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(just(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2)), (a) -> a)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner Publishers + * @param the zipped result type + * @param sources + * a Publisher of source Publishers + * @param zipper + * a function that, when applied to an item emitted by each of the Publishers emitted by + * {@code ws}, results in an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings({ "rawtypes", "unchecked", "cast" }) + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip(Publisher> sources, + final Function zipper) { + ObjectHelper.requireNonNull(zipper, "zipper is null"); + return fromPublisher(sources).toList().flatMapPublisher((Function)FlowableInternalHelper.zipIterable(zipper)); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * two items emitted, in sequence, by two other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by {@code o1} and the first item + * emitted by {@code o2}; the second item emitted by the new Publisher will be the result of the function + * applied to the second item emitted by {@code o1} and the second item emitted by {@code o2}; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results + * in an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, + BiFunction zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * two items emitted, in sequence, by two other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by {@code o1} and the first item + * emitted by {@code o2}; the second item emitted by the new Publisher will be the result of the function + * applied to the second item emitted by {@code o1} and the second item emitted by {@code o2}; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results + * in an item that will be emitted by the resulting Publisher + * @param delayError delay errors from any of the source Publishers till the other terminates + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, + BiFunction zipper, boolean delayError) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return zipArray(Functions.toFunction(zipper), delayError, bufferSize(), source1, source2); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * two items emitted, in sequence, by two other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by {@code o1} and the first item + * emitted by {@code o2}; the second item emitted by the new Publisher will be the result of the function + * applied to the second item emitted by {@code o1} and the second item emitted by {@code o2}; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results + * in an item that will be emitted by the resulting Publisher + * @param delayError delay errors from any of the source Publishers till the other terminates + * @param bufferSize the number of elements to prefetch from each source Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, + BiFunction zipper, boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return zipArray(Functions.toFunction(zipper), delayError, bufferSize, source1, source2); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * three items emitted, in sequence, by three other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by {@code o1}, the first item + * emitted by {@code o2}, and the first item emitted by {@code o3}; the second item emitted by the new + * Publisher will be the result of the function applied to the second item emitted by {@code o1}, the + * second item emitted by {@code o2}, and the second item emitted by {@code o3}; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param source3 + * a third source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, Publisher source3, + Function3 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * four items emitted, in sequence, by four other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by {@code o1}, the first item + * emitted by {@code o2}, the first item emitted by {@code o3}, and the first item emitted by {@code 04}; + * the second item emitted by the new Publisher will be the result of the function applied to the second + * item emitted by each of those Publishers; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param source3 + * a third source Publisher + * @param source4 + * a fourth source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, Publisher source3, + Publisher source4, + Function4 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * five items emitted, in sequence, by five other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by {@code o1}, the first item + * emitted by {@code o2}, the first item emitted by {@code o3}, the first item emitted by {@code o4}, and + * the first item emitted by {@code o5}; the second item emitted by the new Publisher will be the result of + * the function applied to the second item emitted by each of those Publishers; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param source3 + * a third source Publisher + * @param source4 + * a fourth source Publisher + * @param source5 + * a fifth source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, Publisher source3, + Publisher source4, Publisher source5, + Function5 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * six items emitted, in sequence, by six other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by each source Publisher, the + * second item emitted by the new Publisher will be the result of the function applied to the second item + * emitted by each of those Publishers, and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e, f) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param source3 + * a third source Publisher + * @param source4 + * a fourth source Publisher + * @param source5 + * a fifth source Publisher + * @param source6 + * a sixth source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, Publisher source3, + Publisher source4, Publisher source5, Publisher source6, + Function6 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5, source6); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * seven items emitted, in sequence, by seven other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by each source Publisher, the + * second item emitted by the new Publisher will be the result of the function applied to the second item + * emitted by each of those Publishers, and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e, f, g) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the value type of the seventh source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param source3 + * a third source Publisher + * @param source4 + * a fourth source Publisher + * @param source5 + * a fifth source Publisher + * @param source6 + * a sixth source Publisher + * @param source7 + * a seventh source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, Publisher source3, + Publisher source4, Publisher source5, Publisher source6, + Publisher source7, + Function7 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5, source6, source7); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * eight items emitted, in sequence, by eight other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by each source Publisher, the + * second item emitted by the new Publisher will be the result of the function applied to the second item + * emitted by each of those Publishers, and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e, f, g, h) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the value type of the seventh source + * @param the value type of the eighth source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param source3 + * a third source Publisher + * @param source4 + * a fourth source Publisher + * @param source5 + * a fifth source Publisher + * @param source6 + * a sixth source Publisher + * @param source7 + * a seventh source Publisher + * @param source8 + * an eighth source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, Publisher source3, + Publisher source4, Publisher source5, Publisher source6, + Publisher source7, Publisher source8, + Function8 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5, source6, source7, source8); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * nine items emitted, in sequence, by nine other Publishers. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by each source Publisher, the + * second item emitted by the new Publisher will be the result of the function applied to the second item + * emitted by each of those Publishers, and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@link Subscriber#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source Publisher that emits the fewest + * items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e, f, g, h, i) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *
+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the value type of the seventh source + * @param the value type of the eighth source + * @param the value type of the ninth source + * @param the zipped result type + * @param source1 + * the first source Publisher + * @param source2 + * a second source Publisher + * @param source3 + * a third source Publisher + * @param source4 + * a fourth source Publisher + * @param source5 + * a fifth source Publisher + * @param source6 + * a sixth source Publisher + * @param source7 + * a seventh source Publisher + * @param source8 + * an eighth source Publisher + * @param source9 + * a ninth source Publisher + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zip( + Publisher source1, Publisher source2, Publisher source3, + Publisher source4, Publisher source5, Publisher source6, + Publisher source7, Publisher source8, Publisher source9, + Function9 zipper) { + + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + ObjectHelper.requireNonNull(source9, "source9 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5, source6, source7, source8, source9); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * items emitted, in sequence, by an array of other Publishers. + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by each of the source Publishers; + * the second item emitted by the new Publisher will be the result of the function applied to the second + * item emitted by each of those Publishers; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@code onNext} as many times as + * the number of {@code onNext} invocations of the source Publisher that emits the fewest items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(new Publisher[]{range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2)}, (a) ->
+     * a)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zipArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element type + * @param the result type + * @param sources + * an array of source Publishers + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @param delayError + * delay errors signaled by any of the source Publisher until all Publishers terminate + * @param bufferSize + * the number of elements to prefetch from each source Publisher + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zipArray(Function zipper, + boolean delayError, int bufferSize, Publisher... sources) { + if (sources.length == 0) { + return empty(); + } + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableZip(sources, null, zipper, bufferSize, delayError)); + } + + /** + * Returns a Flowable that emits the results of a specified combiner function applied to combinations of + * items emitted, in sequence, by an Iterable of other Publishers. + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new Publisher + * will be the result of the function applied to the first item emitted by each of the source Publishers; + * the second item emitted by the new Publisher will be the result of the function applied to the second + * item emitted by each of those Publishers; and so forth. + *

+ * The resulting {@code Publisher} returned from {@code zip} will invoke {@code onNext} as many times as + * the number of {@code onNext} invocations of the source Publisher that emits the fewest items. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

zip(Arrays.asList(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2)), (a) -> a)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zipIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * + * @param sources + * an Iterable of source Publishers + * @param zipper + * a function that, when applied to an item emitted by each of the source Publishers, results in + * an item that will be emitted by the resulting Publisher + * @param delayError + * delay errors signaled by any of the source Publisher until all Publishers terminate + * @param bufferSize + * the number of elements to prefetch from each source Publisher + * @param the common source value type + * @param the zipped result type + * @return a Flowable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable zipIterable(Iterable> sources, + Function zipper, boolean delayError, + int bufferSize) { + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableZip(null, sources, zipper, bufferSize, delayError)); + } + + // *************************************************************************************************** + // Instance operators + // *************************************************************************************************** + + /** + * Returns a Single that emits a Boolean that indicates whether all of the items emitted by the source + * Publisher satisfy a condition. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code all} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function that evaluates an item and returns a Boolean + * @return a Single that emits {@code true} if all items emitted by the source Publisher satisfy the + * predicate; otherwise, {@code false} + * @see ReactiveX operators documentation: All + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single all(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new FlowableAllSingle(this, predicate)); + } + + /** + * Mirrors the Publisher (current or provided) that first either emits an item or sends a termination + * notification. + *

+ * + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the winning + * {@code Publisher}'s backpressure behavior.
+ *
Scheduler:
+ *
{@code ambWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * a Publisher competing to react first. A subscription to this provided Publisher will occur after subscribing + * to the current Publisher. + * @return a Flowable that emits the same sequence as whichever of the source Publishers first + * emitted an item or sent a termination notification + * @see ReactiveX operators documentation: Amb + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable ambWith(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return ambArray(this, other); + } + + /** + * Returns a Single that emits {@code true} if any item emitted by the source Publisher satisfies a + * specified condition, otherwise {@code false}. Note: this always emits {@code false} if the + * source Publisher is empty. + *

+ * + *

+ * In Rx.Net this is the {@code any} operator but we renamed it in RxJava to better match Java naming + * idioms. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code any} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * the condition to test items emitted by the source Publisher + * @return a Single that emits a Boolean that indicates whether any item emitted by the source + * Publisher satisfies the {@code predicate} + * @see ReactiveX operators documentation: Contains + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single any(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new FlowableAnySingle(this, predicate)); + } + + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * This allows fluent conversion to any other type. + *

+ *
Backpressure:
+ *
The backpressure behavior depends on what happens in the {@code converter} function.
+ *
Scheduler:
+ *
{@code as} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.7 - experimental + * @param the resulting object type + * @param converter the function that receives the current Flowable instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final R as(@NonNull FlowableConverter converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + + /** + * Returns the first item emitted by this {@code Flowable}, or throws + * {@code NoSuchElementException} if it emits no items. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingFirst} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @return the first item emitted by this {@code Flowable} + * @throws NoSuchElementException + * if this {@code Flowable} emits no items + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingFirst() { + BlockingFirstSubscriber s = new BlockingFirstSubscriber(); + subscribe(s); + T v = s.blockingGet(); + if (v != null) { + return v; + } + throw new NoSuchElementException(); + } + + /** + * Returns the first item emitted by this {@code Flowable}, or a default value if it emits no + * items. + *
+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingFirst} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @param defaultItem + * a default value to return if this {@code Flowable} emits no items + * @return the first item emitted by this {@code Flowable}, or the default value if it emits no + * items + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingFirst(T defaultItem) { + BlockingFirstSubscriber s = new BlockingFirstSubscriber(); + subscribe(s); + T v = s.blockingGet(); + return v != null ? v : defaultItem; + } + + /** + * Consumes the upstream {@code Flowable} in a blocking fashion and invokes the given + * {@code Consumer} with each upstream item on the current thread until the + * upstream terminates. + *

+ * + *

+ * Note: the method will only return if the upstream terminates or the current + * thread is interrupted. + *

+ * This method executes the {@code Consumer} on the current thread while + * {@link #subscribe(Consumer)} executes the consumer on the original caller thread of the + * sequence. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingForEach} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @param onNext + * the {@link Consumer} to invoke for each item emitted by the {@code Flowable} + * @throws RuntimeException + * if an error occurs + * @see ReactiveX documentation: Subscribe + * @see #subscribe(Consumer) + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingForEach(Consumer onNext) { + Iterator it = blockingIterable().iterator(); + while (it.hasNext()) { + try { + onNext.accept(it.next()); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + ((Disposable)it).dispose(); + throw ExceptionHelper.wrapOrThrow(e); + } + } + } + + /** + * Converts this {@code Flowable} into an {@link Iterable}. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to honor backpressure otherwise the returned + * Iterable's iterator will throw a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code blockingIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an {@link Iterable} version of this {@code Flowable} + * @see ReactiveX documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingIterable() { + return blockingIterable(bufferSize()); + } + + /** + * Converts this {@code Flowable} into an {@link Iterable}. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to honor backpressure otherwise the returned + * Iterable's iterator will throw a {@code MissingBackpressureException}. + *
+ *
Scheduler:
+ *
{@code blockingIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param bufferSize the number of items to prefetch from the current Flowable + * @return an {@link Iterable} version of this {@code Flowable} + * @see ReactiveX documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingIterable(int bufferSize) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return new BlockingFlowableIterable(this, bufferSize); + } + + /** + * Returns the last item emitted by this {@code Flowable}, or throws + * {@code NoSuchElementException} if this {@code Flowable} emits no items. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingLast} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @return the last item emitted by this {@code Flowable} + * @throws NoSuchElementException + * if this {@code Flowable} emits no items + * @see ReactiveX documentation: Last + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingLast() { + BlockingLastSubscriber s = new BlockingLastSubscriber(); + subscribe(s); + T v = s.blockingGet(); + if (v != null) { + return v; + } + throw new NoSuchElementException(); + } + + /** + * Returns the last item emitted by this {@code Flowable}, or a default value if it emits no + * items. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingLast} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @param defaultItem + * a default value to return if this {@code Flowable} emits no items + * @return the last item emitted by the {@code Flowable}, or the default value if it emits no + * items + * @see ReactiveX documentation: Last + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingLast(T defaultItem) { + BlockingLastSubscriber s = new BlockingLastSubscriber(); + subscribe(s); + T v = s.blockingGet(); + return v != null ? v : defaultItem; + } + + /** + * Returns an {@link Iterable} that returns the latest item emitted by this {@code Flowable}, + * waiting if necessary for one to become available. + *

+ * If this {@code Flowable} produces items faster than {@code Iterator.next} takes them, + * {@code onNext} events might be skipped, but {@code onError} or {@code onComplete} events are not. + *

+ * Note also that an {@code onNext} directly followed by {@code onComplete} might hide the {@code onNext} + * event. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an Iterable that always returns the latest item emitted by this {@code Flowable} + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingLatest() { + return new BlockingFlowableLatest(this); + } + + /** + * Returns an {@link Iterable} that always returns the item most recently emitted by this + * {@code Flowable}. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingMostRecent} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param initialItem + * the initial item that the {@link Iterable} sequence will yield if this + * {@code Flowable} has not yet emitted an item + * @return an {@link Iterable} that on each iteration returns the item that this {@code Flowable} + * has most recently emitted + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingMostRecent(T initialItem) { + return new BlockingFlowableMostRecent(this, initialItem); + } + + /** + * Returns an {@link Iterable} that blocks until this {@code Flowable} emits another item, then + * returns that item. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an {@link Iterable} that blocks upon each iteration until this {@code Flowable} emits + * a new item, whereupon the Iterable returns that item + * @see ReactiveX documentation: TakeLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingNext() { + return new BlockingFlowableNext(this); + } + + /** + * If this {@code Flowable} completes after emitting a single item, return that item, otherwise + * throw a {@code NoSuchElementException}. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingSingle} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @return the single item emitted by this {@code Flowable} + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingSingle() { + return singleOrError().blockingGet(); + } + + /** + * If this {@code Flowable} completes after emitting a single item, return that item; if it emits + * more than one item, throw an {@code IllegalArgumentException}; if it emits no items, return a default + * value. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingSingle} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @param defaultItem + * a default value to return if this {@code Flowable} emits no items + * @return the single item emitted by this {@code Flowable}, or the default value if it emits no + * items + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingSingle(T defaultItem) { + return single(defaultItem).blockingGet(); + } + + /** + * Returns a {@link Future} representing the only value emitted by this {@code Flowable}. + *

+ * + *

+ * If the {@link Flowable} emits more than one item, {@link Future} will receive an + * {@link IndexOutOfBoundsException}. If the {@link Flowable} is empty, {@link Future} + * will receive a {@link NoSuchElementException}. The {@code Flowable} source has to terminate in order + * for the returned {@code Future} to terminate as well. + *

+ * If the {@code Flowable} may emit more than one item, use {@code Flowable.toList().toFuture()}. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code toFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Future} that expects a single item to be emitted by this {@code Flowable} + * @see ReactiveX documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Future toFuture() { + return subscribeWith(new FutureSubscriber()); + } + + /** + * Runs the source Flowable to a terminal event, ignoring any values and rethrowing any exception. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @since 2.0 + * @see #blockingSubscribe(Consumer) + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe() { + FlowableBlockingSubscribe.subscribe(this); + } + + /** + * Subscribes to the source and calls the given callbacks on the current thread. + *

+ * If the Flowable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + * Using the overloads {@link #blockingSubscribe(Consumer, Consumer)} + * or {@link #blockingSubscribe(Consumer, Consumer, Action)} instead is recommended. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onNext the callback action for each source value + * @since 2.0 + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer onNext) { + FlowableBlockingSubscribe.subscribe(this, onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to the source and calls the given callbacks on the current thread. + *

+ * If the Flowable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + * Using the overloads {@link #blockingSubscribe(Consumer, Consumer)} + * or {@link #blockingSubscribe(Consumer, Consumer, Action)} instead is recommended. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an bounded manner (up to bufferSize + * outstanding request amount for items).
+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.15 - experimental + * @param onNext the callback action for each source value + * @param bufferSize the size of the buffer + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer onNext, int bufferSize) { + FlowableBlockingSubscribe.subscribe(this, onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION, bufferSize); + } + + /** + * Subscribes to the source and calls the given callbacks on the current thread. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @since 2.0 + * @see #blockingSubscribe(Consumer, Consumer, Action) + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer onNext, Consumer onError) { + FlowableBlockingSubscribe.subscribe(this, onNext, onError, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to the source and calls the given callbacks on the current thread. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an bounded manner (up to bufferSize + * outstanding request amount for items).
+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.15 - experimental + * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param bufferSize the size of the buffer + * @since 2.2 + * @see #blockingSubscribe(Consumer, Consumer, Action) + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer onNext, Consumer onError, + int bufferSize) { + FlowableBlockingSubscribe.subscribe(this, onNext, onError, Functions.EMPTY_ACTION, bufferSize); + } + + /** + * Subscribes to the source and calls the given callbacks on the current thread. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param onComplete the callback action for the completion event. + * @since 2.0 + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer onNext, Consumer onError, Action onComplete) { + FlowableBlockingSubscribe.subscribe(this, onNext, onError, onComplete); + } + + /** + * Subscribes to the source and calls the given callbacks on the current thread. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Flowable} in an bounded manner (up to bufferSize + * outstanding request amount for items).
+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.15 - experimental + * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param onComplete the callback action for the completion event. + * @param bufferSize the size of the buffer + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer onNext, Consumer onError, Action onComplete, + int bufferSize) { + FlowableBlockingSubscribe.subscribe(this, onNext, onError, onComplete, bufferSize); + } + + /** + * Subscribes to the source and calls the {@link Subscriber} methods on the current thread. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally, with an error or the {@code Subscriber} cancels the {@link Subscription} it receives via + * {@link Subscriber#onSubscribe(Subscription)}. + * Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Backpressure:
+ *
The supplied {@code Subscriber} determines how backpressure is applied.
+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * The cancellation and backpressure is composed through. + * @param subscriber the subscriber to forward events and calls to in the current thread + * @since 2.0 + */ + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Subscriber subscriber) { + FlowableBlockingSubscribe.subscribe(this, subscriber); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping buffers, each containing {@code count} items. When the source + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and expects the source {@code Publisher} to honor it as + * well, although not enforced; violation may lead to {@code MissingBackpressureException} somewhere + * downstream.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum number of items in each buffer before it should be emitted + * @return a Flowable that emits connected, non-overlapping buffers, each containing at most + * {@code count} items from the source Publisher + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> buffer(int count) { + return buffer(count, count); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits buffers every {@code skip} items, each containing {@code count} items. When the source + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and expects the source {@code Publisher} to honor it as + * well, although not enforced; violation may lead to {@code MissingBackpressureException} somewhere + * downstream.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum size of each buffer before it should be emitted + * @param skip + * how many items emitted by the source Publisher should be skipped before starting a new + * buffer. Note that when {@code skip} and {@code count} are equal, this is the same operation as + * {@link #buffer(int)}. + * @return a Flowable that emits buffers for every {@code skip} item from the source Publisher and + * containing at most {@code count} items + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> buffer(int count, int skip) { + return buffer(count, skip, ArrayListSupplier.asCallable()); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits buffers every {@code skip} items, each containing {@code count} items. When the source + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and expects the source {@code Publisher} to honor it as + * well, although not enforced; violation may lead to {@code MissingBackpressureException} somewhere + * downstream.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param count + * the maximum size of each buffer before it should be emitted + * @param skip + * how many items emitted by the source Publisher should be skipped before starting a new + * buffer. Note that when {@code skip} and {@code count} are equal, this is the same operation as + * {@link #buffer(int)}. + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return a Flowable that emits buffers for every {@code skip} item from the source Publisher and + * containing at most {@code count} items + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final > Flowable buffer(int count, int skip, Callable bufferSupplier) { + ObjectHelper.verifyPositive(count, "count"); + ObjectHelper.verifyPositive(skip, "skip"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new FlowableBuffer(this, count, skip, bufferSupplier)); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping buffers, each containing {@code count} items. When the source + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and expects the source {@code Publisher} to honor it as + * well, although not enforced; violation may lead to {@code MissingBackpressureException} somewhere + * downstream.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param count + * the maximum number of items in each buffer before it should be emitted + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return a Flowable that emits connected, non-overlapping buffers, each containing at most + * {@code count} items from the source Publisher + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final > Flowable buffer(int count, Callable bufferSupplier) { + return buffer(count, count, bufferSupplier); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher starts a new buffer periodically, as determined by the {@code timeskip} argument. It emits + * each buffer after a fixed timespan, specified by the {@code timespan} argument. When the source + * Publisher completes, the resulting Publisher emits the current buffer and propagates the notification from the + * source Publisher. Note that if the source Publisher issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} + * upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted + * @param timeskip + * the period of time after which a new buffer will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @return a Flowable that emits new buffers of items emitted by the source Publisher periodically after + * a fixed timespan has elapsed + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable> buffer(long timespan, long timeskip, TimeUnit unit) { + return buffer(timespan, timeskip, unit, Schedulers.computation(), ArrayListSupplier.asCallable()); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher starts a new buffer periodically, as determined by the {@code timeskip} argument, and on the + * specified {@code scheduler}. It emits each buffer after a fixed timespan, specified by the + * {@code timespan} argument. When the source Publisher completes, the resulting Publisher emits the current buffer + * and propagates the notification from the source Publisher. Note that if the source Publisher issues an onError + * notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} + * upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted + * @param timeskip + * the period of time after which a new buffer will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @return a Flowable that emits new buffers of items emitted by the source Publisher periodically after + * a fixed timespan has elapsed + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable> buffer(long timespan, long timeskip, TimeUnit unit, Scheduler scheduler) { + return buffer(timespan, timeskip, unit, scheduler, ArrayListSupplier.asCallable()); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher starts a new buffer periodically, as determined by the {@code timeskip} argument, and on the + * specified {@code scheduler}. It emits each buffer after a fixed timespan, specified by the + * {@code timespan} argument. When the source Publisher completes, the resulting Publisher emits the current buffer + * and propagates the notification from the source Publisher. Note that if the source Publisher issues an onError + * notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} + * upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param the collection subclass type to buffer into + * @param timespan + * the period of time each buffer collects items before it is emitted + * @param timeskip + * the period of time after which a new buffer will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return a Flowable that emits new buffers of items emitted by the source Publisher periodically after + * a fixed timespan has elapsed + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final > Flowable buffer(long timespan, long timeskip, TimeUnit unit, + Scheduler scheduler, Callable bufferSupplier) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new FlowableBufferTimed(this, timespan, timeskip, unit, scheduler, bufferSupplier, Integer.MAX_VALUE, false)); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument. When the source Publisher completes, the resulting Publisher emits the current buffer + * and propagates the notification from the source Publisher. Note that if the source Publisher issues an onError + * notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} + * upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time that applies to the {@code timespan} argument + * @return a Flowable that emits connected, non-overlapping buffers of items emitted by the source + * Publisher within a fixed duration + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable> buffer(long timespan, TimeUnit unit) { + return buffer(timespan, unit, Schedulers.computation(), Integer.MAX_VALUE); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source Publisher completes, the resulting Publisher emits the current buffer and propagates the + * notification from the source Publisher. Note that if the source Publisher issues an onError notification the event + * is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} + * upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each buffer before it is emitted + * @return a Flowable that emits connected, non-overlapping buffers of items emitted by the source + * Publisher, after a fixed duration or when the buffer reaches maximum capacity (whichever occurs + * first) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable> buffer(long timespan, TimeUnit unit, int count) { + return buffer(timespan, unit, Schedulers.computation(), count); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument as measured on the specified {@code scheduler}, or a maximum size specified by + * the {@code count} argument (whichever is reached first). When the source Publisher completes, the resulting + * Publisher emits the current buffer and propagates the notification from the source Publisher. Note that if the + * source Publisher issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} + * upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @param count + * the maximum size of each buffer before it is emitted + * @return a Flowable that emits connected, non-overlapping buffers of items emitted by the source + * Publisher after a fixed duration or when the buffer reaches maximum capacity (whichever occurs + * first) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable> buffer(long timespan, TimeUnit unit, Scheduler scheduler, int count) { + return buffer(timespan, unit, scheduler, count, ArrayListSupplier.asCallable(), false); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument as measured on the specified {@code scheduler}, or a maximum size specified by + * the {@code count} argument (whichever is reached first). When the source Publisher completes, the resulting + * Publisher emits the current buffer and propagates the notification from the source Publisher. Note that if the + * source Publisher issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} + * upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param the collection subclass type to buffer into + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @param count + * the maximum size of each buffer before it is emitted + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @param restartTimerOnMaxSize if true the time window is restarted when the max capacity of the current buffer + * is reached + * @return a Flowable that emits connected, non-overlapping buffers of items emitted by the source + * Publisher after a fixed duration or when the buffer reaches maximum capacity (whichever occurs + * first) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final > Flowable buffer( + long timespan, TimeUnit unit, + Scheduler scheduler, int count, + Callable bufferSupplier, + boolean restartTimerOnMaxSize) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + ObjectHelper.verifyPositive(count, "count"); + return RxJavaPlugins.onAssembly(new FlowableBufferTimed(this, timespan, timespan, unit, scheduler, bufferSupplier, count, restartTimerOnMaxSize)); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument and on the specified {@code scheduler}. When the source Publisher completes, the + * resulting Publisher emits the current buffer and propagates the notification from the source Publisher. Note that + * if the source Publisher issues an onError notification the event is passed on immediately without first emitting + * the buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time. It requests {@code Long.MAX_VALUE} + * upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @return a Flowable that emits connected, non-overlapping buffers of items emitted by the source + * Publisher within a fixed duration + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable> buffer(long timespan, TimeUnit unit, Scheduler scheduler) { + return buffer(timespan, unit, scheduler, Integer.MAX_VALUE, ArrayListSupplier.asCallable(), false); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits buffers that it creates when the specified {@code openingIndicator} Publisher emits an + * item, and closes when the Publisher returned from {@code closingIndicator} emits an item. If any of the source + * Publisher, {@code openingIndicator} or {@code closingIndicator} issues an onError notification the event is passed + * on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it is instead controlled by the given Publishers and + * buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the buffer-opening Publisher + * @param the element type of the individual buffer-closing Publishers + * @param openingIndicator + * the Publisher that, when it emits an item, causes a new buffer to be created + * @param closingIndicator + * the {@link Function} that is used to produce a Publisher for every buffer created. When this + * Publisher emits an item, the associated buffer is emitted. + * @return a Flowable that emits buffers, containing items from the source Publisher, that are created + * and closed when the specified Publishers emit items + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> buffer( + Flowable openingIndicator, + Function> closingIndicator) { + return buffer(openingIndicator, closingIndicator, ArrayListSupplier.asCallable()); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits buffers that it creates when the specified {@code openingIndicator} Publisher emits an + * item, and closes when the Publisher returned from {@code closingIndicator} emits an item. If any of the source + * Publisher, {@code openingIndicator} or {@code closingIndicator} issues an onError notification the event is passed + * on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it is instead controlled by the given Publishers and + * buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param the element type of the buffer-opening Publisher + * @param the element type of the individual buffer-closing Publishers + * @param openingIndicator + * the Publisher that, when it emits an item, causes a new buffer to be created + * @param closingIndicator + * the {@link Function} that is used to produce a Publisher for every buffer created. When this + * Publisher emits an item, the associated buffer is emitted. + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return a Flowable that emits buffers, containing items from the source Publisher, that are created + * and closed when the specified Publishers emit items + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final > Flowable buffer( + Flowable openingIndicator, + Function> closingIndicator, + Callable bufferSupplier) { + ObjectHelper.requireNonNull(openingIndicator, "openingIndicator is null"); + ObjectHelper.requireNonNull(closingIndicator, "closingIndicator is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new FlowableBufferBoundary(this, openingIndicator, closingIndicator, bufferSupplier)); + } + + /** + * Returns a Flowable that emits non-overlapping buffered items from the source Publisher each time the + * specified boundary Publisher emits an item. + *

+ * + *

+ * Completion of either the source or the boundary Publisher causes the returned Publisher to emit the + * latest buffer and complete. If either the source Publisher or the boundary Publisher issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it is instead controlled by the {@code Publisher} + * {@code boundary} and buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey + * downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the boundary value type (ignored) + * @param boundaryIndicator + * the boundary Publisher + * @return a Flowable that emits buffered items from the source Publisher when the boundary Publisher + * emits an item + * @see #buffer(Publisher, int) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> buffer(Publisher boundaryIndicator) { + return buffer(boundaryIndicator, ArrayListSupplier.asCallable()); + } + + /** + * Returns a Flowable that emits non-overlapping buffered items from the source Publisher each time the + * specified boundary Publisher emits an item. + *

+ * + *

+ * Completion of either the source or the boundary Publisher causes the returned Publisher to emit the + * latest buffer and complete. If either the source Publisher or the boundary Publisher issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it is instead controlled by the {@code Publisher} + * {@code boundary} and buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey + * downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the boundary value type (ignored) + * @param boundaryIndicator + * the boundary Publisher + * @param initialCapacity + * the initial capacity of each buffer chunk + * @return a Flowable that emits buffered items from the source Publisher when the boundary Publisher + * emits an item + * @see ReactiveX operators documentation: Buffer + * @see #buffer(Publisher) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> buffer(Publisher boundaryIndicator, final int initialCapacity) { + ObjectHelper.verifyPositive(initialCapacity, "initialCapacity"); + return buffer(boundaryIndicator, Functions.createArrayList(initialCapacity)); + } + + /** + * Returns a Flowable that emits non-overlapping buffered items from the source Publisher each time the + * specified boundary Publisher emits an item. + *

+ * + *

+ * Completion of either the source or the boundary Publisher causes the returned Publisher to emit the + * latest buffer and complete. If either the source Publisher or the boundary Publisher issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it is instead controlled by the {@code Publisher} + * {@code boundary} and buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey + * downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param + * the boundary value type (ignored) + * @param boundaryIndicator + * the boundary Publisher + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return a Flowable that emits buffered items from the source Publisher when the boundary Publisher + * emits an item + * @see #buffer(Publisher, int) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final > Flowable buffer(Publisher boundaryIndicator, Callable bufferSupplier) { + ObjectHelper.requireNonNull(boundaryIndicator, "boundaryIndicator is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new FlowableBufferExactBoundary(this, boundaryIndicator, bufferSupplier)); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a + * new buffer whenever the Publisher produced by the specified {@code boundaryIndicatorSupplier} emits an item. + *

+ * + *

+ * If either the source {@code Publisher} or the boundary {@code Publisher} issues an {@code onError} notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it is instead controlled by the given Publishers and + * buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the boundary-providing Publisher + * @param boundaryIndicatorSupplier + * a {@link Callable} that produces a Publisher that governs the boundary between buffers. + * Whenever the supplied {@code Publisher} emits an item, {@code buffer} emits the current buffer and + * begins to fill a new one + * @return a Flowable that emits a connected, non-overlapping buffer of items from the source Publisher + * each time the Publisher created with the {@code closingIndicator} argument emits an item + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> buffer(Callable> boundaryIndicatorSupplier) { + return buffer(boundaryIndicatorSupplier, ArrayListSupplier.asCallable()); + } + + /** + * Returns a Flowable that emits buffers of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a + * new buffer whenever the Publisher produced by the specified {@code boundaryIndicatorSupplier} emits an item. + *

+ * + *

+ * If either the source {@code Publisher} or the boundary {@code Publisher} issues an {@code onError} notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it is instead controlled by the given Publishers and + * buffers data. It requests {@code Long.MAX_VALUE} upstream and does not obey downstream requests.
+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param the value type of the boundary-providing Publisher + * @param boundaryIndicatorSupplier + * a {@link Callable} that produces a Publisher that governs the boundary between buffers. + * Whenever the supplied {@code Publisher} emits an item, {@code buffer} emits the current buffer and + * begins to fill a new one + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return a Flowable that emits a connected, non-overlapping buffer of items from the source Publisher + * each time the Publisher created with the {@code closingIndicator} argument emits an item + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final > Flowable buffer(Callable> boundaryIndicatorSupplier, + Callable bufferSupplier) { + ObjectHelper.requireNonNull(boundaryIndicatorSupplier, "boundaryIndicatorSupplier is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new FlowableBufferBoundarySupplier(this, boundaryIndicatorSupplier, bufferSupplier)); + } + + /** + * Returns a Flowable that subscribes to this Publisher lazily, caches all of its events + * and replays them, in the same order as received, to all the downstream subscribers. + *

+ * + *

+ * This is useful when you want a Publisher to cache responses and you can't control the + * subscribe/cancel behavior of all the {@link Subscriber}s. + *

+ * The operator subscribes only when the first downstream subscriber subscribes and maintains + * a single subscription towards this Publisher. In contrast, the operator family of {@link #replay()} + * that return a {@link ConnectableFlowable} require an explicit call to {@link ConnectableFlowable#connect()}. + *

+ * Note: You sacrifice the ability to cancel the origin when you use the {@code cache} + * Subscriber so be careful not to use this Subscriber on Publishers that emit an infinite or very large number + * of items that will use up memory. + * A possible workaround is to apply `takeUntil` with a predicate or + * another source before (and perhaps after) the application of cache(). + *


+     * AtomicBoolean shouldStop = new AtomicBoolean();
+     *
+     * source.takeUntil(v -> shouldStop.get())
+     *       .cache()
+     *       .takeUntil(v -> shouldStop.get())
+     *       .subscribe(...);
+     * 
+ * Since the operator doesn't allow clearing the cached values either, the possible workaround is + * to forget all references to it via {@link #onTerminateDetach()} applied along with the previous + * workaround: + *

+     * AtomicBoolean shouldStop = new AtomicBoolean();
+     *
+     * source.takeUntil(v -> shouldStop.get())
+     *       .onTerminateDetach()
+     *       .cache()
+     *       .takeUntil(v -> shouldStop.get())
+     *       .onTerminateDetach()
+     *       .subscribe(...);
+     * 
+ *
+ *
Backpressure:
+ *
The operator consumes this Publisher in an unbounded fashion but respects the backpressure + * of each downstream Subscriber individually.
+ *
Scheduler:
+ *
{@code cache} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Flowable that, when first subscribed to, caches all of its items and notifications for the + * benefit of subsequent subscribers + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable cache() { + return cacheWithInitialCapacity(16); + } + + /** + * Returns a Flowable that subscribes to this Publisher lazily, caches all of its events + * and replays them, in the same order as received, to all the downstream subscribers. + *

+ * + *

+ * This is useful when you want a Publisher to cache responses and you can't control the + * subscribe/cancel behavior of all the {@link Subscriber}s. + *

+ * The operator subscribes only when the first downstream subscriber subscribes and maintains + * a single subscription towards this Publisher. In contrast, the operator family of {@link #replay()} + * that return a {@link ConnectableFlowable} require an explicit call to {@link ConnectableFlowable#connect()}. + *

+ * Note: You sacrifice the ability to cancel the origin when you use the {@code cache} + * Subscriber so be careful not to use this Subscriber on Publishers that emit an infinite or very large number + * of items that will use up memory. + * A possible workaround is to apply `takeUntil` with a predicate or + * another source before (and perhaps after) the application of cache(). + *


+     * AtomicBoolean shouldStop = new AtomicBoolean();
+     *
+     * source.takeUntil(v -> shouldStop.get())
+     *       .cache()
+     *       .takeUntil(v -> shouldStop.get())
+     *       .subscribe(...);
+     * 
+ * Since the operator doesn't allow clearing the cached values either, the possible workaround is + * to forget all references to it via {@link #onTerminateDetach()} applied along with the previous + * workaround: + *

+     * AtomicBoolean shouldStop = new AtomicBoolean();
+     *
+     * source.takeUntil(v -> shouldStop.get())
+     *       .onTerminateDetach()
+     *       .cache()
+     *       .takeUntil(v -> shouldStop.get())
+     *       .onTerminateDetach()
+     *       .subscribe(...);
+     * 
+ *
+ *
Backpressure:
+ *
The operator consumes this Publisher in an unbounded fashion but respects the backpressure + * of each downstream Subscriber individually.
+ *
Scheduler:
+ *
{@code cacheWithInitialCapacity} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

+ * Note: The capacity hint is not an upper bound on cache size. For that, consider + * {@link #replay(int)} in combination with {@link ConnectableFlowable#autoConnect()} or similar. + * + * @param initialCapacity hint for number of items to cache (for optimizing underlying data structure) + * @return a Flowable that, when first subscribed to, caches all of its items and notifications for the + * benefit of subsequent subscribers + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable cacheWithInitialCapacity(int initialCapacity) { + ObjectHelper.verifyPositive(initialCapacity, "initialCapacity"); + return RxJavaPlugins.onAssembly(new FlowableCache(this, initialCapacity)); + } + + /** + * Returns a Flowable that emits the items emitted by the source Publisher, converted to the specified + * type. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code cast} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output value type cast to + * @param clazz + * the target class type that {@code cast} will cast the items emitted by the source Publisher + * into before emitting them from the resulting Publisher + * @return a Flowable that emits each item from the source Publisher after converting it to the + * specified type + * @see ReactiveX operators documentation: Map + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable cast(final Class clazz) { + ObjectHelper.requireNonNull(clazz, "clazz is null"); + return map(Functions.castFunction(clazz)); + } + + /** + * Collects items emitted by the finite source Publisher into a single mutable data structure and returns + * a Single that emits this structure. + *

+ * + *

+ * This is a simplified version of {@code reduce} that does not need to return the state on each pass. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
This operator does not support backpressure because by intent it will receive all values and reduce + * them to a single {@code onNext}.
+ *
Scheduler:
+ *
{@code collect} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the accumulator and output type + * @param initialItemSupplier + * the mutable data structure that will collect the items + * @param collector + * a function that accepts the {@code state} and an emitted item, and modifies {@code state} + * accordingly + * @return a Single that emits the result of collecting the values emitted by the source Publisher + * into a single mutable data structure + * @see ReactiveX operators documentation: Reduce + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single collect(Callable initialItemSupplier, BiConsumer collector) { + ObjectHelper.requireNonNull(initialItemSupplier, "initialItemSupplier is null"); + ObjectHelper.requireNonNull(collector, "collector is null"); + return RxJavaPlugins.onAssembly(new FlowableCollectSingle(this, initialItemSupplier, collector)); + } + + /** + * Collects items emitted by the finite source Publisher into a single mutable data structure and returns + * a Single that emits this structure. + *

+ * + *

+ * This is a simplified version of {@code reduce} that does not need to return the state on each pass. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
This operator does not support backpressure because by intent it will receive all values and reduce + * them to a single {@code onNext}.
+ *
Scheduler:
+ *
{@code collectInto} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the accumulator and output type + * @param initialItem + * the mutable data structure that will collect the items + * @param collector + * a function that accepts the {@code state} and an emitted item, and modifies {@code state} + * accordingly + * @return a Single that emits the result of collecting the values emitted by the source Publisher + * into a single mutable data structure + * @see ReactiveX operators documentation: Reduce + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single collectInto(final U initialItem, BiConsumer collector) { + ObjectHelper.requireNonNull(initialItem, "initialItem is null"); + return collect(Functions.justCallable(initialItem), collector); + } + + /** + * Transform a Publisher by applying a particular Transformer function to it. + *

+ * This method operates on the Publisher itself whereas {@link #lift} operates on the Publisher's + * Subscribers or Subscribers. + *

+ * If the operator you are creating is designed to act on the individual items emitted by a source + * Publisher, use {@link #lift}. If your operator is designed to transform the source Publisher as a whole + * (for instance, by applying a particular set of existing RxJava operators to it) use {@code compose}. + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with the backpressure behavior which only depends + * on what kind of {@code Publisher} the transformer returns.
+ *
Scheduler:
+ *
{@code compose} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the output Publisher + * @param composer implements the function that transforms the source Publisher + * @return the source Publisher, transformed by the transformer function + * @see RxJava wiki: Implementing Your Own Operators + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable compose(FlowableTransformer composer) { + return fromPublisher(((FlowableTransformer) ObjectHelper.requireNonNull(composer, "composer is null")).apply(this)); + } + + /** + * Returns a new Flowable that emits items resulting from applying a function that you supply to each item + * emitted by the source Publisher, where that function returns a Publisher, and then emitting the items + * that result from concatenating those resulting Publishers. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both this and the inner {@code Publisher}s are + * expected to honor backpressure as well. If the source {@code Publisher} violates the rule, the operator will + * signal a {@code MissingBackpressureException}. If any of the inner {@code Publisher}s doesn't honor + * backpressure, that may throw an {@code IllegalStateException} when that + * {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the inner Publisher sources and thus the output type + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @return a Flowable that emits the result of applying the transformation function to each item emitted + * by the source Publisher and concatenating the Publishers obtained from this transformation + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMap(Function> mapper) { + return concatMap(mapper, 2); + } + + /** + * Returns a new Flowable that emits items resulting from applying a function that you supply to each item + * emitted by the source Publisher, where that function returns a Publisher, and then emitting the items + * that result from concatenating those resulting Publishers. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both this and the inner {@code Publisher}s are + * expected to honor backpressure as well. If the source {@code Publisher} violates the rule, the operator will + * signal a {@code MissingBackpressureException}. If any of the inner {@code Publisher}s doesn't honor + * backpressure, that may throw an {@code IllegalStateException} when that + * {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the inner Publisher sources and thus the output type + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @param prefetch + * the number of elements to prefetch from the current Flowable + * @return a Flowable that emits the result of applying the transformation function to each item emitted + * by the source Publisher and concatenating the Publishers obtained from this transformation + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMap(Function> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + if (this instanceof ScalarCallable) { + @SuppressWarnings("unchecked") + T v = ((ScalarCallable)this).call(); + if (v == null) { + return empty(); + } + return FlowableScalarXMap.scalarXMap(v, mapper); + } + return RxJavaPlugins.onAssembly(new FlowableConcatMap(this, mapper, prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other completes. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @return a new Completable instance + * @see #concatMapCompletableDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletable(Function mapper) { + return concatMapCompletable(mapper, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other completes. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code CompletableSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code CompletableSource}s. + * @return a new Completable instance + * @see #concatMapCompletableDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletable(Function mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapCompletable(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, delaying all errors till both this {@code Flowable} and all + * inner {@code CompletableSource}s terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @return a new Completable instance + * @see #concatMapCompletable(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletableDelayError(Function mapper) { + return concatMapCompletableDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, optionally delaying all errors till both this {@code Flowable} and all + * inner {@code CompletableSource}s terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code CompletableSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code CompletableSource} terminates and only then is + * it emitted to the downstream. + * @return a new Completable instance + * @see #concatMapCompletable(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletableDelayError(Function mapper, boolean tillTheEnd) { + return concatMapCompletableDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, optionally delaying all errors till both this {@code Flowable} and all + * inner {@code CompletableSource}s terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code CompletableSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code CompletableSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code CompletableSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code CompletableSource}s. + * @return a new Completable instance + * @see #concatMapCompletable(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public final Completable concatMapCompletableDelayError(Function mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapCompletable(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Maps each of the items into a Publisher, subscribes to them one after the other, + * one at a time and emits their values in order + * while delaying any error from either this or any of the inner Publishers + * till all of them terminate. + * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both this and the inner {@code Publisher}s are + * expected to honor backpressure as well. If the source {@code Publisher} violates the rule, the operator will + * signal a {@code MissingBackpressureException}. If any of the inner {@code Publisher}s doesn't honor + * backpressure, that may throw an {@code IllegalStateException} when that + * {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concatMapDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper the function that maps the items of this Publisher into the inner Publishers. + * @return the new Publisher instance with the concatenation behavior + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapDelayError(Function> mapper) { + return concatMapDelayError(mapper, 2, true); + } + + /** + * Maps each of the items into a Publisher, subscribes to them one after the other, + * one at a time and emits their values in order + * while delaying any error from either this or any of the inner Publishers + * till all of them terminate. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both this and the inner {@code Publisher}s are + * expected to honor backpressure as well. If the source {@code Publisher} violates the rule, the operator will + * signal a {@code MissingBackpressureException}. If any of the inner {@code Publisher}s doesn't honor + * backpressure, that may throw an {@code IllegalStateException} when that + * {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concatMapDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper the function that maps the items of this Publisher into the inner Publishers. + * @param prefetch + * the number of elements to prefetch from the current Flowable + * @param tillTheEnd + * if true, all errors from the outer and inner Publisher sources are delayed until the end, + * if false, an error from the main source is signaled when the current Publisher source terminates + * @return the new Publisher instance with the concatenation behavior + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapDelayError(Function> mapper, + int prefetch, boolean tillTheEnd) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + if (this instanceof ScalarCallable) { + @SuppressWarnings("unchecked") + T v = ((ScalarCallable)this).call(); + if (v == null) { + return empty(); + } + return FlowableScalarXMap.scalarXMap(v, mapper); + } + return RxJavaPlugins.onAssembly(new FlowableConcatMap(this, mapper, prefetch, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY)); + } + + /** + * Maps a sequence of values into Publishers and concatenates these Publishers eagerly into a single + * Publisher. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source Publishers. The operator buffers the values emitted by these Publishers and then drains them in + * order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream, however, due to the eagerness requirement, sources + * are subscribed to in unbounded mode and their values are queued up in an unbounded buffer.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param mapper the function that maps a sequence of values into a sequence of Publishers that will be + * eagerly concatenated + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapEager(Function> mapper) { + return concatMapEager(mapper, bufferSize(), bufferSize()); + } + + /** + * Maps a sequence of values into Publishers and concatenates these Publishers eagerly into a single + * Publisher. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source Publishers. The operator buffers the values emitted by these Publishers and then drains them in + * order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream, however, due to the eagerness requirement, sources + * are subscribed to in unbounded mode and their values are queued up in an unbounded buffer.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param mapper the function that maps a sequence of values into a sequence of Publishers that will be + * eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscribed Publishers + * @param prefetch hints about the number of expected values from each inner Publisher, must be positive + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapEager(Function> mapper, + int maxConcurrency, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapEager(this, mapper, maxConcurrency, prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Maps a sequence of values into Publishers and concatenates these Publishers eagerly into a single + * Publisher. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source Publishers. The operator buffers the values emitted by these Publishers and then drains them in + * order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream, however, due to the eagerness requirement, sources + * are subscribed to in unbounded mode and their values are queued up in an unbounded buffer.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param mapper the function that maps a sequence of values into a sequence of Publishers that will be + * eagerly concatenated + * @param tillTheEnd + * if true, all errors from the outer and inner Publisher sources are delayed until the end, + * if false, an error from the main source is signaled when the current Publisher source terminates + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapEagerDelayError(Function> mapper, + boolean tillTheEnd) { + return concatMapEagerDelayError(mapper, bufferSize(), bufferSize(), tillTheEnd); + } + + /** + * Maps a sequence of values into Publishers and concatenates these Publishers eagerly into a single + * Publisher. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source Publishers. The operator buffers the values emitted by these Publishers and then drains them in + * order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream, however, due to the eagerness requirement, sources + * are subscribed to in unbounded mode and their values are queued up in an unbounded buffer.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param mapper the function that maps a sequence of values into a sequence of Publishers that will be + * eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscribed Publishers + * @param prefetch + * the number of elements to prefetch from each source Publisher + * @param tillTheEnd + * if true, exceptions from the current Flowable and all the inner Publishers are delayed until + * all of them terminate, if false, exception from the current Flowable is delayed until the + * currently running Publisher terminates + * @return the new Publisher instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapEagerDelayError(Function> mapper, + int maxConcurrency, int prefetch, boolean tillTheEnd) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapEager(this, mapper, maxConcurrency, prefetch, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY)); + } + + /** + * Returns a Flowable that concatenate each item emitted by the source Publisher with the values in an + * Iterable corresponding to that item that is generated by a selector. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s is + * expected to honor backpressure as well. If the source {@code Publisher} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting Publisher + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Publisher + * @return a Flowable that emits the results of concatenating the items emitted by the source Publisher with + * the values in the Iterables corresponding to those items, as generated by {@code collectionSelector} + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapIterable(Function> mapper) { + return concatMapIterable(mapper, 2); + } + + /** + * Returns a Flowable that concatenate each item emitted by the source Publisher with the values in an + * Iterable corresponding to that item that is generated by a selector. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s is + * expected to honor backpressure as well. If the source {@code Publisher} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting Publisher + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Publisher + * @param prefetch + * the number of elements to prefetch from the current Flowable + * @return a Flowable that emits the results of concatenating the items emitted by the source Publisher with + * the values in the Iterables corresponding to those items, as generated by {@code collectionSelector} + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapIterable(final Function> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableFlattenIterable(this, mapper, prefetch)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other succeeds or completes, emits their success value if available or terminates immediately if + * either this {@code Flowable} or the current inner {@code MaybeSource} fail. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @return a new Flowable instance + * @see #concatMapMaybeDelayError(Function) + * @see #concatMapMaybe(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapMaybe(Function> mapper) { + return concatMapMaybe(mapper, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other succeeds or completes, emits their success value if available or terminates immediately if + * either this {@code Flowable} or the current inner {@code MaybeSource} fail. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code MaybeSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code MaybeSource}s. + * @return a new Flowable instance + * @see #concatMapMaybe(Function) + * @see #concatMapMaybeDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapMaybe(Function> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapMaybe(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and delaying all errors + * till both this {@code Flowable} and all inner {@code MaybeSource}s terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @return a new Flowable instance + * @see #concatMapMaybe(Function) + * @see #concatMapMaybeDelayError(Function, boolean) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapMaybeDelayError(Function> mapper) { + return concatMapMaybeDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and optionally delaying all errors + * till both this {@code Flowable} and all inner {@code MaybeSource}s terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code MaybeSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code MaybeSource} terminates and only then is + * it emitted to the downstream. + * @return a new Flowable instance + * @see #concatMapMaybe(Function, int) + * @see #concatMapMaybeDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapMaybeDelayError(Function> mapper, boolean tillTheEnd) { + return concatMapMaybeDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and optionally delaying all errors + * till both this {@code Flowable} and all inner {@code MaybeSource}s terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code MaybeSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code MaybeSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code MaybeSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code MaybeSource}s. + * @return a new Flowable instance + * @see #concatMapMaybe(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapMaybeDelayError(Function> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapMaybe(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds, emits their success values or terminates immediately if + * either this {@code Flowable} or the current inner {@code SingleSource} fail. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @return a new Flowable instance + * @see #concatMapSingleDelayError(Function) + * @see #concatMapSingle(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapSingle(Function> mapper) { + return concatMapSingle(mapper, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds, emits their success values or terminates immediately if + * either this {@code Flowable} or the current inner {@code SingleSource} fail. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code SingleSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code SingleSource}s. + * @return a new Flowable instance + * @see #concatMapSingle(Function) + * @see #concatMapSingleDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapSingle(Function> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapSingle(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and delays all errors + * till both this {@code Flowable} and all inner {@code SingleSource}s terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @return a new Flowable instance + * @see #concatMapSingle(Function) + * @see #concatMapSingleDelayError(Function, boolean) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapSingleDelayError(Function> mapper) { + return concatMapSingleDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and optionally delays all errors + * till both this {@code Flowable} and all inner {@code SingleSource}s terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code SingleSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code SingleSource} terminates and only then is + * it emitted to the downstream. + * @return a new Flowable instance + * @see #concatMapSingle(Function, int) + * @see #concatMapSingleDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapSingleDelayError(Function> mapper, boolean tillTheEnd) { + return concatMapSingleDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and optionally delays errors + * till both this {@code Flowable} and all inner {@code SingleSource}s terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the upstream to support backpressure and honors + * the backpressure from downstream. If this {@code Flowable} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Flowable} or any of the + * inner {@code SingleSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Flowable} is delayed until the current inner + * {@code SingleSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code SingleSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code SingleSource}s. + * @return a new Flowable instance + * @see #concatMapSingle(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatMapSingleDelayError(Function> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapSingle(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Returns a Flowable that emits the items emitted from the current Publisher, then the next, one after + * the other, without interleaving them. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both this and the {@code other} {@code Publisher}s + * are expected to honor backpressure as well. If any of then violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * a Publisher to be concatenated after the current + * @return a Flowable that emits items emitted by the two source Publishers, one after the other, + * without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatWith(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return concat(this, other); + } + + /** + * Returns a {@code Flowable} that emits the items from this {@code Flowable} followed by the success item or error event + * of the other {@link SingleSource}. + *

+ * + *

+ *
Backpressure:
+ *
The operator supports backpressure and makes sure the success item of the other {@code SingleSource} + * is only emitted when there is a demand for it.
+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the SingleSource whose signal should be emitted after this {@code Flowable} completes normally. + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatWith(@NonNull SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableConcatWithSingle(this, other)); + } + + /** + * Returns a {@code Flowable} that emits the items from this {@code Flowable} followed by the success item or terminal events + * of the other {@link MaybeSource}. + *

+ * + *

+ *
Backpressure:
+ *
The operator supports backpressure and makes sure the success item of the other {@code MaybeSource} + * is only emitted when there is a demand for it.
+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the MaybeSource whose signal should be emitted after this Flowable completes normally. + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatWith(@NonNull MaybeSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableConcatWithMaybe(this, other)); + } + + /** + * Returns a {@code Flowable} that emits items from this {@code Flowable} and when it completes normally, the + * other {@link CompletableSource} is subscribed to and the returned {@code Flowable} emits its terminal events. + *

+ * + *

+ *
Backpressure:
+ *
The operator does not interfere with backpressure between the current Flowable and the + * downstream consumer (i.e., acts as pass-through). When the operator switches to the + * {@code Completable}, backpressure is no longer present because {@code Completable} doesn't + * have items to apply backpressure to.
+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the {@code CompletableSource} to subscribe to once the current {@code Flowable} completes normally + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatWith(@NonNull CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableConcatWithCompletable(this, other)); + } + + /** + * Returns a Single that emits a Boolean that indicates whether the source Publisher emitted a + * specified item. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code contains} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the item to search for in the emissions from the source Publisher + * @return a Flowable that emits {@code true} if the specified item is emitted by the source Publisher, + * or {@code false} if the source Publisher completes without emitting that item + * @see ReactiveX operators documentation: Contains + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single contains(final Object item) { + ObjectHelper.requireNonNull(item, "item is null"); + return any(Functions.equalsWith(item)); + } + + /** + * Returns a Single that counts the total number of items emitted by the source Publisher and emits + * this count as a 64-bit Long. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code count} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Single that emits a single item: the number of items emitted by the source Publisher as a + * 64-bit Long item + * @see ReactiveX operators documentation: Count + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single count() { + return RxJavaPlugins.onAssembly(new FlowableCountSingle(this)); + } + + /** + * Returns a Flowable that mirrors the source Publisher, except that it drops items emitted by the + * source Publisher that are followed by another item within a computed debounce duration. + *

+ * + *

+ * The delivery of the item happens on the thread of the first {@code onNext} or {@code onComplete} + * signal of the generated {@code Publisher} sequence, + * which if takes too long, a newer item may arrive from the upstream, causing the + * generated sequence to get cancelled, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses the {@code debounceSelector} to mark + * boundaries.
+ *
Scheduler:
+ *
This version of {@code debounce} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the debounce value type (ignored) + * @param debounceIndicator + * function to retrieve a sequence that indicates the throttle duration for each item + * @return a Flowable that omits items emitted by the source Publisher that are followed by another item + * within a computed debounce duration + * @see ReactiveX operators documentation: Debounce + * @see RxJava wiki: Backpressure + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable debounce(Function> debounceIndicator) { + ObjectHelper.requireNonNull(debounceIndicator, "debounceIndicator is null"); + return RxJavaPlugins.onAssembly(new FlowableDebounce(this, debounceIndicator)); + } + + /** + * Returns a Flowable that mirrors the source Publisher, except that it drops items emitted by the + * source Publisher that are followed by newer items before a timeout value expires. The timer resets on + * each emission. + *

+ * Note: If items keep being emitted by the source Publisher faster than the timeout then no items + * will be emitted by the resulting Publisher. + *

+ * + *

+ * Delivery of the item after the grace period happens on the {@code computation} {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
{@code debounce} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * the length of the window of time that must pass after the emission of an item from the source + * Publisher in which that Publisher emits no items in order for the item to be emitted by the + * resulting Publisher + * @param unit + * the unit of time for the specified {@code timeout} + * @return a Flowable that filters out items from the source Publisher that are too quickly followed by + * newer items + * @see ReactiveX operators documentation: Debounce + * @see RxJava wiki: Backpressure + * @see #throttleWithTimeout(long, TimeUnit) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable debounce(long timeout, TimeUnit unit) { + return debounce(timeout, unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that mirrors the source Publisher, except that it drops items emitted by the + * source Publisher that are followed by newer items before a timeout value expires on a specified + * Scheduler. The timer resets on each emission. + *

+ * Note: If items keep being emitted by the source Publisher faster than the timeout then no items + * will be emitted by the resulting Publisher. + *

+ * + *

+ * Delivery of the item after the grace period happens on the given {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * the time each item has to be "the most recent" of those emitted by the source Publisher to + * ensure that it's not dropped + * @param unit + * the unit of time for the specified {@code timeout} + * @param scheduler + * the {@link Scheduler} to use internally to manage the timers that handle the timeout for each + * item + * @return a Flowable that filters out items from the source Publisher that are too quickly followed by + * newer items + * @see ReactiveX operators documentation: Debounce + * @see RxJava wiki: Backpressure + * @see #throttleWithTimeout(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable debounce(long timeout, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableDebounceTimed(this, timeout, unit, scheduler)); + } + + /** + * Returns a Flowable that emits the items emitted by the source Publisher or a specified default item + * if the source Publisher is empty. + *

+ * + *

+ *
Backpressure:
+ *
If the source {@code Publisher} is empty, this operator is guaranteed to honor backpressure from downstream. + * If the source {@code Publisher} is non-empty, it is expected to honor backpressure as well; if the rule is violated, + * a {@code MissingBackpressureException} may get signaled somewhere downstream. + *
+ *
Scheduler:
+ *
{@code defaultIfEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * the item to emit if the source Publisher emits no items + * @return a Flowable that emits either the specified default item if the source Publisher emits no + * items, or the items emitted by the source Publisher + * @see ReactiveX operators documentation: DefaultIfEmpty + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable defaultIfEmpty(T defaultItem) { + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); + return switchIfEmpty(just(defaultItem)); + } + + /** + * Returns a Flowable that delays the emissions of the source Publisher via another Publisher on a + * per-item basis. + *

+ * + *

+ * Note: the resulting Publisher will immediately propagate any {@code onError} notification + * from the source Publisher. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}. + * All of the other {@code Publisher}s supplied by the function are consumed + * in an unbounded manner (i.e., no backpressure applied to them).
+ *
Scheduler:
+ *
This version of {@code delay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the item delay value type (ignored) + * @param itemDelayIndicator + * a function that returns a Publisher for each item emitted by the source Publisher, which is + * then used to delay the emission of that item by the resulting Publisher until the Publisher + * returned from {@code itemDelay} emits an item + * @return a Flowable that delays the emissions of the source Publisher via another Publisher on a + * per-item basis + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable delay(final Function> itemDelayIndicator) { + ObjectHelper.requireNonNull(itemDelayIndicator, "itemDelayIndicator is null"); + return flatMap(FlowableInternalHelper.itemDelay(itemDelayIndicator)); + } + + /** + * Returns a Flowable that emits the items emitted by the source Publisher shifted forward in time by a + * specified delay. Error notifications from the source Publisher are not delayed. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}.
+ *
Scheduler:
+ *
This version of {@code delay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @return the source Publisher shifted in time by the specified delay + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable delay(long delay, TimeUnit unit) { + return delay(delay, unit, Schedulers.computation(), false); + } + + /** + * Returns a Flowable that emits the items emitted by the source Publisher shifted forward in time by a + * specified delay. If {@code delayError} is true, error notifications will also be delayed. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}.
+ *
Scheduler:
+ *
This version of {@code delay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @param delayError + * if true, the upstream exception is signaled with the given delay, after all preceding normal elements, + * if false, the upstream exception is signaled immediately + * @return the source Publisher shifted in time by the specified delay + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable delay(long delay, TimeUnit unit, boolean delayError) { + return delay(delay, unit, Schedulers.computation(), delayError); + } + + /** + * Returns a Flowable that emits the items emitted by the source Publisher shifted forward in time by a + * specified delay. Error notifications from the source Publisher are not delayed. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the {@link Scheduler} to use for delaying + * @return the source Publisher shifted in time by the specified delay + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable delay(long delay, TimeUnit unit, Scheduler scheduler) { + return delay(delay, unit, scheduler, false); + } + + /** + * Returns a Flowable that emits the items emitted by the source Publisher shifted forward in time by a + * specified delay. If {@code delayError} is true, error notifications will also be delayed. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the {@link Scheduler} to use for delaying + * @param delayError + * if true, the upstream exception is signaled with the given delay, after all preceding normal elements, + * if false, the upstream exception is signaled immediately + * @return the source Publisher shifted in time by the specified delay + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable delay(long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + + return RxJavaPlugins.onAssembly(new FlowableDelay(this, Math.max(0L, delay), unit, scheduler, delayError)); + } + + /** + * Returns a Flowable that delays the subscription to and emissions from the source Publisher via another + * Publisher on a per-item basis. + *

+ * + *

+ * Note: the resulting Publisher will immediately propagate any {@code onError} notification + * from the source Publisher. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}. + * All of the other {@code Publisher}s supplied by the functions are consumed + * in an unbounded manner (i.e., no backpressure applied to them).
+ *
Scheduler:
+ *
This version of {@code delay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the subscription delay value type (ignored) + * @param + * the item delay value type (ignored) + * @param subscriptionIndicator + * a function that returns a Publisher that triggers the subscription to the source Publisher + * once it emits any item + * @param itemDelayIndicator + * a function that returns a Publisher for each item emitted by the source Publisher, which is + * then used to delay the emission of that item by the resulting Publisher until the Publisher + * returned from {@code itemDelay} emits an item + * @return a Flowable that delays the subscription and emissions of the source Publisher via another + * Publisher on a per-item basis + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable delay(Publisher subscriptionIndicator, + Function> itemDelayIndicator) { + return delaySubscription(subscriptionIndicator).delay(itemDelayIndicator); + } + + /** + * Returns a Flowable that delays the subscription to this Publisher + * until the other Publisher emits an element or completes normally. + *
+ *
Backpressure:
+ *
The operator forwards the backpressure requests to this Publisher once + * the subscription happens and requests Long.MAX_VALUE from the other Publisher
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the other Publisher, irrelevant + * @param subscriptionIndicator the other Publisher that should trigger the subscription + * to this Publisher. + * @return a Flowable that delays the subscription to this Publisher + * until the other Publisher emits an element or completes normally. + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable delaySubscription(Publisher subscriptionIndicator) { + ObjectHelper.requireNonNull(subscriptionIndicator, "subscriptionIndicator is null"); + return RxJavaPlugins.onAssembly(new FlowableDelaySubscriptionOther(this, subscriptionIndicator)); + } + + /** + * Returns a Flowable that delays the subscription to the source Publisher by a given amount of time. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}.
+ *
Scheduler:
+ *
This version of {@code delaySubscription} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the time to delay the subscription + * @param unit + * the time unit of {@code delay} + * @return a Flowable that delays the subscription to the source Publisher by the given amount + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable delaySubscription(long delay, TimeUnit unit) { + return delaySubscription(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that delays the subscription to the source Publisher by a given amount of time, + * both waiting and subscribing on a given Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with the backpressure behavior which is determined by the source {@code Publisher}.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the time to delay the subscription + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the Scheduler on which the waiting and subscription will happen + * @return a Flowable that delays the subscription to the source Publisher by a given + * amount, waiting and subscribing on the given Scheduler + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable delaySubscription(long delay, TimeUnit unit, Scheduler scheduler) { + return delaySubscription(timer(delay, unit, scheduler)); + } + + /** + * Returns a Flowable that reverses the effect of {@link #materialize materialize} by transforming the + * {@link Notification} objects emitted by the source Publisher into the items or notifications they + * represent. + *

+ * + *

+ * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Flowable cancels the flow and terminates with that type of terminal event: + *


+     * Flowable.just(createOnNext(1), createOnComplete(), createOnNext(2))
+     * .doOnCancel(() -> System.out.println("Cancelled!"));
+     * .dematerialize()
+     * .test()
+     * .assertResult(1);
+     * 
+ * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + *

+     * Flowable.just(createOnNext(1), createOnNext(2))
+     * .dematerialize()
+     * .test()
+     * .assertResult(1, 2);
+     * 
+ * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(Publisher)} + * with a {@link #never()} source. + *
+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code dematerialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output value type + * @return a Flowable that emits the items and notifications embedded in the {@link Notification} objects + * emitted by the source Publisher + * @see ReactiveX operators documentation: Dematerialize + * @see #dematerialize(Function) + * @deprecated in 2.2.4; inherently type-unsafe as it overrides the output generic type. Use {@link #dematerialize(Function)} instead. + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @Deprecated + @SuppressWarnings({ "unchecked", "rawtypes" }) + public final Flowable dematerialize() { + return RxJavaPlugins.onAssembly(new FlowableDematerialize(this, Functions.identity())); + } + + /** + * Returns a Flowable that reverses the effect of {@link #materialize materialize} by transforming the + * {@link Notification} objects extracted from the source items via a selector function + * into their respective {@code Subscriber} signal types. + *

+ * + *

+ * The intended use of the {@code selector} function is to perform a + * type-safe identity mapping (see example) on a source that is already of type + * {@code Notification}. The Java language doesn't allow + * limiting instance methods to a certain generic argument shape, therefore, + * a function is used to ensure the conversion remains type safe. + *

+ * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Flowable cancels of the flow and terminates with that type of terminal event: + *


+     * Flowable.just(createOnNext(1), createOnComplete(), createOnNext(2))
+     * .doOnCancel(() -> System.out.println("Canceled!"));
+     * .dematerialize(notification -> notification)
+     * .test()
+     * .assertResult(1);
+     * 
+ * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + *

+     * Flowable.just(createOnNext(1), createOnNext(2))
+     * .dematerialize(notification -> notification)
+     * .test()
+     * .assertResult(1, 2);
+     * 
+ * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(Publisher)} + * with a {@link #never()} source. + *
+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code dematerialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output value type + * @param selector function that returns the upstream item and should return a Notification to signal + * the corresponding {@code Subscriber} event to the downstream. + * @return a Flowable that emits the items and notifications embedded in the {@link Notification} objects + * selected from the items emitted by the source Flowable + * @see ReactiveX operators documentation: Dematerialize + * @since 2.2.4 - experimental + */ + @Experimental + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable dematerialize(Function> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new FlowableDematerialize(this, selector)); + } + + /** + * Returns a Flowable that emits all items emitted by the source Publisher that are distinct + * based on {@link Object#equals(Object)} comparison. + *

+ * + *

+ * It is recommended the elements' class {@code T} in the flow overrides the default {@code Object.equals()} and {@link Object#hashCode()} to provide + * a meaningful comparison between items as the default Java implementation only considers reference equivalence. + *

+ * By default, {@code distinct()} uses an internal {@link HashSet} per Subscriber to remember + * previously seen items and uses {@link Set#add(Object)} returning {@code false} as the + * indicator for duplicates. + *

+ * Note that this internal {@code HashSet} may grow unbounded as items won't be removed from it by + * the operator. Therefore, using very long or infinite upstream (with very distinct elements) may lead + * to {@code OutOfMemoryError}. + *

+ * Customizing the retention policy can happen only by providing a custom {@link Collection} implementation + * to the {@link #distinct(Function, Callable)} overload. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code distinct} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Flowable that emits only those items emitted by the source Publisher that are distinct from + * each other + * @see ReactiveX operators documentation: Distinct + * @see #distinct(Function) + * @see #distinct(Function, Callable) + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable distinct() { + return distinct((Function)Functions.identity(), Functions.createHashSet()); + } + + /** + * Returns a Flowable that emits all items emitted by the source Publisher that are distinct according + * to a key selector function and based on {@link Object#equals(Object)} comparison of the objects + * returned by the key selector function. + *

+ * + *

+ * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} and {@link Object#hashCode()} to provide + * a meaningful comparison between the key objects as the default Java implementation only considers reference equivalence. + *

+ * By default, {@code distinct()} uses an internal {@link HashSet} per Subscriber to remember + * previously seen keys and uses {@link Set#add(Object)} returning {@code false} as the + * indicator for duplicates. + *

+ * Note that this internal {@code HashSet} may grow unbounded as keys won't be removed from it by + * the operator. Therefore, using very long or infinite upstream (with very distinct keys) may lead + * to {@code OutOfMemoryError}. + *

+ * Customizing the retention policy can happen only by providing a custom {@link Collection} implementation + * to the {@link #distinct(Function, Callable)} overload. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code distinct} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type + * @param keySelector + * a function that projects an emitted item to a key value that is used to decide whether an item + * is distinct from another one or not + * @return a Flowable that emits those items emitted by the source Publisher that have distinct keys + * @see ReactiveX operators documentation: Distinct + * @see #distinct(Function, Callable) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable distinct(Function keySelector) { + return distinct(keySelector, Functions.createHashSet()); + } + + /** + * Returns a Flowable that emits all items emitted by the source Publisher that are distinct according + * to a key selector function and based on {@link Object#equals(Object)} comparison of the objects + * returned by the key selector function. + *

+ * + *

+ * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} and {@link Object#hashCode()} to provide + * a meaningful comparison between the key objects as the default Java implementation only considers reference equivalence. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code distinct} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type + * @param keySelector + * a function that projects an emitted item to a key value that is used to decide whether an item + * is distinct from another one or not + * @param collectionSupplier + * function called for each individual Subscriber to return a Collection subtype for holding the extracted + * keys and whose add() method's return indicates uniqueness. + * @return a Flowable that emits those items emitted by the source Publisher that have distinct keys + * @see ReactiveX operators documentation: Distinct + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable distinct(Function keySelector, + Callable> collectionSupplier) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(collectionSupplier, "collectionSupplier is null"); + return RxJavaPlugins.onAssembly(new FlowableDistinct(this, keySelector, collectionSupplier)); + } + + /** + * Returns a Flowable that emits all items emitted by the source Publisher that are distinct from their + * immediate predecessors based on {@link Object#equals(Object)} comparison. + *

+ * + *

+ * It is recommended the elements' class {@code T} in the flow overrides the default {@code Object.equals()} to provide + * a meaningful comparison between items as the default Java implementation only considers reference equivalence. + * Alternatively, use the {@link #distinctUntilChanged(BiPredicate)} overload and provide a comparison function + * in case the class {@code T} can't be overridden with custom {@code equals()} or the comparison itself + * should happen on different terms or properties of the class {@code T}. + *

+ * Note that the operator always retains the latest item from upstream regardless of the comparison result + * and uses it in the next comparison with the next upstream item. + *

+ * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Flowable that emits those items from the source Publisher that are distinct from their + * immediate predecessors + * @see ReactiveX operators documentation: Distinct + * @see #distinctUntilChanged(BiPredicate) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable distinctUntilChanged() { + return distinctUntilChanged(Functions.identity()); + } + + /** + * Returns a Flowable that emits all items emitted by the source Publisher that are distinct from their + * immediate predecessors, according to a key selector function and based on {@link Object#equals(Object)} comparison + * of those objects returned by the key selector function. + *

+ * + *

+ * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} to provide + * a meaningful comparison between the key objects as the default Java implementation only considers reference equivalence. + * Alternatively, use the {@link #distinctUntilChanged(BiPredicate)} overload and provide a comparison function + * in case the class {@code K} can't be overridden with custom {@code equals()} or the comparison itself + * should happen on different terms or properties of the item class {@code T} (for which the keys can be + * derived via a similar selector). + *

+ * Note that the operator always retains the latest key from upstream regardless of the comparison result + * and uses it in the next comparison with the next key derived from the next upstream item. + *

+ * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type + * @param keySelector + * a function that projects an emitted item to a key value that is used to decide whether an item + * is distinct from another one or not + * @return a Flowable that emits those items from the source Publisher whose keys are distinct from + * those of their immediate predecessors + * @see ReactiveX operators documentation: Distinct + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable distinctUntilChanged(Function keySelector) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + return RxJavaPlugins.onAssembly(new FlowableDistinctUntilChanged(this, keySelector, ObjectHelper.equalsPredicate())); + } + + /** + * Returns a Flowable that emits all items emitted by the source Publisher that are distinct from their + * immediate predecessors when compared with each other via the provided comparator function. + *

+ * + *

+ * Note that the operator always retains the latest item from upstream regardless of the comparison result + * and uses it in the next comparison with the next upstream item. + *

+ * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param comparer the function that receives the previous item and the current item and is + * expected to return true if the two are equal, thus skipping the current value. + * @return a Flowable that emits those items from the source Publisher that are distinct from their + * immediate predecessors + * @see ReactiveX operators documentation: Distinct + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable distinctUntilChanged(BiPredicate comparer) { + ObjectHelper.requireNonNull(comparer, "comparer is null"); + return RxJavaPlugins.onAssembly(new FlowableDistinctUntilChanged(this, Functions.identity(), comparer)); + } + + /** + * Calls the specified action after this Flowable signals onError or onCompleted or gets canceled by + * the downstream. + *

In case of a race between a terminal event and a cancellation, the provided {@code onFinally} action + * is executed once per subscription. + *

Note that the {@code onFinally} action is shared between subscriptions and as such + * should be thread-safe. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code doFinally} does not operate by default on a particular {@link Scheduler}.
+ *
Operator-fusion:
+ *
This operator supports normal and conditional Subscribers as well as boundary-limited + * synchronous or asynchronous queue-fusion.
+ *
+ *

History: 2.0.1 - experimental + * @param onFinally the action called when this Flowable terminates or gets canceled + * @return the new Flowable instance + * @since 2.1 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doFinally(Action onFinally) { + ObjectHelper.requireNonNull(onFinally, "onFinally is null"); + return RxJavaPlugins.onAssembly(new FlowableDoFinally(this, onFinally)); + } + + /** + * Calls the specified consumer with the current item after this item has been emitted to the downstream. + *

Note that the {@code onAfterNext} action is shared between subscriptions and as such + * should be thread-safe. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code doAfterNext} does not operate by default on a particular {@link Scheduler}.
+ *
Operator-fusion:
+ *
This operator supports normal and conditional Subscribers as well as boundary-limited + * synchronous or asynchronous queue-fusion.
+ *
+ *

History: 2.0.1 - experimental + * @param onAfterNext the Consumer that will be called after emitting an item from upstream to the downstream + * @return the new Flowable instance + * @since 2.1 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doAfterNext(Consumer onAfterNext) { + ObjectHelper.requireNonNull(onAfterNext, "onAfterNext is null"); + return RxJavaPlugins.onAssembly(new FlowableDoAfterNext(this, onAfterNext)); + } + + /** + * Registers an {@link Action} to be called when this Publisher invokes either + * {@link Subscriber#onComplete onComplete} or {@link Subscriber#onError onError}. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code doAfterTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onAfterTerminate + * an {@link Action} to be invoked when the source Publisher finishes + * @return a Flowable that emits the same items as the source Publisher, then invokes the + * {@link Action} + * @see ReactiveX operators documentation: Do + * @see #doOnTerminate(Action) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doAfterTerminate(Action onAfterTerminate) { + return doOnEach(Functions.emptyConsumer(), Functions.emptyConsumer(), + Functions.EMPTY_ACTION, onAfterTerminate); + } + + /** + * Calls the cancel {@code Action} if the downstream cancels the sequence. + *

+ * The action is shared between subscriptions and thus may be called concurrently from multiple + * threads; the action must be thread-safe. + *

+ * If the action throws a runtime exception, that exception is rethrown by the {@code onCancel()} call, + * sometimes as a {@code CompositeException} if there were multiple exceptions along the way. + *

+ * Note that terminal events trigger the action unless the {@code Publisher} is subscribed to via {@code unsafeSubscribe()}. + *

+ * + *

+ *
Backpressure:
+ *
{@code doOnCancel} does not interact with backpressure requests or value delivery; backpressure + * behavior is preserved between its upstream and its downstream.
+ *
Scheduler:
+ *
{@code doOnCancel} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onCancel + * the action that gets called when the source {@code Publisher}'s Subscription is canceled + * @return the source {@code Publisher} modified so as to call this Action when appropriate + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnCancel(Action onCancel) { + return doOnLifecycle(Functions.emptyConsumer(), Functions.EMPTY_LONG_CONSUMER, onCancel); + } + + /** + * Modifies the source Publisher so that it invokes an action when it calls {@code onComplete}. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnComplete} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onComplete + * the action to invoke when the source Publisher calls {@code onComplete} + * @return the source Publisher with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnComplete(Action onComplete) { + return doOnEach(Functions.emptyConsumer(), Functions.emptyConsumer(), + onComplete, Functions.EMPTY_ACTION); + } + + /** + * Calls the appropriate onXXX consumer (shared between all subscribers) whenever a signal with the same type + * passes through, before forwarding them to downstream. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnEach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the source Publisher with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + private Flowable doOnEach(Consumer onNext, Consumer onError, + Action onComplete, Action onAfterTerminate) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"); + return RxJavaPlugins.onAssembly(new FlowableDoOnEach(this, onNext, onError, onComplete, onAfterTerminate)); + } + + /** + * Modifies the source Publisher so that it invokes an action for each item it emits. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnEach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNotification + * the action to invoke for each item emitted by the source Publisher + * @return the source Publisher with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnEach(final Consumer> onNotification) { + ObjectHelper.requireNonNull(onNotification, "onNotification is null"); + return doOnEach( + Functions.notificationOnNext(onNotification), + Functions.notificationOnError(onNotification), + Functions.notificationOnComplete(onNotification), + Functions.EMPTY_ACTION + ); + } + + /** + * Modifies the source Publisher so that it notifies a Subscriber for each item and terminal event it emits. + *

+ * In case the {@code onError} of the supplied Subscriber throws, the downstream will receive a composite + * exception containing the original exception and the exception thrown by {@code onError}. If either the + * {@code onNext} or the {@code onComplete} method of the supplied Subscriber throws, the downstream will be + * terminated and will receive this thrown exception. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnEach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param subscriber + * the Subscriber to be notified about onNext, onError and onComplete events on its + * respective methods before the actual downstream Subscriber gets notified. + * @return the source Publisher with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnEach(final Subscriber subscriber) { + ObjectHelper.requireNonNull(subscriber, "subscriber is null"); + return doOnEach( + FlowableInternalHelper.subscriberOnNext(subscriber), + FlowableInternalHelper.subscriberOnError(subscriber), + FlowableInternalHelper.subscriberOnComplete(subscriber), + Functions.EMPTY_ACTION); + } + + /** + * Modifies the source Publisher so that it invokes an action if it calls {@code onError}. + *

+ * In case the {@code onError} action throws, the downstream will receive a composite exception containing + * the original exception and the exception thrown by {@code onError}. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onError + * the action to invoke if the source Publisher calls {@code onError} + * @return the source Publisher with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnError(Consumer onError) { + return doOnEach(Functions.emptyConsumer(), onError, + Functions.EMPTY_ACTION, Functions.EMPTY_ACTION); + } + + /** + * Calls the appropriate onXXX method (shared between all Subscribers) for the lifecycle events of + * the sequence (subscription, cancellation, requesting). + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnLifecycle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onSubscribe + * a Consumer called with the Subscription sent via Subscriber.onSubscribe() + * @param onRequest + * a LongConsumer called with the request amount sent via Subscription.request() + * @param onCancel + * called when the downstream cancels the Subscription via cancel() + * @return the source Publisher with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnLifecycle(final Consumer onSubscribe, + final LongConsumer onRequest, final Action onCancel) { + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + ObjectHelper.requireNonNull(onRequest, "onRequest is null"); + ObjectHelper.requireNonNull(onCancel, "onCancel is null"); + return RxJavaPlugins.onAssembly(new FlowableDoOnLifecycle(this, onSubscribe, onRequest, onCancel)); + } + + /** + * Modifies the source Publisher so that it invokes an action when it calls {@code onNext}. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the action to invoke when the source Publisher calls {@code onNext} + * @return the source Publisher with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnNext(Consumer onNext) { + return doOnEach(onNext, Functions.emptyConsumer(), + Functions.EMPTY_ACTION, Functions.EMPTY_ACTION); + } + + /** + * Modifies the source {@code Publisher} so that it invokes the given action when it receives a + * request for more items. + *

+ * Note: This operator is for tracing the internal behavior of back-pressure request + * patterns and generally intended for debugging use. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnRequest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onRequest + * the action that gets called when a Subscriber requests items from this + * {@code Publisher} + * @return the source {@code Publisher} modified so as to call this Action when appropriate + * @see ReactiveX operators + * documentation: Do + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnRequest(LongConsumer onRequest) { + return doOnLifecycle(Functions.emptyConsumer(), onRequest, Functions.EMPTY_ACTION); + } + + /** + * Modifies the source {@code Publisher} so that it invokes the given action when it is subscribed from + * its subscribers. Each subscription will result in an invocation of the given action except when the + * source {@code Publisher} is reference counted, in which case the source {@code Publisher} will invoke + * the given action for the first subscription. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onSubscribe + * the Consumer that gets called when a Subscriber subscribes to the current {@code Flowable} + * @return the source {@code Publisher} modified so as to call this Consumer when appropriate + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnSubscribe(Consumer onSubscribe) { + return doOnLifecycle(onSubscribe, Functions.EMPTY_LONG_CONSUMER, Functions.EMPTY_ACTION); + } + + /** + * Modifies the source Publisher so that it invokes an action when it calls {@code onComplete} or + * {@code onError}. + *

+ * + *

+ * This differs from {@code doAfterTerminate} in that this happens before the {@code onComplete} or + * {@code onError} notification. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s + * backpressure behavior.
+ *
Scheduler:
+ *
{@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onTerminate + * the action to invoke when the source Publisher calls {@code onComplete} or {@code onError} + * @return the source Publisher with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + * @see #doAfterTerminate(Action) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable doOnTerminate(final Action onTerminate) { + return doOnEach(Functions.emptyConsumer(), Functions.actionConsumer(onTerminate), + onTerminate, Functions.EMPTY_ACTION); + } + + /** + * Returns a Maybe that emits the single item at a specified index in a sequence of emissions from + * this Flowable or completes if this Flowable sequence has fewer elements than index. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code elementAt} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param index + * the zero-based index of the item to retrieve + * @return a Maybe that emits a single item: the item at the specified position in the sequence of + * those emitted by the source Publisher + * @see ReactiveX operators documentation: ElementAt + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe elementAt(long index) { + if (index < 0) { + throw new IndexOutOfBoundsException("index >= 0 required but it was " + index); + } + return RxJavaPlugins.onAssembly(new FlowableElementAtMaybe(this, index)); + } + + /** + * Returns a Single that emits the item found at a specified index in a sequence of emissions from + * this Flowable, or a default item if that index is out of range. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code elementAt} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param index + * the zero-based index of the item to retrieve + * @param defaultItem + * the default item + * @return a Single that emits the item at the specified position in the sequence emitted by the source + * Publisher, or the default item if that index is outside the bounds of the source sequence + * @throws IndexOutOfBoundsException + * if {@code index} is less than 0 + * @see ReactiveX operators documentation: ElementAt + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single elementAt(long index, T defaultItem) { + if (index < 0) { + throw new IndexOutOfBoundsException("index >= 0 required but it was " + index); + } + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); + return RxJavaPlugins.onAssembly(new FlowableElementAtSingle(this, index, defaultItem)); + } + + /** + * Returns a Single that emits the item found at a specified index in a sequence of emissions from + * this Flowable or signals a {@link NoSuchElementException} if this Flowable has fewer elements than index. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded manner + * (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code elementAtOrError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param index + * the zero-based index of the item to retrieve + * @return a Single that emits the item at the specified position in the sequence emitted by the source + * Publisher, or the default item if that index is outside the bounds of the source sequence + * @throws IndexOutOfBoundsException + * if {@code index} is less than 0 + * @see ReactiveX operators documentation: ElementAt + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single elementAtOrError(long index) { + if (index < 0) { + throw new IndexOutOfBoundsException("index >= 0 required but it was " + index); + } + return RxJavaPlugins.onAssembly(new FlowableElementAtSingle(this, index, null)); + } + + /** + * Filters items emitted by a Publisher by only emitting those that satisfy a specified predicate. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code filter} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function that evaluates each item emitted by the source Publisher, returning {@code true} + * if it passes the filter + * @return a Flowable that emits only those items emitted by the source Publisher that the filter + * evaluates as {@code true} + * @see ReactiveX operators documentation: Filter + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable filter(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new FlowableFilter(this, predicate)); + } + + /** + * Returns a Maybe that emits only the very first item emitted by this Flowable or + * completes if this Flowable is empty. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code firstElement} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Maybe instance + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) // take may trigger UNBOUNDED_IN + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe firstElement() { + return elementAt(0); + } + + /** + * Returns a Single that emits only the very first item emitted by this Flowable, or a default + * item if this Flowable completes without emitting anything. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code first} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * the default item to emit if the source Publisher doesn't emit anything + * @return a Single that emits only the very first item from the source, or a default item if the + * source Publisher completes without emitting any items + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) // take may trigger UNBOUNDED_IN + @SchedulerSupport(SchedulerSupport.NONE) + public final Single first(T defaultItem) { + return elementAt(0, defaultItem); + } + + /** + * Returns a Single that emits only the very first item emitted by this Flowable or + * signals a {@link NoSuchElementException} if this Flowable is empty. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code firstOrError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Single instance + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) // take may trigger UNBOUNDED_IN + @SchedulerSupport(SchedulerSupport.NONE) + public final Single firstOrError() { + return elementAtOrError(0); + } + + /** + * Returns a Flowable that emits items based on applying a function that you supply to each item emitted + * by the source Publisher, where that function returns a Publisher, and then merging those resulting + * Publishers and emitting the results of this merger. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@link #bufferSize()} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner Publishers and the output type + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @return a Flowable that emits the result of applying the transformation function to each item emitted + * by the source Publisher and merging the results of the Publishers obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(Function> mapper) { + return flatMap(mapper, false, bufferSize(), bufferSize()); + } + + /** + * Returns a Flowable that emits items based on applying a function that you supply to each item emitted + * by the source Publisher, where that function returns a Publisher, and then merging those resulting + * Publishers and emitting the results of this merger. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@link #bufferSize()} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner Publishers and the output type + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @param delayErrors + * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate + * if false, the first one signaling an exception will terminate the whole sequence immediately + * @return a Flowable that emits the result of applying the transformation function to each item emitted + * by the source Publisher and merging the results of the Publishers obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(Function> mapper, boolean delayErrors) { + return flatMap(mapper, delayErrors, bufferSize(), bufferSize()); + } + + /** + * Returns a Flowable that emits items based on applying a function that you supply to each item emitted + * by the source Publisher, where that function returns a Publisher, and then merging those resulting + * Publishers and emitting the results of this merger, while limiting the maximum number of concurrent + * subscriptions to these Publishers. + * + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@code maxConcurrency} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner Publishers and the output type + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @return a Flowable that emits the result of applying the transformation function to each item emitted + * by the source Publisher and merging the results of the Publishers obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(Function> mapper, int maxConcurrency) { + return flatMap(mapper, false, maxConcurrency, bufferSize()); + } + + /** + * Returns a Flowable that emits items based on applying a function that you supply to each item emitted + * by the source Publisher, where that function returns a Publisher, and then merging those resulting + * Publishers and emitting the results of this merger, while limiting the maximum number of concurrent + * subscriptions to these Publishers. + * + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@code maxConcurrency} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner Publishers and the output type + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @param delayErrors + * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate + * if false, the first one signaling an exception will terminate the whole sequence immediately + * @return a Flowable that emits the result of applying the transformation function to each item emitted + * by the source Publisher and merging the results of the Publishers obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(Function> mapper, boolean delayErrors, int maxConcurrency) { + return flatMap(mapper, delayErrors, maxConcurrency, bufferSize()); + } + + /** + * Returns a Flowable that emits items based on applying a function that you supply to each item emitted + * by the source Publisher, where that function returns a Publisher, and then merging those resulting + * Publishers and emitting the results of this merger, while limiting the maximum number of concurrent + * subscriptions to these Publishers. + * + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@code maxConcurrency} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner Publishers and the output type + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @param delayErrors + * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate + * if false, the first one signaling an exception will terminate the whole sequence immediately + * @param bufferSize + * the number of elements to prefetch from each inner Publisher + * @return a Flowable that emits the result of applying the transformation function to each item emitted + * by the source Publisher and merging the results of the Publishers obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(Function> mapper, + boolean delayErrors, int maxConcurrency, int bufferSize) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + if (this instanceof ScalarCallable) { + @SuppressWarnings("unchecked") + T v = ((ScalarCallable)this).call(); + if (v == null) { + return empty(); + } + return FlowableScalarXMap.scalarXMap(v, mapper); + } + return RxJavaPlugins.onAssembly(new FlowableFlatMap(this, mapper, delayErrors, maxConcurrency, bufferSize)); + } + + /** + * Returns a Flowable that applies a function to each item emitted or notification raised by the source + * Publisher and then flattens the Publishers returned from these functions and emits the resulting items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@link #bufferSize()} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the result type + * @param onNextMapper + * a function that returns a Publisher to merge for each item emitted by the source Publisher + * @param onErrorMapper + * a function that returns a Publisher to merge for an onError notification from the source + * Publisher + * @param onCompleteSupplier + * a function that returns a Publisher to merge for an onComplete notification from the source + * Publisher + * @return a Flowable that emits the results of merging the Publishers returned from applying the + * specified functions to the emissions and notifications of the source Publisher + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap( + Function> onNextMapper, + Function> onErrorMapper, + Callable> onCompleteSupplier) { + ObjectHelper.requireNonNull(onNextMapper, "onNextMapper is null"); + ObjectHelper.requireNonNull(onErrorMapper, "onErrorMapper is null"); + ObjectHelper.requireNonNull(onCompleteSupplier, "onCompleteSupplier is null"); + return merge(new FlowableMapNotification>(this, onNextMapper, onErrorMapper, onCompleteSupplier)); + } + + /** + * Returns a Flowable that applies a function to each item emitted or notification raised by the source + * Publisher and then flattens the Publishers returned from these functions and emits the resulting items, + * while limiting the maximum number of concurrent subscriptions to these Publishers. + * + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@code maxConcurrency} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the result type + * @param onNextMapper + * a function that returns a Publisher to merge for each item emitted by the source Publisher + * @param onErrorMapper + * a function that returns a Publisher to merge for an onError notification from the source + * Publisher + * @param onCompleteSupplier + * a function that returns a Publisher to merge for an onComplete notification from the source + * Publisher + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @return a Flowable that emits the results of merging the Publishers returned from applying the + * specified functions to the emissions and notifications of the source Publisher + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap( + Function> onNextMapper, + Function> onErrorMapper, + Callable> onCompleteSupplier, + int maxConcurrency) { + ObjectHelper.requireNonNull(onNextMapper, "onNextMapper is null"); + ObjectHelper.requireNonNull(onErrorMapper, "onErrorMapper is null"); + ObjectHelper.requireNonNull(onCompleteSupplier, "onCompleteSupplier is null"); + return merge(new FlowableMapNotification>( + this, onNextMapper, onErrorMapper, onCompleteSupplier), maxConcurrency); + } + + /** + * Returns a Flowable that emits the results of a specified function to the pair of values emitted by the + * source Publisher and a specified collection Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@code maxConcurrency} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the inner Publishers + * @param + * the type of items emitted by the combiner function + * @param mapper + * a function that returns a Publisher for each item emitted by the source Publisher + * @param combiner + * a function that combines one item emitted by each of the source and collection Publishers and + * returns an item to be emitted by the resulting Publisher + * @return a Flowable that emits the results of applying a function to a pair of values emitted by the + * source Publisher and the collection Publisher + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(Function> mapper, + BiFunction combiner) { + return flatMap(mapper, combiner, false, bufferSize(), bufferSize()); + } + + /** + * Returns a Flowable that emits the results of a specified function to the pair of values emitted by the + * source Publisher and a specified collection Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@link #bufferSize()} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the inner Publishers + * @param + * the type of items emitted by the combiner functions + * @param mapper + * a function that returns a Publisher for each item emitted by the source Publisher + * @param combiner + * a function that combines one item emitted by each of the source and collection Publishers and + * returns an item to be emitted by the resulting Publisher + * @param delayErrors + * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate + * if false, the first one signaling an exception will terminate the whole sequence immediately + * @return a Flowable that emits the results of applying a function to a pair of values emitted by the + * source Publisher and the collection Publisher + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(Function> mapper, + BiFunction combiner, boolean delayErrors) { + return flatMap(mapper, combiner, delayErrors, bufferSize(), bufferSize()); + } + + /** + * Returns a Flowable that emits the results of a specified function to the pair of values emitted by the + * source Publisher and a specified collection Publisher, while limiting the maximum number of concurrent + * subscriptions to these Publishers. + * + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@code maxConcurrency} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the inner Publishers + * @param + * the type of items emitted by the combiner function + * @param mapper + * a function that returns a Publisher for each item emitted by the source Publisher + * @param combiner + * a function that combines one item emitted by each of the source and collection Publishers and + * returns an item to be emitted by the resulting Publisher + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @param delayErrors + * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate + * if false, the first one signaling an exception will terminate the whole sequence immediately + * @return a Flowable that emits the results of applying a function to a pair of values emitted by the + * source Publisher and the collection Publisher + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(Function> mapper, + BiFunction combiner, boolean delayErrors, int maxConcurrency) { + return flatMap(mapper, combiner, delayErrors, maxConcurrency, bufferSize()); + } + + /** + * Returns a Flowable that emits the results of a specified function to the pair of values emitted by the + * source Publisher and a specified collection Publisher, while limiting the maximum number of concurrent + * subscriptions to these Publishers. + * + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@code maxConcurrency} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the inner Publishers + * @param + * the type of items emitted by the combiner function + * @param mapper + * a function that returns a Publisher for each item emitted by the source Publisher + * @param combiner + * a function that combines one item emitted by each of the source and collection Publishers and + * returns an item to be emitted by the resulting Publisher + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @param delayErrors + * if true, exceptions from the current Flowable and all inner Publishers are delayed until all of them terminate + * if false, the first one signaling an exception will terminate the whole sequence immediately + * @param bufferSize + * the number of elements to prefetch from the inner Publishers. + * @return a Flowable that emits the results of applying a function to a pair of values emitted by the + * source Publisher and the collection Publisher + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(final Function> mapper, + final BiFunction combiner, boolean delayErrors, int maxConcurrency, int bufferSize) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return flatMap(FlowableInternalHelper.flatMapWithCombiner(mapper, combiner), delayErrors, maxConcurrency, bufferSize); + } + + /** + * Returns a Flowable that emits the results of a specified function to the pair of values emitted by the + * source Publisher and a specified collection Publisher, while limiting the maximum number of concurrent + * subscriptions to these Publishers. + * + * + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The upstream Flowable is consumed + * in a bounded manner (up to {@link #bufferSize()} outstanding request amount for items). + * The inner {@code Publisher}s are expected to honor backpressure; if violated, + * the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the inner Publishers + * @param + * the type of items emitted by the combiner function + * @param mapper + * a function that returns a Publisher for each item emitted by the source Publisher + * @param combiner + * a function that combines one item emitted by each of the source and collection Publishers and + * returns an item to be emitted by the resulting Publisher + * @param maxConcurrency + * the maximum number of Publishers that may be subscribed to concurrently + * @return a Flowable that emits the results of applying a function to a pair of values emitted by the + * source Publisher and the collection Publisher + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMap(Function> mapper, + BiFunction combiner, int maxConcurrency) { + return flatMap(mapper, combiner, false, maxConcurrency, bufferSize()); + } + + /** + * Maps each element of the upstream Flowable into CompletableSources, subscribes to them and + * waits until the upstream and all CompletableSources complete. + *
+ *
Backpressure:
+ *
The operator consumes the upstream in an unbounded manner.
+ *
Scheduler:
+ *
{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param mapper the function that received each source value and transforms them into CompletableSources. + * @return the new Completable instance + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable flatMapCompletable(Function mapper) { + return flatMapCompletable(mapper, false, Integer.MAX_VALUE); + } + + /** + * Maps each element of the upstream Flowable into CompletableSources, subscribes to them and + * waits until the upstream and all CompletableSources complete, optionally delaying all errors. + *
+ *
Backpressure:
+ *
If {@code maxConcurrency == Integer.MAX_VALUE} the operator consumes the upstream in an unbounded manner. + * Otherwise, the operator expects the upstream to honor backpressure. If the upstream doesn't support backpressure + * the operator behaves as if {@code maxConcurrency == Integer.MAX_VALUE} was used.
+ *
Scheduler:
+ *
{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param mapper the function that received each source value and transforms them into CompletableSources. + * @param delayErrors if true errors from the upstream and inner CompletableSources are delayed until each of them + * terminates. + * @param maxConcurrency the maximum number of active subscriptions to the CompletableSources. + * @return the new Completable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable flatMapCompletable(Function mapper, boolean delayErrors, int maxConcurrency) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + return RxJavaPlugins.onAssembly(new FlowableFlatMapCompletableCompletable(this, mapper, delayErrors, maxConcurrency)); + } + + /** + * Returns a Flowable that merges each item emitted by the source Publisher with the values in an + * Iterable corresponding to that item that is generated by a selector. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s is + * expected to honor backpressure as well. If the source {@code Publisher} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting Iterable + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Publisher + * @return a Flowable that emits the results of merging the items emitted by the source Publisher with + * the values in the Iterables corresponding to those items, as generated by {@code collectionSelector} + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapIterable(final Function> mapper) { + return flatMapIterable(mapper, bufferSize()); + } + + /** + * Returns a Flowable that merges each item emitted by the source Publisher with the values in an + * Iterable corresponding to that item that is generated by a selector. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s is + * expected to honor backpressure as well. If the source {@code Publisher} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting Iterable + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Publisher + * @param bufferSize + * the number of elements to prefetch from the current Flowable + * @return a Flowable that emits the results of merging the items emitted by the source Publisher with + * the values in the Iterables corresponding to those items, as generated by {@code collectionSelector} + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapIterable(final Function> mapper, int bufferSize) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableFlattenIterable(this, mapper, bufferSize)); + } + + /** + * Returns a Flowable that emits the results of applying a function to the pair of values from the source + * Publisher and an Iterable corresponding to that item that is generated by a selector. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and the source {@code Publisher}s is + * consumed in an unbounded manner (i.e., no backpressure is applied to it).
+ *
Scheduler:
+ *
{@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the collection element type + * @param + * the type of item emitted by the resulting Iterable + * @param mapper + * a function that returns an Iterable sequence of values for each item emitted by the source + * Publisher + * @param resultSelector + * a function that returns an item based on the item emitted by the source Publisher and the + * Iterable returned for that item by the {@code collectionSelector} + * @return a Flowable that emits the items returned by {@code resultSelector} for each item in the source + * Publisher + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapIterable(final Function> mapper, + final BiFunction resultSelector) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.requireNonNull(resultSelector, "resultSelector is null"); + return flatMap(FlowableInternalHelper.flatMapIntoIterable(mapper), resultSelector, false, bufferSize(), bufferSize()); + } + + /** + * Returns a Flowable that merges each item emitted by the source Publisher with the values in an + * Iterable corresponding to that item that is generated by a selector, while limiting the number of concurrent + * subscriptions to these Publishers. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s is + * expected to honor backpressure as well. If the source {@code Publisher} violates the rule, the operator will + * signal a {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the element type of the inner Iterable sequences + * @param + * the type of item emitted by the resulting Publisher + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Publisher + * @param resultSelector + * a function that returns an item based on the item emitted by the source Publisher and the + * Iterable returned for that item by the {@code collectionSelector} + * @param prefetch + * the number of elements to prefetch from the current Flowable + * @return a Flowable that emits the results of merging the items emitted by the source Publisher with + * the values in the Iterables corresponding to those items, as generated by {@code collectionSelector} + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapIterable(final Function> mapper, + final BiFunction resultSelector, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.requireNonNull(resultSelector, "resultSelector is null"); + return flatMap(FlowableInternalHelper.flatMapIntoIterable(mapper), resultSelector, false, bufferSize(), prefetch); + } + + /** + * Maps each element of the upstream Flowable into MaybeSources, subscribes to all of them + * and merges their onSuccess values, in no particular order, into a single Flowable sequence. + *
+ *
Backpressure:
+ *
The operator consumes the upstream in an unbounded manner.
+ *
Scheduler:
+ *
{@code flatMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result value type + * @param mapper the function that received each source value and transforms them into MaybeSources. + * @return the new Flowable instance + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapMaybe(Function> mapper) { + return flatMapMaybe(mapper, false, Integer.MAX_VALUE); + } + + /** + * Maps each element of the upstream Flowable into MaybeSources, subscribes to at most + * {@code maxConcurrency} MaybeSources at a time and merges their onSuccess values, + * in no particular order, into a single Flowable sequence, optionally delaying all errors. + *
+ *
Backpressure:
+ *
If {@code maxConcurrency == Integer.MAX_VALUE} the operator consumes the upstream in an unbounded manner. + * Otherwise, the operator expects the upstream to honor backpressure. If the upstream doesn't support backpressure + * the operator behaves as if {@code maxConcurrency == Integer.MAX_VALUE} was used.
+ *
Scheduler:
+ *
{@code flatMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result value type + * @param mapper the function that received each source value and transforms them into MaybeSources. + * @param delayErrors if true errors from the upstream and inner MaybeSources are delayed until each of them + * terminates. + * @param maxConcurrency the maximum number of active subscriptions to the MaybeSources. + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapMaybe(Function> mapper, boolean delayErrors, int maxConcurrency) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + return RxJavaPlugins.onAssembly(new FlowableFlatMapMaybe(this, mapper, delayErrors, maxConcurrency)); + } + + /** + * Maps each element of the upstream Flowable into SingleSources, subscribes to all of them + * and merges their onSuccess values, in no particular order, into a single Flowable sequence. + *
+ *
Backpressure:
+ *
The operator consumes the upstream in an unbounded manner.
+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result value type + * @param mapper the function that received each source value and transforms them into SingleSources. + * @return the new Flowable instance + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapSingle(Function> mapper) { + return flatMapSingle(mapper, false, Integer.MAX_VALUE); + } + + /** + * Maps each element of the upstream Flowable into SingleSources, subscribes to at most + * {@code maxConcurrency} SingleSources at a time and merges their onSuccess values, + * in no particular order, into a single Flowable sequence, optionally delaying all errors. + *
+ *
Backpressure:
+ *
If {@code maxConcurrency == Integer.MAX_VALUE} the operator consumes the upstream in an unbounded manner. + * Otherwise, the operator expects the upstream to honor backpressure. If the upstream doesn't support backpressure + * the operator behaves as if {@code maxConcurrency == Integer.MAX_VALUE} was used.
+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result value type + * @param mapper the function that received each source value and transforms them into SingleSources. + * @param delayErrors if true errors from the upstream and inner SingleSources are delayed until each of them + * terminates. + * @param maxConcurrency the maximum number of active subscriptions to the SingleSources. + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapSingle(Function> mapper, boolean delayErrors, int maxConcurrency) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + return RxJavaPlugins.onAssembly(new FlowableFlatMapSingle(this, mapper, delayErrors, maxConcurrency)); + } + + /** + * Subscribes to the {@link Publisher} and receives notifications for each element. + *

+ * Alias to {@link #subscribe(Consumer)} + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no + * backpressure is applied to it).
+ *
Scheduler:
+ *
{@code forEach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * {@link Consumer} to execute for each item. + * @return + * a Disposable that allows canceling an asynchronous sequence + * @throws NullPointerException + * if {@code onNext} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.NONE) + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable forEach(Consumer onNext) { + return subscribe(onNext); + } + + /** + * Subscribes to the {@link Publisher} and receives notifications for each element until the + * onNext Predicate returns false. + *

+ * If the Flowable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no + * backpressure is applied to it).
+ *
Scheduler:
+ *
{@code forEachWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * {@link Predicate} to execute for each item. + * @return + * a {@link Disposable} that allows canceling an asynchronous sequence + * @throws NullPointerException + * if {@code onNext} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.NONE) + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable forEachWhile(Predicate onNext) { + return forEachWhile(onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to the {@link Publisher} and receives notifications for each element and error events until the + * onNext Predicate returns false. + *
+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no + * backpressure is applied to it).
+ *
Scheduler:
+ *
{@code forEachWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * {@link Predicate} to execute for each item. + * @param onError + * {@link Consumer} to execute when an error is emitted. + * @return + * a {@link Disposable} that allows canceling an asynchronous sequence + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.NONE) + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable forEachWhile(Predicate onNext, Consumer onError) { + return forEachWhile(onNext, onError, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to the {@link Publisher} and receives notifications for each element and the terminal events until the + * onNext Predicate returns false. + *
+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no + * backpressure is applied to it).
+ *
Scheduler:
+ *
{@code forEachWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * {@link Predicate} to execute for each item. + * @param onError + * {@link Consumer} to execute when an error is emitted. + * @param onComplete + * {@link Action} to execute when completion is signaled. + * @return + * a {@link Disposable} that allows canceling an asynchronous sequence + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null, or + * if {@code onComplete} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.NONE) + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable forEachWhile(final Predicate onNext, final Consumer onError, + final Action onComplete) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + + ForEachWhileSubscriber s = new ForEachWhileSubscriber(onNext, onError, onComplete); + subscribe(s); + return s; + } + + /** + * Groups the items emitted by a {@code Publisher} according to a specified criterion, and emits these + * grouped items as {@link GroupedFlowable}s. The emitted {@code GroupedPublisher} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} cancels before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedPublisher} emission. + *

+ * + *

+ * Note: A {@link GroupedFlowable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * + *

+ *
Backpressure:
+ *
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} + * is consumed in a bounded mode (i.e., requested a fixed amount upfront and replenished based on + * downstream consumption). Note that both the returned and its inner {@code Publisher}s use + * unbounded internal buffers and if the source {@code Publisher} doesn't honor backpressure, that may + * lead to {@code OutOfMemoryError}.
+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param + * the key type + * @return a {@code Publisher} that emits {@link GroupedFlowable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Publisher that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> groupBy(Function keySelector) { + return groupBy(keySelector, Functions.identity(), false, bufferSize()); + } + + /** + * Groups the items emitted by a {@code Publisher} according to a specified criterion, and emits these + * grouped items as {@link GroupedFlowable}s. The emitted {@code GroupedPublisher} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} cancels before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedPublisher} emission. + *

+ * + *

+ * Note: A {@link GroupedFlowable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + *

+ *
Backpressure:
+ *
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} + * is consumed in a bounded mode (i.e., requested a fixed amount upfront and replenished based on + * downstream consumption). Note that both the returned and its inner {@code Publisher}s use + * unbounded internal buffers and if the source {@code Publisher} doesn't honor backpressure, that may + * lead to {@code OutOfMemoryError}.
+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param + * the key type + * @param delayError + * if true, the exception from the current Flowable is delayed in each group until that specific group emitted + * the normal values; if false, the exception bypasses values in the groups and is reported immediately. + * @return a {@code Publisher} that emits {@link GroupedFlowable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Publisher that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> groupBy(Function keySelector, boolean delayError) { + return groupBy(keySelector, Functions.identity(), delayError, bufferSize()); + } + + /** + * Groups the items emitted by a {@code Publisher} according to a specified criterion, and emits these + * grouped items as {@link GroupedFlowable}s. The emitted {@code GroupedPublisher} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} cancels before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedPublisher} emission. + *

+ * + *

+ * Note: A {@link GroupedFlowable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * + *

+ *
Backpressure:
+ *
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} + * is consumed in a bounded mode (i.e., requested a fixed amount upfront and replenished based on + * downstream consumption). Note that both the returned and its inner {@code Publisher}s use + * unbounded internal buffers and if the source {@code Publisher} doesn't honor backpressure, that may + * lead to {@code OutOfMemoryError}.
+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param valueSelector + * a function that extracts the return element for each item + * @param + * the key type + * @param + * the element type + * @return a {@code Publisher} that emits {@link GroupedFlowable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Publisher that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> groupBy(Function keySelector, + Function valueSelector) { + return groupBy(keySelector, valueSelector, false, bufferSize()); + } + + /** + * Groups the items emitted by a {@code Publisher} according to a specified criterion, and emits these + * grouped items as {@link GroupedFlowable}s. The emitted {@code GroupedPublisher} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} cancels before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedPublisher} emission. + *

+ * + *

+ * Note: A {@link GroupedFlowable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * + *

+ *
Backpressure:
+ *
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} + * is consumed in a bounded mode (i.e., requested a fixed amount upfront and replenished based on + * downstream consumption). Note that both the returned and its inner {@code Publisher}s use + * unbounded internal buffers and if the source {@code Publisher} doesn't honor backpressure, that may + * lead to {@code OutOfMemoryError}.
+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param valueSelector + * a function that extracts the return element for each item + * @param + * the key type + * @param + * the element type + * @param delayError + * if true, the exception from the current Flowable is delayed in each group until that specific group emitted + * the normal values; if false, the exception bypasses values in the groups and is reported immediately. + * @return a {@code Publisher} that emits {@link GroupedFlowable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Publisher that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> groupBy(Function keySelector, + Function valueSelector, boolean delayError) { + return groupBy(keySelector, valueSelector, delayError, bufferSize()); + } + + /** + * Groups the items emitted by a {@code Publisher} according to a specified criterion, and emits these + * grouped items as {@link GroupedFlowable}s. The emitted {@code GroupedPublisher} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} cancels before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedPublisher} emission. + *

+ * + *

+ * Note: A {@link GroupedFlowable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedPublisher}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * + *

+ *
Backpressure:
+ *
Both the returned and its inner {@code Publisher}s honor backpressure and the source {@code Publisher} + * is consumed in a bounded mode (i.e., requested a fixed amount upfront and replenished based on + * downstream consumption). Note that both the returned and its inner {@code Publisher}s use + * unbounded internal buffers and if the source {@code Publisher} doesn't honor backpressure, that may + * lead to {@code OutOfMemoryError}.
+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param valueSelector + * a function that extracts the return element for each item + * @param delayError + * if true, the exception from the current Flowable is delayed in each group until that specific group emitted + * the normal values; if false, the exception bypasses values in the groups and is reported immediately. + * @param bufferSize + * the hint for how many {@link GroupedFlowable}s and element in each {@link GroupedFlowable} should be buffered + * @param + * the key type + * @param + * the element type + * @return a {@code Publisher} that emits {@link GroupedFlowable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Publisher that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> groupBy(Function keySelector, + Function valueSelector, + boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + + return RxJavaPlugins.onAssembly(new FlowableGroupBy(this, keySelector, valueSelector, bufferSize, delayError, null)); + } + + /** + * Groups the items emitted by a {@code Publisher} according to a specified criterion, and emits these + * grouped items as {@link GroupedFlowable}s. The emitted {@code GroupedFlowable} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} cancels before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedPublisher} emission. The {@code evictingMapFactory} is used to create a map that will + * be used to hold the {@link GroupedFlowable}s by key. The evicting map created by this factory must + * notify the provided {@code Consumer} with the entry value (not the key!) when an entry in this + * map has been evicted. The next source emission will bring about the completion of the evicted + * {@link GroupedFlowable}s and the arrival of an item with the same key as a completed {@link GroupedFlowable} + * will prompt the creation and emission of a new {@link GroupedFlowable} with that key. + * + *

A use case for specifying an {@code evictingMapFactory} is where the source is infinite and fast and + * over time the number of keys grows enough to be a concern in terms of the memory footprint of the + * internal hash map containing the {@link GroupedFlowable}s. + * + *

The map created by an {@code evictingMapFactory} must be thread-safe. + * + *

An example of an {@code evictingMapFactory} using CacheBuilder from the Guava library is below: + * + *


+     * Function<Consumer<Object>, Map<Integer, Object>> evictingMapFactory =
+     *   notify ->
+     *       CacheBuilder
+     *         .newBuilder()
+     *         .maximumSize(3)
+     *         .removalListener(entry -> {
+     *              try {
+     *                  // emit the value not the key!
+     *                  notify.accept(entry.getValue());
+     *              } catch (Exception e) {
+     *                  throw new RuntimeException(e);
+     *              }
+     *            })
+     *         .<Integer, Object> build()
+     *         .asMap();
+     *
+     * // Emit 1000 items but ensure that the
+     * // internal map never has more than 3 items in it
+     * Flowable
+     *   .range(1, 1000)
+     *   // note that number of keys is 10
+     *   .groupBy(x -> x % 10, x -> x, true, 16, evictingMapFactory)
+     *   .flatMap(g -> g)
+     *   .forEach(System.out::println);
+     * 
+ * + *

+ * + *

+ * Note: A {@link GroupedFlowable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedFlowable}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ * Note that the {@link GroupedFlowable}s should be subscribed to as soon as possible, otherwise, + * the unconsumed groups may starve other groups due to the internal backpressure + * coordination of the {@code groupBy} operator. Such hangs can be usually avoided by using + * {@link #flatMap(Function, int)} or {@link #concatMapEager(Function, int, int)} and overriding the default maximum concurrency + * value to be greater or equal to the expected number of groups, possibly using + * {@code Integer.MAX_VALUE} if the number of expected groups is unknown. + * + *

+ *
Backpressure:
+ *
Both the returned and its inner {@code GroupedFlowable}s honor backpressure and the source {@code Publisher} + * is consumed in a bounded mode (i.e., requested a fixed amount upfront and replenished based on + * downstream consumption). Note that both the returned and its inner {@code GroupedFlowable}s use + * unbounded internal buffers and if the source {@code Publisher} doesn't honor backpressure, that may + * lead to {@code OutOfMemoryError}.
+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - beta + * @param keySelector + * a function that extracts the key for each item + * @param valueSelector + * a function that extracts the return element for each item + * @param delayError + * if true, the exception from the current Flowable is delayed in each group until that specific group emitted + * the normal values; if false, the exception bypasses values in the groups and is reported immediately. + * @param bufferSize + * the hint for how many {@link GroupedFlowable}s and element in each {@link GroupedFlowable} should be buffered + * @param evictingMapFactory + * The factory used to create a map that will be used by the implementation to hold the + * {@link GroupedFlowable}s. The evicting map created by this factory must + * notify the provided {@code Consumer} with the entry value (not the key!) when + * an entry in this map has been evicted. The next source emission will bring about the + * completion of the evicted {@link GroupedFlowable}s. See example above. + * @param + * the key type + * @param + * the element type + * @return a {@code Publisher} that emits {@link GroupedFlowable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Publisher that share that + * key value + * @see ReactiveX operators documentation: GroupBy + * + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> groupBy(Function keySelector, + Function valueSelector, + boolean delayError, int bufferSize, + Function, ? extends Map> evictingMapFactory) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(evictingMapFactory, "evictingMapFactory is null"); + + return RxJavaPlugins.onAssembly(new FlowableGroupBy(this, keySelector, valueSelector, bufferSize, delayError, evictingMapFactory)); + } + + /** + * Returns a Flowable that correlates two Publishers when they overlap in time and groups the results. + *

+ * There are no guarantees in what order the items get combined when multiple + * items from one or both source Publishers overlap. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't support backpressure and consumes all participating {@code Publisher}s in + * an unbounded mode (i.e., not applying any backpressure to them).
+ *
Scheduler:
+ *
{@code groupJoin} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the right Publisher source + * @param the element type of the left duration Publishers + * @param the element type of the right duration Publishers + * @param the result type + * @param other + * the other Publisher to correlate items from the source Publisher with + * @param leftEnd + * a function that returns a Publisher whose emissions indicate the duration of the values of + * the source Publisher + * @param rightEnd + * a function that returns a Publisher whose emissions indicate the duration of the values of + * the {@code right} Publisher + * @param resultSelector + * a function that takes an item emitted by each Publisher and returns the value to be emitted + * by the resulting Publisher + * @return a Flowable that emits items based on combining those items emitted by the source Publishers + * whose durations overlap + * @see ReactiveX operators documentation: Join + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable groupJoin( + Publisher other, + Function> leftEnd, + Function> rightEnd, + BiFunction, ? extends R> resultSelector) { + ObjectHelper.requireNonNull(other, "other is null"); + ObjectHelper.requireNonNull(leftEnd, "leftEnd is null"); + ObjectHelper.requireNonNull(rightEnd, "rightEnd is null"); + ObjectHelper.requireNonNull(resultSelector, "resultSelector is null"); + return RxJavaPlugins.onAssembly(new FlowableGroupJoin( + this, other, leftEnd, rightEnd, resultSelector)); + } + + /** + * Hides the identity of this Flowable and its Subscription. + *

Allows hiding extra features such as {@link Processor}'s + * {@link Subscriber} methods or preventing certain identity-based + * optimizations (fusion). + *

+ *
Backpressure:
+ *
The operator is a pass-through for backpressure, the behavior is determined by the upstream's + * backpressure behavior.
+ *
Scheduler:
+ *
{@code hide} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Flowable instance + * + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable hide() { + return RxJavaPlugins.onAssembly(new FlowableHide(this)); + } + + /** + * Ignores all items emitted by the source Publisher and only calls {@code onComplete} or {@code onError}. + *

+ * + *

+ *
Backpressure:
+ *
This operator ignores backpressure as it doesn't emit any elements and consumes the source {@code Publisher} + * in an unbounded manner (i.e., no backpressure is applied to it).
+ *
Scheduler:
+ *
{@code ignoreElements} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Completable that only calls {@code onComplete} or {@code onError}, based on which one is + * called by the source Publisher + * @see ReactiveX operators documentation: IgnoreElements + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable ignoreElements() { + return RxJavaPlugins.onAssembly(new FlowableIgnoreElementsCompletable(this)); + } + + /** + * Returns a Single that emits {@code true} if the source Publisher is empty, otherwise {@code false}. + *

+ * In Rx.Net this is negated as the {@code any} Subscriber but we renamed this in RxJava to better match Java + * naming idioms. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code isEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Flowable that emits a Boolean + * @see ReactiveX operators documentation: Contains + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single isEmpty() { + return all(Functions.alwaysFalse()); + } + + /** + * Correlates the items emitted by two Publishers based on overlapping durations. + *

+ * There are no guarantees in what order the items get combined when multiple + * items from one or both source Publishers overlap. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't support backpressure and consumes all participating {@code Publisher}s in + * an unbounded mode (i.e., not applying any backpressure to them).
+ *
Scheduler:
+ *
{@code join} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the right Publisher source + * @param the element type of the left duration Publishers + * @param the element type of the right duration Publishers + * @param the result type + * @param other + * the second Publisher to join items from + * @param leftEnd + * a function to select a duration for each item emitted by the source Publisher, used to + * determine overlap + * @param rightEnd + * a function to select a duration for each item emitted by the {@code right} Publisher, used to + * determine overlap + * @param resultSelector + * a function that computes an item to be emitted by the resulting Publisher for any two + * overlapping items emitted by the two Publishers + * @return a Flowable that emits items correlating to items emitted by the source Publishers that have + * overlapping durations + * @see ReactiveX operators documentation: Join + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable join( + Publisher other, + Function> leftEnd, + Function> rightEnd, + BiFunction resultSelector) { + ObjectHelper.requireNonNull(other, "other is null"); + ObjectHelper.requireNonNull(leftEnd, "leftEnd is null"); + ObjectHelper.requireNonNull(rightEnd, "rightEnd is null"); + ObjectHelper.requireNonNull(resultSelector, "resultSelector is null"); + return RxJavaPlugins.onAssembly(new FlowableJoin( + this, other, leftEnd, rightEnd, resultSelector)); + } + + /** + * Returns a Maybe that emits the last item emitted by this Flowable or completes if + * this Flowable is empty. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code lastElement} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a new Maybe instance + * @see ReactiveX operators documentation: Last + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe lastElement() { + return RxJavaPlugins.onAssembly(new FlowableLastMaybe(this)); + } + + /** + * Returns a Single that emits only the last item emitted by this Flowable, or a default item + * if this Flowable completes without emitting any items. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code last} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * the default item to emit if the source Publisher is empty + * @return the new Single instance + * @see ReactiveX operators documentation: Last + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single last(T defaultItem) { + ObjectHelper.requireNonNull(defaultItem, "defaultItem"); + return RxJavaPlugins.onAssembly(new FlowableLastSingle(this, defaultItem)); + } + + /** + * Returns a Single that emits only the last item emitted by this Flowable or signals + * a {@link NoSuchElementException} if this Flowable is empty. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code lastOrError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Single instance + * @see ReactiveX operators documentation: Last + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single lastOrError() { + return RxJavaPlugins.onAssembly(new FlowableLastSingle(this, null)); + } + + /** + * This method requires advanced knowledge about building operators, please consider + * other standard composition methods first; + * Returns a {@code Flowable} which, when subscribed to, invokes the {@link FlowableOperator#apply(Subscriber) apply(Subscriber)} method + * of the provided {@link FlowableOperator} for each individual downstream {@link Subscriber} and allows the + * insertion of a custom operator by accessing the downstream's {@link Subscriber} during this subscription phase + * and providing a new {@code Subscriber}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + *

+ * Generally, such a new {@code Subscriber} will wrap the downstream's {@code Subscriber} and forwards the + * {@code onNext}, {@code onError} and {@code onComplete} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code cancel} and {@code request} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. + *

+ * Example: + *


+     * // Step 1: Create the consumer type that will be returned by the FlowableOperator.apply():
+     *
+     * public final class CustomSubscriber<T> implements FlowableSubscriber<T>, Subscription {
+     *
+     *     // The downstream's Subscriber that will receive the onXXX events
+     *     final Subscriber<? super String> downstream;
+     *
+     *     // The connection to the upstream source that will call this class' onXXX methods
+     *     Subscription upstream;
+     *
+     *     // The constructor takes the downstream subscriber and usually any other parameters
+     *     public CustomSubscriber(Subscriber<? super String> downstream) {
+     *         this.downstream = downstream;
+     *     }
+     *
+     *     // In the subscription phase, the upstream sends a Subscription to this class
+     *     // and subsequently this class has to send a Subscription to the downstream.
+     *     // Note that relaying the upstream's Subscription instance directly is not allowed in RxJava
+     *     @Override
+     *     public void onSubscribe(Subscription s) {
+     *         if (upstream != null) {
+     *             s.cancel();
+     *         } else {
+     *             upstream = s;
+     *             downstream.onSubscribe(this);
+     *         }
+     *     }
+     *
+     *     // The upstream calls this with the next item and the implementation's
+     *     // responsibility is to emit an item to the downstream based on the intended
+     *     // business logic, or if it can't do so for the particular item,
+     *     // request more from the upstream
+     *     @Override
+     *     public void onNext(T item) {
+     *         String str = item.toString();
+     *         if (str.length() < 2) {
+     *             downstream.onNext(str);
+     *         } else {
+     *             upstream.request(1);
+     *         }
+     *     }
+     *
+     *     // Some operators may handle the upstream's error while others
+     *     // could just forward it to the downstream.
+     *     @Override
+     *     public void onError(Throwable throwable) {
+     *         downstream.onError(throwable);
+     *     }
+     *
+     *     // When the upstream completes, usually the downstream should complete as well.
+     *     @Override
+     *     public void onComplete() {
+     *         downstream.onComplete();
+     *     }
+     *
+     *     // Some operators have to intercept the downstream's request calls to trigger
+     *     // the emission of queued items while others can simply forward the request
+     *     // amount as is.
+     *     @Override
+     *     public void request(long n) {
+     *         upstream.request(n);
+     *     }
+     *
+     *     // Some operators may use their own resources which should be cleaned up if
+     *     // the downstream cancels the flow before it completed. Operators without
+     *     // resources can simply forward the cancellation to the upstream.
+     *     // In some cases, a canceled flag may be set by this method so that other parts
+     *     // of this class may detect the cancellation and stop sending events
+     *     // to the downstream.
+     *     @Override
+     *     public void cancel() {
+     *         upstream.cancel();
+     *     }
+     * }
+     *
+     * // Step 2: Create a class that implements the FlowableOperator interface and
+     * //         returns the custom consumer type from above in its apply() method.
+     * //         Such class may define additional parameters to be submitted to
+     * //         the custom consumer type.
+     *
+     * final class CustomOperator<T> implements FlowableOperator<String> {
+     *     @Override
+     *     public Subscriber<? super String> apply(Subscriber<? super T> upstream) {
+     *         return new CustomSubscriber<T>(upstream);
+     *     }
+     * }
+     *
+     * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it
+     * //         or reusing an existing one.
+     *
+     * Flowable.range(5, 10)
+     * .lift(new CustomOperator<Integer>())
+     * .test()
+     * .assertResult("5", "6", "7", "8", "9");
+     * 
+ *

+ * Creating custom operators can be complicated and it is recommended one consults the + * RxJava wiki: Writing operators page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. + *

+ * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Flowable} + * class and creating a {@link FlowableTransformer} with it is recommended. + *

+ * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code Subscriber} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Flowable}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return a {@code Subscriber} that should immediately cancel the upstream's {@code Subscription} in its + * {@code onSubscribe} method. Again, using a {@code FlowableTransformer} and extending the {@code Flowable} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. + *

+ *
Backpressure:
+ *
The {@code Subscriber} instance returned by the {@link FlowableOperator} is responsible to be + * backpressure-aware or document the fact that the consumer of the returned {@code Publisher} has to apply one of + * the {@code onBackpressureXXX} operators.
+ *
Scheduler:
+ *
{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link FlowableOperator} may use a {@code Scheduler} to support its own asynchronous behavior.
+ *
+ * + * @param the output value type + * @param lifter the {@link FlowableOperator} that receives the downstream's {@code Subscriber} and should return + * a {@code Subscriber} with custom behavior to be used as the consumer for the current + * {@code Flowable}. + * @return the new Flowable instance + * @see RxJava wiki: Writing operators + * @see #compose(FlowableTransformer) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable lift(FlowableOperator lifter) { + ObjectHelper.requireNonNull(lifter, "lifter is null"); + return RxJavaPlugins.onAssembly(new FlowableLift(this, lifter)); + } + + /** + * Limits both the number of upstream items (after which the sequence completes) + * and the total downstream request amount requested from the upstream to + * possibly prevent the creation of excess items by the upstream. + *

+ * The operator requests at most the given {@code count} of items from upstream even + * if the downstream requests more than that. For example, given a {@code limit(5)}, + * if the downstream requests 1, a request of 1 is submitted to the upstream + * and the operator remembers that only 4 items can be requested now on. A request + * of 5 at this point will request 4 from the upstream and any subsequent requests will + * be ignored. + *

+ * Note that requests are negotiated on an operator boundary and {@code limit}'s amount + * may not be preserved further upstream. For example, + * {@code source.observeOn(Schedulers.computation()).limit(5)} will still request the + * default (128) elements from the given {@code source}. + *

+ * The main use of this operator is with sources that are async boundaries that + * don't interfere with request amounts, such as certain {@code Flowable}-based + * network endpoints that relay downstream request amounts unchanged and are, therefore, + * prone to trigger excessive item creation/transmission over the network. + *

+ *
Backpressure:
+ *
The operator requests a total of the given {@code count} items from the upstream.
+ *
Scheduler:
+ *
{@code limit} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.6 - experimental + * @param count the maximum number of items and the total request amount, non-negative. + * Zero will immediately cancel the upstream on subscription and complete + * the downstream. + * @return the new Flowable instance + * @see #take(long) + * @see #rebatchRequests(int) + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + public final Flowable limit(long count) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } + return RxJavaPlugins.onAssembly(new FlowableLimit(this, count)); + } + + /** + * Returns a Flowable that applies a specified function to each item emitted by the source Publisher and + * emits the results of these function applications. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code map} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output type + * @param mapper + * a function to apply to each item emitted by the Publisher + * @return a Flowable that emits the items from the source Publisher, transformed by the specified + * function + * @see ReactiveX operators documentation: Map + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable map(Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableMap(this, mapper)); + } + + /** + * Returns a Flowable that represents all of the emissions and notifications from the source + * Publisher into emissions marked with their original types within {@link Notification} objects. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and expects it from the source {@code Publisher}. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code materialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Flowable that emits items that are the result of materializing the items and notifications + * of the source Publisher + * @see ReactiveX operators documentation: Materialize + * @see #dematerialize(Function) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> materialize() { + return RxJavaPlugins.onAssembly(new FlowableMaterialize(this)); + } + + /** + * Flattens this and another Publisher into a single Publisher, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Publishers so that they appear as a single Publisher, by + * using the {@code mergeWith} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. This and the other {@code Publisher}s are expected to honor + * backpressure; if violated, the operator may signal {@code MissingBackpressureException}.
+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * a Publisher to be merged + * @return a Flowable that emits all of the items emitted by the source Publishers + * @see ReactiveX operators documentation: Merge + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable mergeWith(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return merge(this, other); + } + + /** + * Merges the sequence of items of this Flowable with the success value of the other SingleSource. + *

+ * + *

+ * The success value of the other {@code SingleSource} can get interleaved at any point of this + * {@code Flowable} sequence. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and ensures the success item from the + * {@code SingleSource} is emitted only when there is a downstream demand.
+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the {@code SingleSource} whose success value to merge with + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable mergeWith(@NonNull SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableMergeWithSingle(this, other)); + } + + /** + * Merges the sequence of items of this Flowable with the success value of the other MaybeSource + * or waits for both to complete normally if the MaybeSource is empty. + *

+ * + *

+ * The success value of the other {@code MaybeSource} can get interleaved at any point of this + * {@code Flowable} sequence. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and ensures the success item from the + * {@code MaybeSource} is emitted only when there is a downstream demand.
+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the {@code MaybeSource} which provides a success value to merge with or completes + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable mergeWith(@NonNull MaybeSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableMergeWithMaybe(this, other)); + } + + /** + * Relays the items of this Flowable and completes only when the other CompletableSource completes + * as well. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the {@code CompletableSource} to await for completion + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable mergeWith(@NonNull CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableMergeWithCompletable(this, other)); + } + + /** + * Modifies a Publisher to perform its emissions and notifications on a specified {@link Scheduler}, + * asynchronously with a bounded buffer of {@link #bufferSize()} slots. + * + *

Note that onError notifications will cut ahead of onNext notifications on the emission thread if Scheduler is truly + * asynchronous. If strict event ordering is required, consider using the {@link #observeOn(Scheduler, boolean)} overload. + *

+ * + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. + *

+ *
Backpressure:
+ *
This operator honors backpressure from downstream and expects it from the source {@code Publisher}. Violating this + * expectation will lead to {@code MissingBackpressureException}. This is the most common operator where the exception + * pops up; look for sources up the chain that don't support backpressure, + * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any + * of the {@code onBackpressureXXX} operators before applying {@code observeOn} itself.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to notify {@link Subscriber}s on + * @return the source Publisher modified so that its {@link Subscriber}s are notified on the specified + * {@link Scheduler} + * @see ReactiveX operators documentation: ObserveOn + * @see RxJava Threading Examples + * @see #subscribeOn + * @see #observeOn(Scheduler, boolean) + * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable observeOn(Scheduler scheduler) { + return observeOn(scheduler, false, bufferSize()); + } + + /** + * Modifies a Publisher to perform its emissions and notifications on a specified {@link Scheduler}, + * asynchronously with a bounded buffer and optionally delays onError notifications. + *

+ * + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. + *

+ *
Backpressure:
+ *
This operator honors backpressure from downstream and expects it from the source {@code Publisher}. Violating this + * expectation will lead to {@code MissingBackpressureException}. This is the most common operator where the exception + * pops up; look for sources up the chain that don't support backpressure, + * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any + * of the {@code onBackpressureXXX} operators before applying {@code observeOn} itself.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to notify {@link Subscriber}s on + * @param delayError + * indicates if the onError notification may not cut ahead of onNext notification on the other side of the + * scheduling boundary. If true a sequence ending in onError will be replayed in the same order as was received + * from upstream + * @return the source Publisher modified so that its {@link Subscriber}s are notified on the specified + * {@link Scheduler} + * @see ReactiveX operators documentation: ObserveOn + * @see RxJava Threading Examples + * @see #subscribeOn + * @see #observeOn(Scheduler) + * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler, boolean) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable observeOn(Scheduler scheduler, boolean delayError) { + return observeOn(scheduler, delayError, bufferSize()); + } + + /** + * Modifies a Publisher to perform its emissions and notifications on a specified {@link Scheduler}, + * asynchronously with a bounded buffer of configurable size and optionally delays onError notifications. + *

+ * + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. + *

+ *
Backpressure:
+ *
This operator honors backpressure from downstream and expects it from the source {@code Publisher}. Violating this + * expectation will lead to {@code MissingBackpressureException}. This is the most common operator where the exception + * pops up; look for sources up the chain that don't support backpressure, + * such as {@code interval}, {@code timer}, {code PublishSubject} or {@code BehaviorSubject} and apply any + * of the {@code onBackpressureXXX} operators before applying {@code observeOn} itself.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to notify {@link Subscriber}s on + * @param delayError + * indicates if the onError notification may not cut ahead of onNext notification on the other side of the + * scheduling boundary. If true a sequence ending in onError will be replayed in the same order as was received + * from upstream + * @param bufferSize the size of the buffer. + * @return the source Publisher modified so that its {@link Subscriber}s are notified on the specified + * {@link Scheduler} + * @see ReactiveX operators documentation: ObserveOn + * @see RxJava Threading Examples + * @see #subscribeOn + * @see #observeOn(Scheduler) + * @see #observeOn(Scheduler, boolean) + * @see #delay(long, TimeUnit, Scheduler, boolean) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable observeOn(Scheduler scheduler, boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableObserveOn(this, scheduler, delayError, bufferSize)); + } + + /** + * Filters the items emitted by a Publisher, only emitting those of the specified type. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code ofType} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output type + * @param clazz + * the class type to filter the items emitted by the source Publisher + * @return a Flowable that emits items from the source Publisher of type {@code clazz} + * @see ReactiveX operators documentation: Filter + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable ofType(final Class clazz) { + ObjectHelper.requireNonNull(clazz, "clazz is null"); + return filter(Functions.isInstanceOf(clazz)).cast(clazz); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer these + * items indefinitely until they can be emitted. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the source Publisher modified to buffer items to the extent system resources allow + * @see ReactiveX operators documentation: backpressure operators + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureBuffer() { + return onBackpressureBuffer(bufferSize(), false, true); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer these + * items indefinitely until they can be emitted. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param delayError + * if true, an exception from the current Flowable is delayed until all buffered elements have been + * consumed by the downstream; if false, an exception is immediately signaled to the downstream, skipping + * any buffered element + * @return the source Publisher modified to buffer items to the extent system resources allow + * @see ReactiveX operators documentation: backpressure operators + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureBuffer(boolean delayError) { + return onBackpressureBuffer(bufferSize(), delayError, true); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, and canceling the source. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacity number of slots available in the buffer. + * @return the source {@code Publisher} modified to buffer items up to the given capacity. + * @see ReactiveX operators documentation: backpressure operators + * @since 1.1.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureBuffer(int capacity) { + return onBackpressureBuffer(capacity, false, false); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, and canceling the source. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacity number of slots available in the buffer. + * @param delayError + * if true, an exception from the current Flowable is delayed until all buffered elements have been + * consumed by the downstream; if false, an exception is immediately signaled to the downstream, skipping + * any buffered element + * @return the source {@code Publisher} modified to buffer items up to the given capacity. + * @see ReactiveX operators documentation: backpressure operators + * @since 1.1.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureBuffer(int capacity, boolean delayError) { + return onBackpressureBuffer(capacity, delayError, false); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, and canceling the source. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacity number of slots available in the buffer. + * @param delayError + * if true, an exception from the current Flowable is delayed until all buffered elements have been + * consumed by the downstream; if false, an exception is immediately signaled to the downstream, skipping + * any buffered element + * @param unbounded + * if true, the capacity value is interpreted as the internal "island" size of the unbounded buffer + * @return the source {@code Publisher} modified to buffer items up to the given capacity. + * @see ReactiveX operators documentation: backpressure operators + * @since 1.1.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureBuffer(int capacity, boolean delayError, boolean unbounded) { + ObjectHelper.verifyPositive(capacity, "capacity"); + return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBuffer(this, capacity, unbounded, delayError, Functions.EMPTY_ACTION)); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, canceling the source, and notifying the producer with {@code onOverflow}. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacity number of slots available in the buffer. + * @param delayError + * if true, an exception from the current Flowable is delayed until all buffered elements have been + * consumed by the downstream; if false, an exception is immediately signaled to the downstream, skipping + * any buffered element + * @param unbounded + * if true, the capacity value is interpreted as the internal "island" size of the unbounded buffer + * @param onOverflow action to execute if an item needs to be buffered, but there are no available slots. Null is allowed. + * @return the source {@code Publisher} modified to buffer items up to the given capacity + * @see ReactiveX operators documentation: backpressure operators + * @since 1.1.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureBuffer(int capacity, boolean delayError, boolean unbounded, + Action onOverflow) { + ObjectHelper.requireNonNull(onOverflow, "onOverflow is null"); + ObjectHelper.verifyPositive(capacity, "capacity"); + return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBuffer(this, capacity, unbounded, delayError, onOverflow)); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to + * a given amount of items until they can be emitted. The resulting Publisher will signal + * a {@code BufferOverflowException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered + * items, canceling the source, and notifying the producer with {@code onOverflow}. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacity number of slots available in the buffer. + * @param onOverflow action to execute if an item needs to be buffered, but there are no available slots. Null is allowed. + * @return the source {@code Publisher} modified to buffer items up to the given capacity + * @see ReactiveX operators documentation: backpressure operators + * @since 1.1.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureBuffer(int capacity, Action onOverflow) { + return onBackpressureBuffer(capacity, false, false, onOverflow); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to buffer up to + * a given amount of items until they can be emitted. The resulting Publisher will behave as determined + * by {@code overflowStrategy} if the buffer capacity is exceeded. + * + *
    + *
  • {@code BackpressureOverflow.Strategy.ON_OVERFLOW_ERROR} (default) will call {@code onError} dropping all undelivered items, + * canceling the source, and notifying the producer with {@code onOverflow}.
  • + *
  • {@code BackpressureOverflow.Strategy.ON_OVERFLOW_DROP_LATEST} will drop any new items emitted by the producer while + * the buffer is full, without generating any {@code onError}. Each drop will, however, invoke {@code onOverflow} + * to signal the overflow to the producer.
  • + *
  • {@code BackpressureOverflow.Strategy.ON_OVERFLOW_DROP_OLDEST} will drop the oldest items in the buffer in order to make + * room for newly emitted ones. Overflow will not generate an{@code onError}, but each drop will invoke + * {@code onOverflow} to signal the overflow to the producer.
  • + *
+ * + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacity number of slots available in the buffer. + * @param onOverflow action to execute if an item needs to be buffered, but there are no available slots. Null is allowed. + * @param overflowStrategy how should the {@code Publisher} react to buffer overflows. Null is not allowed. + * @return the source {@code Flowable} modified to buffer items up to the given capacity + * @see ReactiveX operators documentation: backpressure operators + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureBuffer(long capacity, Action onOverflow, BackpressureOverflowStrategy overflowStrategy) { + ObjectHelper.requireNonNull(overflowStrategy, "overflowStrategy is null"); + ObjectHelper.verifyPositive(capacity, "capacity"); + return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBufferStrategy(this, capacity, onOverflow, overflowStrategy)); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to discard, + * rather than emit, those items that its Subscriber is not prepared to observe. + *

+ * + *

+ * If the downstream request count hits 0 then the Publisher will refrain from calling {@code onNext} until + * the Subscriber invokes {@code request(n)} again to increase the request count. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureDrop} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the source Publisher modified to drop {@code onNext} notifications on overflow + * @see ReactiveX operators documentation: backpressure operators + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureDrop() { + return RxJavaPlugins.onAssembly(new FlowableOnBackpressureDrop(this)); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to discard, + * rather than emit, those items that its Subscriber is not prepared to observe. + *

+ * + *

+ * If the downstream request count hits 0 then the Publisher will refrain from calling {@code onNext} until + * the Subscriber invokes {@code request(n)} again to increase the request count. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureDrop} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onDrop the action to invoke for each item dropped. onDrop action should be fast and should never block. + * @return the source Publisher modified to drop {@code onNext} notifications on overflow + * @see ReactiveX operators documentation: backpressure operators + * @since 1.1.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureDrop(Consumer onDrop) { + ObjectHelper.requireNonNull(onDrop, "onDrop is null"); + return RxJavaPlugins.onAssembly(new FlowableOnBackpressureDrop(this, onDrop)); + } + + /** + * Instructs a Publisher that is emitting items faster than its Subscriber can consume them to + * hold onto the latest value and emit that on request. + *

+ * + *

+ * Its behavior is logically equivalent to {@code blockingLatest()} with the exception that + * the downstream is not blocking while requesting more values. + *

+ * Note that if the upstream Publisher does support backpressure, this operator ignores that capability + * and doesn't propagate any backpressure requests from downstream. + *

+ * Note that due to the nature of how backpressure requests are propagated through subscribeOn/observeOn, + * requesting more than 1 from downstream doesn't guarantee a continuous delivery of onNext events. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded + * manner (i.e., not applying backpressure to it).
+ *
Scheduler:
+ *
{@code onBackpressureLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the source Publisher modified so that it emits the most recently-received item upon request + * @since 1.1.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onBackpressureLatest() { + return RxJavaPlugins.onAssembly(new FlowableOnBackpressureLatest(this)); + } + + /** + * Instructs a Publisher to pass control to another Publisher rather than invoking + * {@link Subscriber#onError onError} if it encounters an error. + *

+ * + *

+ * By default, when a Publisher encounters an error that prevents it from emitting the expected item to + * its {@link Subscriber}, the Publisher invokes its Subscriber's {@code onError} method, and then quits + * without invoking any more of its Subscriber's methods. The {@code onErrorResumeNext} method changes this + * behavior. If you pass a function that returns a Publisher ({@code resumeFunction}) to + * {@code onErrorResumeNext}, if the original Publisher encounters an error, instead of invoking its + * Subscriber's {@code onError} method, it will instead relinquish control to the Publisher returned from + * {@code resumeFunction}, which will invoke the Subscriber's {@link Subscriber#onNext onNext} method if it is + * able to do so. In such a case, because no Publisher necessarily invokes {@code onError}, the Subscriber + * may never know that an error happened. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. This and the resuming {@code Publisher}s + * are expected to honor backpressure as well. + * If any of them violate this expectation, the operator may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes or + * a {@code MissingBackpressureException} is signaled somewhere downstream.
+ *
Scheduler:
+ *
{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param resumeFunction + * a function that returns a Publisher that will take over if the source Publisher encounters + * an error + * @return the original Publisher, with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onErrorResumeNext(Function> resumeFunction) { + ObjectHelper.requireNonNull(resumeFunction, "resumeFunction is null"); + return RxJavaPlugins.onAssembly(new FlowableOnErrorNext(this, resumeFunction, false)); + } + + /** + * Instructs a Publisher to pass control to another Publisher rather than invoking + * {@link Subscriber#onError onError} if it encounters an error. + *

+ * + *

+ * By default, when a Publisher encounters an error that prevents it from emitting the expected item to + * its {@link Subscriber}, the Publisher invokes its Subscriber's {@code onError} method, and then quits + * without invoking any more of its Subscriber's methods. The {@code onErrorResumeNext} method changes this + * behavior. If you pass another Publisher ({@code resumeSequence}) to a Publisher's + * {@code onErrorResumeNext} method, if the original Publisher encounters an error, instead of invoking its + * Subscriber's {@code onError} method, it will instead relinquish control to {@code resumeSequence} which + * will invoke the Subscriber's {@link Subscriber#onNext onNext} method if it is able to do so. In such a case, + * because no Publisher necessarily invokes {@code onError}, the Subscriber may never know that an error + * happened. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. This and the resuming {@code Publisher}s + * are expected to honor backpressure as well. + * If any of them violate this expectation, the operator may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes or + * {@code MissingBackpressureException} is signaled somewhere downstream.
+ *
Scheduler:
+ *
{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param next + * the next Publisher source that will take over if the source Publisher encounters + * an error + * @return the original Publisher, with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onErrorResumeNext(final Publisher next) { + ObjectHelper.requireNonNull(next, "next is null"); + return onErrorResumeNext(Functions.justFunction(next)); + } + + /** + * Instructs a Publisher to emit an item (returned by a specified function) rather than invoking + * {@link Subscriber#onError onError} if it encounters an error. + *

+ * + *

+ * By default, when a Publisher encounters an error that prevents it from emitting the expected item to + * its {@link Subscriber}, the Publisher invokes its Subscriber's {@code onError} method, and then quits + * without invoking any more of its Subscriber's methods. The {@code onErrorReturn} method changes this + * behavior. If you pass a function ({@code resumeFunction}) to a Publisher's {@code onErrorReturn} + * method, if the original Publisher encounters an error, instead of invoking its Subscriber's + * {@code onError} method, it will instead emit the return value of {@code resumeFunction}. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s is expected to honor + * backpressure as well. If it this expectation is violated, the operator may throw + * {@code IllegalStateException} when the source {@code Publisher} completes or + * {@code MissingBackpressureException} is signaled somewhere downstream.
+ *
Scheduler:
+ *
{@code onErrorReturn} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param valueSupplier + * a function that returns a single value that will be emitted along with a regular onComplete in case + * the current Flowable signals an onError event + * @return the original Publisher with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onErrorReturn(Function valueSupplier) { + ObjectHelper.requireNonNull(valueSupplier, "valueSupplier is null"); + return RxJavaPlugins.onAssembly(new FlowableOnErrorReturn(this, valueSupplier)); + } + + /** + * Instructs a Publisher to emit an item (returned by a specified function) rather than invoking + * {@link Subscriber#onError onError} if it encounters an error. + *

+ * + *

+ * By default, when a Publisher encounters an error that prevents it from emitting the expected item to + * its {@link Subscriber}, the Publisher invokes its Subscriber's {@code onError} method, and then quits + * without invoking any more of its Subscriber's methods. The {@code onErrorReturn} method changes this + * behavior. If you pass a function ({@code resumeFunction}) to a Publisher's {@code onErrorReturn} + * method, if the original Publisher encounters an error, instead of invoking its Subscriber's + * {@code onError} method, it will instead emit the return value of {@code resumeFunction}. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher}s is expected to honor + * backpressure as well. If it this expectation is violated, the operator may throw + * {@code IllegalStateException} when the source {@code Publisher} completes or + * {@code MissingBackpressureException} is signaled somewhere downstream.
+ *
Scheduler:
+ *
{@code onErrorReturnItem} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the value that is emitted along with a regular onComplete in case the current + * Flowable signals an exception + * @return the original Publisher with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onErrorReturnItem(final T item) { + ObjectHelper.requireNonNull(item, "item is null"); + return onErrorReturn(Functions.justFunction(item)); + } + + /** + * Instructs a Publisher to pass control to another Publisher rather than invoking + * {@link Subscriber#onError onError} if it encounters an {@link Exception}. + *

+ * This differs from {@link #onErrorResumeNext} in that this one does not handle {@link Throwable} + * or {@link Error} but lets those continue through. + *

+ * + *

+ * By default, when a Publisher encounters an exception that prevents it from emitting the expected item + * to its {@link Subscriber}, the Publisher invokes its Subscriber's {@code onError} method, and then quits + * without invoking any more of its Subscriber's methods. The {@code onExceptionResumeNext} method changes + * this behavior. If you pass another Publisher ({@code resumeSequence}) to a Publisher's + * {@code onExceptionResumeNext} method, if the original Publisher encounters an exception, instead of + * invoking its Subscriber's {@code onError} method, it will instead relinquish control to + * {@code resumeSequence} which will invoke the Subscriber's {@link Subscriber#onNext onNext} method if it is + * able to do so. In such a case, because no Publisher necessarily invokes {@code onError}, the Subscriber + * may never know that an exception happened. + *

+ * You can use this to prevent exceptions from propagating or to supply fallback data should exceptions be + * encountered. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. This and the resuming {@code Publisher}s + * are expected to honor backpressure as well. + * If any of them violate this expectation, the operator may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes or + * {@code MissingBackpressureException} is signaled somewhere downstream.
+ *
Scheduler:
+ *
{@code onExceptionResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param next + * the next Publisher that will take over if the source Publisher encounters + * an exception + * @return the original Publisher, with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onExceptionResumeNext(final Publisher next) { + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new FlowableOnErrorNext(this, Functions.justFunction(next), true)); + } + + /** + * Nulls out references to the upstream producer and downstream Subscriber if + * the sequence is terminated or downstream cancels. + *
+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return a Flowable which nulls out references to the upstream producer and downstream Subscriber if + * the sequence is terminated or downstream cancels + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable onTerminateDetach() { + return RxJavaPlugins.onAssembly(new FlowableDetach(this)); + } + + /** + * Parallelizes the flow by creating multiple 'rails' (equal to the number of CPUs) + * and dispatches the upstream items to them in a round-robin fashion. + *

+ * Note that the rails don't execute in parallel on their own and one needs to + * apply {@link ParallelFlowable#runOn(Scheduler)} to specify the Scheduler where + * each rail will execute. + *

+ * To merge the parallel 'rails' back into a single sequence, use {@link ParallelFlowable#sequential()}. + *

+ * + *

+ *
Backpressure:
+ *
The operator requires the upstream to honor backpressure and each 'rail' honors backpressure + * as well.
+ *
Scheduler:
+ *
{@code parallel} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.5 - experimental; 2.1 - beta + * @return the new ParallelFlowable instance + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + public final ParallelFlowable parallel() { + return ParallelFlowable.from(this); + } + + /** + * Parallelizes the flow by creating the specified number of 'rails' + * and dispatches the upstream items to them in a round-robin fashion. + *

+ * Note that the rails don't execute in parallel on their own and one needs to + * apply {@link ParallelFlowable#runOn(Scheduler)} to specify the Scheduler where + * each rail will execute. + *

+ * To merge the parallel 'rails' back into a single sequence, use {@link ParallelFlowable#sequential()}. + *

+ * + *

+ *
Backpressure:
+ *
The operator requires the upstream to honor backpressure and each 'rail' honors backpressure + * as well.
+ *
Scheduler:
+ *
{@code parallel} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.5 - experimental; 2.1 - beta + * @param parallelism the number of 'rails' to use + * @return the new ParallelFlowable instance + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + public final ParallelFlowable parallel(int parallelism) { + ObjectHelper.verifyPositive(parallelism, "parallelism"); + return ParallelFlowable.from(this, parallelism); + } + + /** + * Parallelizes the flow by creating the specified number of 'rails' + * and dispatches the upstream items to them in a round-robin fashion and + * uses the defined per-'rail' prefetch amount. + *

+ * Note that the rails don't execute in parallel on their own and one needs to + * apply {@link ParallelFlowable#runOn(Scheduler)} to specify the Scheduler where + * each rail will execute. + *

+ * To merge the parallel 'rails' back into a single sequence, use {@link ParallelFlowable#sequential()}. + *

+ * + *

+ *
Backpressure:
+ *
The operator requires the upstream to honor backpressure and each 'rail' honors backpressure + * as well.
+ *
Scheduler:
+ *
{@code parallel} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.5 - experimental; 2.1 - beta + * @param parallelism the number of 'rails' to use + * @param prefetch the number of items each 'rail' should prefetch + * @return the new ParallelFlowable instance + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + public final ParallelFlowable parallel(int parallelism, int prefetch) { + ObjectHelper.verifyPositive(parallelism, "parallelism"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return ParallelFlowable.from(this, parallelism, prefetch); + } + + /** + * Returns a {@link ConnectableFlowable}, which is a variety of Publisher that waits until its + * {@link ConnectableFlowable#connect connect} method is called before it begins emitting items to those + * {@link Subscriber}s that have subscribed to it. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code ConnectableFlowable} honors backpressure for each of its {@code Subscriber}s + * and expects the source {@code Publisher} to honor backpressure as well. If this expectation is violated, + * the operator will signal a {@code MissingBackpressureException} to its {@code Subscriber}s and disconnect.
+ *
Scheduler:
+ *
{@code publish} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link ConnectableFlowable} that upon connection causes the source Publisher to emit items + * to its {@link Subscriber}s + * @see ReactiveX operators documentation: Publish + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final ConnectableFlowable publish() { + return publish(bufferSize()); + } + + /** + * Returns a Flowable that emits the results of invoking a specified selector on items emitted by a + * {@link ConnectableFlowable} that shares a single subscription to the underlying sequence. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the source {@code Publisher} to honor backpressure and if this expectation is + * violated, the operator will signal a {@code MissingBackpressureException} through the {@code Publisher} + * provided to the function. Since the {@code Publisher} returned by the {@code selector} may be + * independent of the provided {@code Publisher} to the function, the output's backpressure behavior + * is determined by this returned {@code Publisher}.
+ *
Scheduler:
+ *
{@code publish} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * a function that can use the multicasted source sequence as many times as needed, without + * causing multiple subscriptions to the source sequence. Subscribers to the given source will + * receive all notifications of the source from the time of the subscription forward. + * @return a Flowable that emits the results of invoking the selector on the items emitted by a {@link ConnectableFlowable} that shares a single subscription to the underlying sequence + * @see ReactiveX operators documentation: Publish + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable publish(Function, ? extends Publisher> selector) { + return publish(selector, bufferSize()); + } + + /** + * Returns a Flowable that emits the results of invoking a specified selector on items emitted by a + * {@link ConnectableFlowable} that shares a single subscription to the underlying sequence. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects the source {@code Publisher} to honor backpressure and if this expectation is + * violated, the operator will signal a {@code MissingBackpressureException} through the {@code Publisher} + * provided to the function. Since the {@code Publisher} returned by the {@code selector} may be + * independent of the provided {@code Publisher} to the function, the output's backpressure behavior + * is determined by this returned {@code Publisher}.
+ *
Scheduler:
+ *
{@code publish} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * a function that can use the multicasted source sequence as many times as needed, without + * causing multiple subscriptions to the source sequence. Subscribers to the given source will + * receive all notifications of the source from the time of the subscription forward. + * @param prefetch + * the number of elements to prefetch from the current Flowable + * @return a Flowable that emits the results of invoking the selector on the items emitted by a {@link ConnectableFlowable} that shares a single subscription to the underlying sequence + * @see ReactiveX operators documentation: Publish + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable publish(Function, ? extends Publisher> selector, int prefetch) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowablePublishMulticast(this, selector, prefetch, false)); + } + + /** + * Returns a {@link ConnectableFlowable}, which is a variety of Publisher that waits until its + * {@link ConnectableFlowable#connect connect} method is called before it begins emitting items to those + * {@link Subscriber}s that have subscribed to it. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code ConnectableFlowable} honors backpressure for each of its {@code Subscriber}s + * and expects the source {@code Publisher} to honor backpressure as well. If this expectation is violated, + * the operator will signal a {@code MissingBackpressureException} to its {@code Subscriber}s and disconnect.
+ *
Scheduler:
+ *
{@code publish} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param bufferSize + * the number of elements to prefetch from the current Flowable + * @return a {@link ConnectableFlowable} that upon connection causes the source Publisher to emit items + * to its {@link Subscriber}s + * @see ReactiveX operators documentation: Publish + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final ConnectableFlowable publish(int bufferSize) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return FlowablePublish.create(this, bufferSize); + } + + /** + * Requests {@code n} initially from the upstream and then 75% of {@code n} subsequently + * after 75% of {@code n} values have been emitted to the downstream. + * + *

This operator allows preventing the downstream to trigger unbounded mode via {@code request(Long.MAX_VALUE)} + * or compensate for the per-item overhead of small and frequent requests. + * + *

+ *
Backpressure:
+ *
The operator expects backpressure from upstream and honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code rebatchRequests} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param n the initial request amount, further request will happen after 75% of this value + * @return the Publisher that rebatches request amounts from downstream + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable rebatchRequests(int n) { + return observeOn(ImmediateThinScheduler.INSTANCE, true, n); + } + + /** + * Returns a Maybe that applies a specified accumulator function to the first item emitted by a source + * Publisher, then feeds the result of that function along with the second item emitted by the source + * Publisher into the same function, and so on until all items have been emitted by the finite source Publisher, + * and emits the final result from the final call to your function as its sole item. + *

+ * + *

+ * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," + * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method + * that does a similar operation on lists. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure of its downstream consumer and consumes the + * upstream source in unbounded mode.
+ *
Scheduler:
+ *
{@code reduce} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param reducer + * an accumulator function to be invoked on each item emitted by the source Publisher, whose + * result will be used in the next accumulator call + * @return a Maybe that emits a single item that is the result of accumulating the items emitted by + * the source Flowable + * @see ReactiveX operators documentation: Reduce + * @see Wikipedia: Fold (higher-order function) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe reduce(BiFunction reducer) { + ObjectHelper.requireNonNull(reducer, "reducer is null"); + return RxJavaPlugins.onAssembly(new FlowableReduceMaybe(this, reducer)); + } + + /** + * Returns a Single that applies a specified accumulator function to the first item emitted by a source + * Publisher and a specified seed value, then feeds the result of that function along with the second item + * emitted by a Publisher into the same function, and so on until all items have been emitted by the + * finite source Publisher, emitting the final result from the final call to your function as its sole item. + *

+ * + *

+ * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," + * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method + * that does a similar operation on lists. + *

+ * Note that the {@code seed} is shared among all subscribers to the resulting Publisher + * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer + * the application of this operator via {@link #defer(Callable)}: + *


+     * Publisher<T> source = ...
+     * Single.defer(() -> source.reduce(new ArrayList<>(), (list, item) -> list.add(item)));
+     *
+     * // alternatively, by using compose to stay fluent
+     *
+     * source.compose(o ->
+     *     Flowable.defer(() -> o.reduce(new ArrayList<>(), (list, item) -> list.add(item)).toFlowable())
+     * ).firstOrError();
+     *
+     * // or, by using reduceWith instead of reduce
+     *
+     * source.reduceWith(() -> new ArrayList<>(), (list, item) -> list.add(item)));
+     * 
+ *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure of its downstream consumer and consumes the + * upstream source in unbounded mode.
+ *
Scheduler:
+ *
{@code reduce} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the accumulator and output value type + * @param seed + * the initial (seed) accumulator value + * @param reducer + * an accumulator function to be invoked on each item emitted by the source Publisher, the + * result of which will be used in the next accumulator call + * @return a Single that emits a single item that is the result of accumulating the output from the + * items emitted by the source Publisher + * @see ReactiveX operators documentation: Reduce + * @see Wikipedia: Fold (higher-order function) + * @see #reduceWith(Callable, BiFunction) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single reduce(R seed, BiFunction reducer) { + ObjectHelper.requireNonNull(seed, "seed is null"); + ObjectHelper.requireNonNull(reducer, "reducer is null"); + return RxJavaPlugins.onAssembly(new FlowableReduceSeedSingle(this, seed, reducer)); + } + + /** + * Returns a Single that applies a specified accumulator function to the first item emitted by a source + * Publisher and a seed value derived from calling a specified seedSupplier, then feeds the result + * of that function along with the second item emitted by a Publisher into the same function, and so on until + * all items have been emitted by the finite source Publisher, emitting the final result from the final call to your + * function as its sole item. + *

+ * + *

+ * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," + * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method + * that does a similar operation on lists. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure of its downstream consumer and consumes the + * upstream source in unbounded mode.
+ *
Scheduler:
+ *
{@code reduceWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the accumulator and output value type + * @param seedSupplier + * the Callable that provides the initial (seed) accumulator value for each individual Subscriber + * @param reducer + * an accumulator function to be invoked on each item emitted by the source Publisher, the + * result of which will be used in the next accumulator call + * @return a Single that emits a single item that is the result of accumulating the output from the + * items emitted by the source Publisher + * @see ReactiveX operators documentation: Reduce + * @see Wikipedia: Fold (higher-order function) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single reduceWith(Callable seedSupplier, BiFunction reducer) { + ObjectHelper.requireNonNull(seedSupplier, "seedSupplier is null"); + ObjectHelper.requireNonNull(reducer, "reducer is null"); + return RxJavaPlugins.onAssembly(new FlowableReduceWithSingle(this, seedSupplier, reducer)); + } + + /** + * Returns a Flowable that repeats the sequence of items emitted by the source Publisher indefinitely. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Flowable that emits the items emitted by the source Publisher repeatedly and in sequence + * @see ReactiveX operators documentation: Repeat + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeat() { + return repeat(Long.MAX_VALUE); + } + + /** + * Returns a Flowable that repeats the sequence of items emitted by the source Publisher at most + * {@code count} times. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param times + * the number of times the source Publisher items are repeated, a count of 0 will yield an empty + * sequence + * @return a Flowable that repeats the sequence of items emitted by the source Publisher at most + * {@code count} times + * @throws IllegalArgumentException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: Repeat + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeat(long times) { + if (times < 0) { + throw new IllegalArgumentException("times >= 0 required but it was " + times); + } + if (times == 0) { + return empty(); + } + return RxJavaPlugins.onAssembly(new FlowableRepeat(this, times)); + } + + /** + * Returns a Flowable that repeats the sequence of items emitted by the source Publisher until + * the provided stop function returns true. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code repeatUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param stop + * a boolean supplier that is called when the current Flowable completes and unless it returns + * false, the current Flowable is resubscribed + * @return the new Flowable instance + * @throws NullPointerException + * if {@code stop} is null + * @see ReactiveX operators documentation: Repeat + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeatUntil(BooleanSupplier stop) { + ObjectHelper.requireNonNull(stop, "stop is null"); + return RxJavaPlugins.onAssembly(new FlowableRepeatUntil(this, stop)); + } + + /** + * Returns a Flowable that emits the same values as the source Publisher with the exception of an + * {@code onComplete}. An {@code onComplete} notification from the source will result in the emission of + * a {@code void} item to the Publisher provided as an argument to the {@code notificationHandler} + * function. If that Publisher calls {@code onComplete} or {@code onError} then {@code repeatWhen} will + * call {@code onComplete} or {@code onError} on the child subscription. Otherwise, this Publisher will + * resubscribe to the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code repeatWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param handler + * receives a Publisher of notifications with which a user can complete or error, aborting the repeat. + * @return the source Publisher modified with repeat logic + * @see ReactiveX operators documentation: Repeat + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeatWhen(final Function, ? extends Publisher> handler) { + ObjectHelper.requireNonNull(handler, "handler is null"); + return RxJavaPlugins.onAssembly(new FlowableRepeatWhen(this, handler)); + } + + /** + * Returns a {@link ConnectableFlowable} that shares a single subscription to the underlying Publisher + * that will replay all of its items and notifications to any future {@link Subscriber}. A Connectable + * Publisher resembles an ordinary Publisher, except that it does not begin emitting items when it is + * subscribed to, but only when its {@code connect} method is called. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link ConnectableFlowable} that upon connection causes the source Publisher to emit its + * items to its {@link Subscriber}s + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final ConnectableFlowable replay() { + return FlowableReplay.createFrom(this); + } + + /** + * Returns a Flowable that emits items that are the results of invoking a specified selector on the items + * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * the selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Publisher + * @return a Flowable that emits items that are the results of invoking the selector on a + * {@link ConnectableFlowable} that shares a single subscription to the source Publisher + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable replay(Function, ? extends Publisher> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return FlowableReplay.multicastSelector(FlowableInternalHelper.replayCallable(this), selector); + } + + /** + * Returns a Flowable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying {@code bufferSize} notifications. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * the selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Publisher + * @param bufferSize + * the buffer size that limits the number of items the connectable Publisher can replay + * @return a Flowable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableFlowable} that shares a single subscription to the source Publisher + * replaying no more than {@code bufferSize} items + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable replay(Function, ? extends Publisher> selector, final int bufferSize) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return FlowableReplay.multicastSelector(FlowableInternalHelper.replayCallable(this, bufferSize), selector); + } + + /** + * Returns a Flowable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying no more than {@code bufferSize} items that were emitted within a specified time window. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Publisher + * @param bufferSize + * the buffer size that limits the number of items the connectable Publisher can replay + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return a Flowable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, and + * replays no more than {@code bufferSize} items that were emitted within the window defined by + * {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable replay(Function, ? extends Publisher> selector, int bufferSize, long time, TimeUnit unit) { + return replay(selector, bufferSize, time, unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying no more than {@code bufferSize} items that were emitted within a specified time window. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Publisher + * @param bufferSize + * the buffer size that limits the number of items the connectable Publisher can replay + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that is the time source for the window + * @return a Flowable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, and + * replays no more than {@code bufferSize} items that were emitted within the window defined by + * {@code time} + * @throws IllegalArgumentException + * if {@code bufferSize} is less than zero + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable replay(Function, ? extends Publisher> selector, final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return FlowableReplay.multicastSelector( + FlowableInternalHelper.replayCallable(this, bufferSize, time, unit, scheduler), selector); + } + + /** + * Returns a Flowable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying a maximum of {@code bufferSize} items. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Publisher + * @param bufferSize + * the buffer size that limits the number of items the connectable Publisher can replay + * @param scheduler + * the Scheduler on which the replay is observed + * @return a Flowable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying no more than {@code bufferSize} notifications + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable replay(final Function, ? extends Publisher> selector, final int bufferSize, final Scheduler scheduler) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return FlowableReplay.multicastSelector(FlowableInternalHelper.replayCallable(this, bufferSize), + FlowableInternalHelper.replayFunction(selector, scheduler) + ); + } + + /** + * Returns a Flowable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying all items that were emitted within a specified time window. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Publisher + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return a Flowable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying all items that were emitted within the window defined by {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable replay(Function, ? extends Publisher> selector, long time, TimeUnit unit) { + return replay(selector, time, unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying all items that were emitted within a specified time window. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Publisher + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler that is the time source for the window + * @return a Flowable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying all items that were emitted within the window defined by {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable replay(Function, ? extends Publisher> selector, final long time, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return FlowableReplay.multicastSelector(FlowableInternalHelper.replayCallable(this, time, unit, scheduler), selector); + } + + /** + * Returns a Flowable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableFlowable} that shares a single subscription to the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param + * the type of items emitted by the resulting Publisher + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Publisher + * @param scheduler + * the Scheduler where the replay is observed + * @return a Flowable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableFlowable} that shares a single subscription to the source Publisher, + * replaying all items + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable replay(final Function, ? extends Publisher> selector, final Scheduler scheduler) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return FlowableReplay.multicastSelector(FlowableInternalHelper.replayCallable(this), + FlowableInternalHelper.replayFunction(selector, scheduler)); + } + + /** + * Returns a {@link ConnectableFlowable} that shares a single subscription to the source Publisher that + * replays at most {@code bufferSize} items emitted by that Publisher. A Connectable Publisher resembles + * an ordinary Publisher, except that it does not begin emitting items when it is subscribed to, but only + * when its {@code connect} method is called. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @return a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays at most {@code bufferSize} items emitted by that Publisher + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final ConnectableFlowable replay(final int bufferSize) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return FlowableReplay.create(this, bufferSize); + } + + /** + * Returns a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays at most {@code bufferSize} items that were emitted during a specified time window. A Connectable + * Publisher resembles an ordinary Publisher, except that it does not begin emitting items when it is + * subscribed to, but only when its {@code connect} method is called. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays at most {@code bufferSize} items that were emitted during the window defined by + * {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final ConnectableFlowable replay(int bufferSize, long time, TimeUnit unit) { + return replay(bufferSize, time, unit, Schedulers.computation()); + } + + /** + * Returns a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * that replays a maximum of {@code bufferSize} items that are emitted within a specified time window. A + * Connectable Publisher resembles an ordinary Publisher, except that it does not begin emitting items + * when it is subscribed to, but only when its {@code connect} method is called. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler that is used as a time source for the window + * @return a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays at most {@code bufferSize} items that were emitted during the window defined by + * {@code time} + * @throws IllegalArgumentException + * if {@code bufferSize} is less than zero + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final ConnectableFlowable replay(final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return FlowableReplay.create(this, time, unit, scheduler, bufferSize); + } + + /** + * Returns a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays at most {@code bufferSize} items emitted by that Publisher. A Connectable Publisher resembles + * an ordinary Publisher, except that it does not begin emitting items when it is subscribed to, but only + * when its {@code connect} method is called. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @param scheduler + * the scheduler on which the Subscribers will observe the emitted items + * @return a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays at most {@code bufferSize} items that were emitted by the Publisher + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final ConnectableFlowable replay(final int bufferSize, final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return FlowableReplay.observeOn(replay(bufferSize), scheduler); + } + + /** + * Returns a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays all items emitted by that Publisher within a specified time window. A Connectable Publisher + * resembles an ordinary Publisher, except that it does not begin emitting items when it is subscribed to, + * but only when its {@code connect} method is called. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays the items that were emitted during the window defined by {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final ConnectableFlowable replay(long time, TimeUnit unit) { + return replay(time, unit, Schedulers.computation()); + } + + /** + * Returns a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays all items emitted by that Publisher within a specified time window. A Connectable Publisher + * resembles an ordinary Publisher, except that it does not begin emitting items when it is subscribed to, + * but only when its {@code connect} method is called. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that is the time source for the window + * @return a {@link ConnectableFlowable} that shares a single subscription to the source Publisher and + * replays the items that were emitted during the window defined by {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final ConnectableFlowable replay(final long time, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return FlowableReplay.create(this, time, unit, scheduler); + } + + /** + * Returns a {@link ConnectableFlowable} that shares a single subscription to the source Publisher that + * will replay all of its items and notifications to any future {@link Subscriber} on the given + * {@link Scheduler}. A Connectable Publisher resembles an ordinary Publisher, except that it does not + * begin emitting items when it is subscribed to, but only when its {@code connect} method is called. + *

+ * + *

+ *
Backpressure:
+ *
This operator supports backpressure. Note that the upstream requests are determined by the child + * Subscriber which requests the largest amount: i.e., two child Subscribers with requests of 10 and 100 will + * request 100 elements from the underlying Publisher sequence.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the Scheduler on which the Subscribers will observe the emitted items + * @return a {@link ConnectableFlowable} that shares a single subscription to the source Publisher that + * will replay all of its items and notifications to any future {@link Subscriber} on the given + * {@link Scheduler} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final ConnectableFlowable replay(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return FlowableReplay.observeOn(replay(), scheduler); + } + + /** + * Returns a Flowable that mirrors the source Publisher, resubscribing to it if it calls {@code onError} + * (infinite retry count). + *

+ * + *

+ * If the source Publisher calls {@link Subscriber#onError}, this method will resubscribe to the source + * Publisher rather than propagating the {@code onError} call. + *

+ * Any and all items emitted by the source Publisher will be emitted by the resulting Publisher, even + * those emitted during failed subscriptions. For example, if a Publisher fails at first but emits + * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence + * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onComplete]}. + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the source Publisher modified with retry logic + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable retry() { + return retry(Long.MAX_VALUE, Functions.alwaysTrue()); + } + + /** + * Returns a Flowable that mirrors the source Publisher, resubscribing to it if it calls {@code onError} + * and the predicate returns true for that specific exception and retry count. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * the predicate that determines if a resubscription may happen in case of a specific exception + * and retry count + * @return the source Publisher modified with retry logic + * @see #retry() + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable retry(BiPredicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + + return RxJavaPlugins.onAssembly(new FlowableRetryBiPredicate(this, predicate)); + } + + /** + * Returns a Flowable that mirrors the source Publisher, resubscribing to it if it calls {@code onError} + * up to a specified number of retries. + *

+ * + *

+ * If the source Publisher calls {@link Subscriber#onError}, this method will resubscribe to the source + * Publisher for a maximum of {@code count} resubscriptions rather than propagating the + * {@code onError} call. + *

+ * Any and all items emitted by the source Publisher will be emitted by the resulting Publisher, even + * those emitted during failed subscriptions. For example, if a Publisher fails at first but emits + * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence + * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onComplete]}. + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the number of times to resubscribe if the current Flowable fails + * @return the source Publisher modified with retry logic + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable retry(long count) { + return retry(count, Functions.alwaysTrue()); + } + + /** + * Retries at most times or until the predicate returns false, whichever happens first. + * + *
+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param times the number of times to resubscribe if the current Flowable fails + * @param predicate the predicate called with the failure Throwable and should return true to trigger a retry. + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable retry(long times, Predicate predicate) { + if (times < 0) { + throw new IllegalArgumentException("times >= 0 required but it was " + times); + } + ObjectHelper.requireNonNull(predicate, "predicate is null"); + + return RxJavaPlugins.onAssembly(new FlowableRetryPredicate(this, times, predicate)); + } + + /** + * Retries the current Flowable if the predicate returns true. + *
+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate the predicate that receives the failure Throwable and should return true to trigger a retry. + * @return the new Flowable instance + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable retry(Predicate predicate) { + return retry(Long.MAX_VALUE, predicate); + } + + /** + * Retries until the given stop function returns true. + *
+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code retryUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param stop the function that should return true to stop retrying + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable retryUntil(final BooleanSupplier stop) { + ObjectHelper.requireNonNull(stop, "stop is null"); + return retry(Long.MAX_VALUE, Functions.predicateReverseFor(stop)); + } + + /** + * Returns a Flowable that emits the same values as the source Publisher with the exception of an + * {@code onError}. An {@code onError} notification from the source will result in the emission of a + * {@link Throwable} item to the Publisher provided as an argument to the {@code notificationHandler} + * function. If that Publisher calls {@code onComplete} or {@code onError} then {@code retry} will call + * {@code onComplete} or {@code onError} on the child subscription. Otherwise, this Publisher will + * resubscribe to the source Publisher. + *

+ * + *

+ * Example: + * + * This retries 3 times, each time incrementing the number of seconds it waits. + * + *


+     *  Flowable.create((FlowableEmitter<? super String> s) -> {
+     *      System.out.println("subscribing");
+     *      s.onError(new RuntimeException("always fails"));
+     *  }, BackpressureStrategy.BUFFER).retryWhen(attempts -> {
+     *      return attempts.zipWith(Flowable.range(1, 3), (n, i) -> i).flatMap(i -> {
+     *          System.out.println("delay retry by " + i + " second(s)");
+     *          return Flowable.timer(i, TimeUnit.SECONDS);
+     *      });
+     *  }).blockingForEach(System.out::println);
+     * 
+ * + * Output is: + * + *
 {@code
+     * subscribing
+     * delay retry by 1 second(s)
+     * subscribing
+     * delay retry by 2 second(s)
+     * subscribing
+     * delay retry by 3 second(s)
+     * subscribing
+     * } 
+ *

+ * Note that the inner {@code Publisher} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signaling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code Publisher} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + *

+ * The following example demonstrates how to retry an asynchronous source with a delay: + *


+     * Flowable.timer(1, TimeUnit.SECONDS)
+     *     .doOnSubscribe(s -> System.out.println("subscribing"))
+     *     .map(v -> { throw new RuntimeException(); })
+     *     .retryWhen(errors -> {
+     *         AtomicInteger counter = new AtomicInteger();
+     *         return errors
+     *                   .takeWhile(e -> counter.getAndIncrement() != 3)
+     *                   .flatMap(e -> {
+     *                       System.out.println("delay retry by " + counter.get() + " second(s)");
+     *                       return Flowable.timer(counter.get(), TimeUnit.SECONDS);
+     *                   });
+     *     })
+     *     .blockingSubscribe(System.out::println, System.out::println);
+     * 
+ *
+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects both the source + * and inner {@code Publisher}s to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code retryWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param handler + * receives a Publisher of notifications with which a user can complete or error, aborting the + * retry + * @return the source Publisher modified with retry logic + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable retryWhen( + final Function, ? extends Publisher> handler) { + ObjectHelper.requireNonNull(handler, "handler is null"); + + return RxJavaPlugins.onAssembly(new FlowableRetryWhen(this, handler)); + } + + /** + * Subscribes to the current Flowable and wraps the given Subscriber into a SafeSubscriber + * (if not already a SafeSubscriber) that + * deals with exceptions thrown by a misbehaving Subscriber (that doesn't follow the + * Reactive Streams specification). + *
+ *
Backpressure:
+ *
This operator leaves the reactive world and the backpressure behavior depends on the Subscriber's behavior.
+ *
Scheduler:
+ *
{@code safeSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param s the incoming Subscriber instance + * @throws NullPointerException if s is null + */ + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final void safeSubscribe(Subscriber s) { + ObjectHelper.requireNonNull(s, "s is null"); + if (s instanceof SafeSubscriber) { + subscribe((SafeSubscriber)s); + } else { + subscribe(new SafeSubscriber(s)); + } + } + + /** + * Returns a Flowable that emits the most recently emitted item (if any) emitted by the source Publisher + * within periodic time intervals. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
{@code sample} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param period + * the sampling rate + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @return a Flowable that emits the results of sampling the items emitted by the source Publisher at + * the specified time interval + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + * @see #throttleLast(long, TimeUnit) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable sample(long period, TimeUnit unit) { + return sample(period, unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits the most recently emitted item (if any) emitted by the source Publisher + * within periodic time intervals and optionally emit the very last upstream item when the upstream completes. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
{@code sample} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + *

History: 2.0.5 - experimental + * @param period + * the sampling rate + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @param emitLast + * if true and the upstream completes while there is still an unsampled item available, + * that item is emitted to downstream before completion + * if false, an unsampled last item is ignored. + * @return a Flowable that emits the results of sampling the items emitted by the source Publisher at + * the specified time interval + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + * @see #throttleLast(long, TimeUnit) + * @since 2.1 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable sample(long period, TimeUnit unit, boolean emitLast) { + return sample(period, unit, Schedulers.computation(), emitLast); + } + + /** + * Returns a Flowable that emits the most recently emitted item (if any) emitted by the source Publisher + * within periodic time intervals, where the intervals are defined on a particular Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param period + * the sampling rate + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @param scheduler + * the {@link Scheduler} to use when sampling + * @return a Flowable that emits the results of sampling the items emitted by the source Publisher at + * the specified time interval + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + * @see #throttleLast(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable sample(long period, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableSampleTimed(this, period, unit, scheduler, false)); + } + + /** + * Returns a Flowable that emits the most recently emitted item (if any) emitted by the source Publisher + * within periodic time intervals, where the intervals are defined on a particular Scheduler + * and optionally emit the very last upstream item when the upstream completes. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + *

History: 2.0.5 - experimental + * @param period + * the sampling rate + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @param scheduler + * the {@link Scheduler} to use when sampling + * @param emitLast + * if true and the upstream completes while there is still an unsampled item available, + * that item is emitted to downstream before completion + * if false, an unsampled last item is ignored. + * @return a Flowable that emits the results of sampling the items emitted by the source Publisher at + * the specified time interval + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + * @see #throttleLast(long, TimeUnit, Scheduler) + * @since 2.1 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable sample(long period, TimeUnit unit, Scheduler scheduler, boolean emitLast) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableSampleTimed(this, period, unit, scheduler, emitLast)); + } + + /** + * Returns a Flowable that, when the specified {@code sampler} Publisher emits an item or completes, + * emits the most recently emitted item (if any) emitted by the source Publisher since the previous + * emission from the {@code sampler} Publisher. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses the emissions of the {@code sampler} + * Publisher to control data flow.
+ *
Scheduler:
+ *
This version of {@code sample} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the sampler Publisher + * @param sampler + * the Publisher to use for sampling the source Publisher + * @return a Flowable that emits the results of sampling the items emitted by this Publisher whenever + * the {@code sampler} Publisher emits an item or completes + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable sample(Publisher sampler) { + ObjectHelper.requireNonNull(sampler, "sampler is null"); + return RxJavaPlugins.onAssembly(new FlowableSamplePublisher(this, sampler, false)); + } + + /** + * Returns a Flowable that, when the specified {@code sampler} Publisher emits an item or completes, + * emits the most recently emitted item (if any) emitted by the source Publisher since the previous + * emission from the {@code sampler} Publisher + * and optionally emit the very last upstream item when the upstream or other Publisher complete. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses the emissions of the {@code sampler} + * Publisher to control data flow.
+ *
Scheduler:
+ *
This version of {@code sample} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + *

History: 2.0.5 - experimental + * @param the element type of the sampler Publisher + * @param sampler + * the Publisher to use for sampling the source Publisher + * @param emitLast + * if true and the upstream completes while there is still an unsampled item available, + * that item is emitted to downstream before completion + * if false, an unsampled last item is ignored. + * @return a Flowable that emits the results of sampling the items emitted by this Publisher whenever + * the {@code sampler} Publisher emits an item or completes + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + * @since 2.1 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable sample(Publisher sampler, boolean emitLast) { + ObjectHelper.requireNonNull(sampler, "sampler is null"); + return RxJavaPlugins.onAssembly(new FlowableSamplePublisher(this, sampler, emitLast)); + } + + /** + * Returns a Flowable that applies a specified accumulator function to the first item emitted by a source + * Publisher, then feeds the result of that function along with the second item emitted by the source + * Publisher into the same function, and so on until all items have been emitted by the source Publisher, + * emitting the result of each of these iterations. + *

+ * + *

+ * This sort of function is sometimes called an accumulator. + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * Violating this expectation, a {@code MissingBackpressureException} may get signaled somewhere downstream.
+ *
Scheduler:
+ *
{@code scan} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param accumulator + * an accumulator function to be invoked on each item emitted by the source Publisher, whose + * result will be emitted to {@link Subscriber}s via {@link Subscriber#onNext onNext} and used in the + * next accumulator call + * @return a Flowable that emits the results of each call to the accumulator function + * @see ReactiveX operators documentation: Scan + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable scan(BiFunction accumulator) { + ObjectHelper.requireNonNull(accumulator, "accumulator is null"); + return RxJavaPlugins.onAssembly(new FlowableScan(this, accumulator)); + } + + /** + * Returns a Flowable that applies a specified accumulator function to the first item emitted by a source + * Publisher and a seed value, then feeds the result of that function along with the second item emitted by + * the source Publisher into the same function, and so on until all items have been emitted by the source + * Publisher, emitting the result of each of these iterations. + *

+ * + *

+ * This sort of function is sometimes called an accumulator. + *

+ * Note that the Publisher that results from this method will emit {@code initialValue} as its first + * emitted item. + *

+ * Note that the {@code initialValue} is shared among all subscribers to the resulting Publisher + * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer + * the application of this operator via {@link #defer(Callable)}: + *


+     * Publisher<T> source = ...
+     * Flowable.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item)));
+     *
+     * // alternatively, by using compose to stay fluent
+     *
+     * source.compose(o ->
+     *     Flowable.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item)))
+     * );
+     * 
+ *
+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * Violating this expectation, a {@code MissingBackpressureException} may get signaled somewhere downstream.
+ *
Scheduler:
+ *
{@code scan} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the initial, accumulator and result type + * @param initialValue + * the initial (seed) accumulator item + * @param accumulator + * an accumulator function to be invoked on each item emitted by the source Publisher, whose + * result will be emitted to {@link Subscriber}s via {@link Subscriber#onNext onNext} and used in the + * next accumulator call + * @return a Flowable that emits {@code initialValue} followed by the results of each call to the + * accumulator function + * @see ReactiveX operators documentation: Scan + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable scan(final R initialValue, BiFunction accumulator) { + ObjectHelper.requireNonNull(initialValue, "initialValue is null"); + return scanWith(Functions.justCallable(initialValue), accumulator); + } + + /** + * Returns a Flowable that applies a specified accumulator function to the first item emitted by a source + * Publisher and a seed value, then feeds the result of that function along with the second item emitted by + * the source Publisher into the same function, and so on until all items have been emitted by the source + * Publisher, emitting the result of each of these iterations. + *

+ * + *

+ * This sort of function is sometimes called an accumulator. + *

+ * Note that the Publisher that results from this method will emit the value returned by + * the {@code seedSupplier} as its first item. + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * Violating this expectation, a {@code MissingBackpressureException} may get signaled somewhere downstream.
+ *
Scheduler:
+ *
{@code scanWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the initial, accumulator and result type + * @param seedSupplier + * a Callable that returns the initial (seed) accumulator item for each individual Subscriber + * @param accumulator + * an accumulator function to be invoked on each item emitted by the source Publisher, whose + * result will be emitted to {@link Subscriber}s via {@link Subscriber#onNext onNext} and used in the + * next accumulator call + * @return a Flowable that emits {@code initialValue} followed by the results of each call to the + * accumulator function + * @see ReactiveX operators documentation: Scan + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable scanWith(Callable seedSupplier, BiFunction accumulator) { + ObjectHelper.requireNonNull(seedSupplier, "seedSupplier is null"); + ObjectHelper.requireNonNull(accumulator, "accumulator is null"); + return RxJavaPlugins.onAssembly(new FlowableScanSeed(this, seedSupplier, accumulator)); + } + + /** + * Forces a Publisher's emissions and notifications to be serialized and for it to obey + * the Publisher contract in other ways. + *

+ * It is possible for a Publisher to invoke its Subscribers' methods asynchronously, perhaps from + * different threads. This could make such a Publisher poorly-behaved, in that it might try to invoke + * {@code onComplete} or {@code onError} before one of its {@code onNext} invocations, or it might call + * {@code onNext} from two different threads concurrently. You can force such a Publisher to be + * well-behaved and sequential by applying the {@code serialize} method to it. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code serialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Publisher} that is guaranteed to be well-behaved and to make only serialized calls to + * its Subscribers + * @see ReactiveX operators documentation: Serialize + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable serialize() { + return RxJavaPlugins.onAssembly(new FlowableSerialized(this)); + } + + /** + * Returns a new {@link Publisher} that multicasts (and shares a single subscription to) the original {@link Publisher}. As long as + * there is at least one {@link Subscriber} this {@link Publisher} will be subscribed and emitting data. + * When all subscribers have canceled it will cancel the source {@link Publisher}. + *

+ * This is an alias for {@link #publish()}.{@link ConnectableFlowable#refCount() refCount()}. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator will signal a {@code MissingBackpressureException} to + * its {@code Subscriber}s.
+ *
Scheduler:
+ *
{@code share} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@code Publisher} that upon connection causes the source {@code Publisher} to emit items + * to its {@link Subscriber}s + * @see ReactiveX operators documentation: RefCount + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable share() { + return publish().refCount(); + } + + /** + * Returns a Maybe that completes if this Flowable is empty, signals one item if this Flowable + * signals exactly one item or signals an {@code IllegalArgumentException} if this Flowable signals + * more than one item. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code singleElement} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Maybe that emits the single item emitted by the source Publisher + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe singleElement() { + return RxJavaPlugins.onAssembly(new FlowableSingleMaybe(this)); + } + + /** + * Returns a Single that emits the single item emitted by the source Publisher, if that Publisher + * emits only a single item, or a default item if the source Publisher emits no items. If the source + * Publisher emits more than one item, an {@code IllegalArgumentException} is signaled instead. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code single} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * a default value to emit if the source Publisher emits no item + * @return a Single that emits the single item emitted by the source Publisher, or a default item if + * the source Publisher is empty + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single single(T defaultItem) { + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); + return RxJavaPlugins.onAssembly(new FlowableSingleSingle(this, defaultItem)); + } + + /** + * Returns a Single that emits the single item emitted by this Flowable, if this Flowable + * emits only a single item, otherwise + * if this Flowable completes without emitting any items a {@link NoSuchElementException} will be signaled and + * if this Flowable emits more than one item, an {@code IllegalArgumentException} will be signaled. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure).
+ *
Scheduler:
+ *
{@code singleOrError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Single instance + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single singleOrError() { + return RxJavaPlugins.onAssembly(new FlowableSingleSingle(this, null)); + } + + /** + * Returns a Flowable that skips the first {@code count} items emitted by the source Publisher and emits + * the remainder. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
This version of {@code skip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the number of items to skip + * @return a Flowable that is identical to the source Publisher except that it does not emit the first + * {@code count} items that the source Publisher emits + * @see ReactiveX operators documentation: Skip + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable skip(long count) { + if (count <= 0L) { + return RxJavaPlugins.onAssembly(this); + } + return RxJavaPlugins.onAssembly(new FlowableSkip(this, count)); + } + + /** + * Returns a Flowable that skips values emitted by the source Publisher before a specified time window + * elapses. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and + * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code skip} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window to skip + * @param unit + * the time unit of {@code time} + * @return a Flowable that skips values emitted by the source Publisher before the time window defined + * by {@code time} elapses and the emits the remainder + * @see ReactiveX operators documentation: Skip + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable skip(long time, TimeUnit unit) { + return skipUntil(timer(time, unit)); + } + + /** + * Returns a Flowable that skips values emitted by the source Publisher before a specified time window + * on a specified {@link Scheduler} elapses. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and + * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use for the timed skipping
+ *
+ * + * @param time + * the length of the time window to skip + * @param unit + * the time unit of {@code time} + * @param scheduler + * the {@link Scheduler} on which the timed wait happens + * @return a Flowable that skips values emitted by the source Publisher before the time window defined + * by {@code time} and {@code scheduler} elapses, and then emits the remainder + * @see ReactiveX operators documentation: Skip + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable skip(long time, TimeUnit unit, Scheduler scheduler) { + return skipUntil(timer(time, unit, scheduler)); + } + + /** + * Returns a Flowable that drops a specified number of items from the end of the sequence emitted by the + * source Publisher. + *

+ * + *

+ * This Subscriber accumulates a queue long enough to store the first {@code count} items. As more items are + * received, items are taken from the front of the queue and emitted by the returned Publisher. This causes + * such items to be delayed. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
This version of {@code skipLast} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * number of items to drop from the end of the source sequence + * @return a Flowable that emits the items emitted by the source Publisher except for the dropped ones + * at the end + * @throws IndexOutOfBoundsException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable skipLast(int count) { + if (count < 0) { + throw new IndexOutOfBoundsException("count >= 0 required but it was " + count); + } + if (count == 0) { + return RxJavaPlugins.onAssembly(this); + } + return RxJavaPlugins.onAssembly(new FlowableSkipLast(this, count)); + } + + /** + * Returns a Flowable that drops items emitted by the source Publisher during a specified time window + * before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Backpressure:
+ *
The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and + * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code skipLast} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return a Flowable that drops those items emitted by the source Publisher in a time window before the + * source completes defined by {@code time} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable skipLast(long time, TimeUnit unit) { + return skipLast(time, unit, Schedulers.computation(), false, bufferSize()); + } + + /** + * Returns a Flowable that drops items emitted by the source Publisher during a specified time window + * before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Backpressure:
+ *
The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and + * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
{@code skipLast} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param delayError + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped + * @return a Flowable that drops those items emitted by the source Publisher in a time window before the + * source completes defined by {@code time} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable skipLast(long time, TimeUnit unit, boolean delayError) { + return skipLast(time, unit, Schedulers.computation(), delayError, bufferSize()); + } + + /** + * Returns a Flowable that drops items emitted by the source Publisher during a specified time window + * (defined on a specified scheduler) before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Backpressure:
+ *
The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and + * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use for tracking the current time
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler used as the time source + * @return a Flowable that drops those items emitted by the source Publisher in a time window before the + * source completes defined by {@code time} and {@code scheduler} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable skipLast(long time, TimeUnit unit, Scheduler scheduler) { + return skipLast(time, unit, scheduler, false, bufferSize()); + } + + /** + * Returns a Flowable that drops items emitted by the source Publisher during a specified time window + * (defined on a specified scheduler) before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Backpressure:
+ *
The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and + * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use to track the current time
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler used as the time source + * @param delayError + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped + * @return a Flowable that drops those items emitted by the source Publisher in a time window before the + * source completes defined by {@code time} and {@code scheduler} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable skipLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError) { + return skipLast(time, unit, scheduler, delayError, bufferSize()); + } + + /** + * Returns a Flowable that drops items emitted by the source Publisher during a specified time window + * (defined on a specified scheduler) before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Backpressure:
+ *
The operator doesn't support backpressure as it uses time to skip an arbitrary number of elements and + * thus has to consume the source {@code Publisher} in an unbounded manner (i.e., no backpressure applied to it).
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler used as the time source + * @param delayError + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped + * @param bufferSize + * the hint about how many elements to expect to be skipped + * @return a Flowable that drops those items emitted by the source Publisher in a time window before the + * source completes defined by {@code time} and {@code scheduler} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable skipLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + // the internal buffer holds pairs of (timestamp, value) so double the default buffer size + int s = bufferSize << 1; + return RxJavaPlugins.onAssembly(new FlowableSkipLastTimed(this, time, unit, scheduler, s, delayError)); + } + + /** + * Returns a Flowable that skips items emitted by the source Publisher until a second Publisher emits + * an item. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code skipUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the other Publisher + * @param other + * the second Publisher that has to emit an item before the source Publisher's elements begin + * to be mirrored by the resulting Publisher + * @return a Flowable that skips items from the source Publisher until the second Publisher emits an + * item, then emits the remaining items + * @see ReactiveX operators documentation: SkipUntil + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable skipUntil(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableSkipUntil(this, other)); + } + + /** + * Returns a Flowable that skips all items emitted by the source Publisher as long as a specified + * condition holds true, but emits all further source items as soon as the condition becomes false. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code skipWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function to test each item emitted from the source Publisher + * @return a Flowable that begins emitting items emitted by the source Publisher when the specified + * predicate becomes false + * @see ReactiveX operators documentation: SkipWhile + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable skipWhile(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new FlowableSkipWhile(this, predicate)); + } + /** + * Returns a Flowable that emits the events emitted by source Publisher, in a + * sorted order. Each item emitted by the Publisher must implement {@link Comparable} with respect to all + * other items in the sequence. + * + *

If any item emitted by this Flowable does not implement {@link Comparable} with respect to + * all other items emitted by this Flowable, no items will be emitted and the + * sequence is terminated with a {@link ClassCastException}. + *

Note that calling {@code sorted} with long, non-terminating or infinite sources + * might cause {@link OutOfMemoryError} + * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code sorted} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Flowable that emits the items emitted by the source Publisher in sorted order + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable sorted() { + return toList().toFlowable().map(Functions.listSorter(Functions.naturalComparator())).flatMapIterable(Functions.>identity()); + } + + /** + * Returns a Flowable that emits the events emitted by source Publisher, in a + * sorted order based on a specified comparison function. + * + *

Note that calling {@code sorted} with long, non-terminating or infinite sources + * might cause {@link OutOfMemoryError} + * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code sorted} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param sortFunction + * a function that compares two items emitted by the source Publisher and returns an Integer + * that indicates their sort order + * @return a Flowable that emits the items emitted by the source Publisher in sorted order + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable sorted(Comparator sortFunction) { + ObjectHelper.requireNonNull(sortFunction, "sortFunction"); + return toList().toFlowable().map(Functions.listSorter(sortFunction)).flatMapIterable(Functions.>identity()); + } + + /** + * Returns a Flowable that emits the items in a specified {@link Iterable} before it begins to emit items + * emitted by the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code startWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param items + * an Iterable that contains the items you want the modified Publisher to emit first + * @return a Flowable that emits the items in the specified {@link Iterable} and then emits the items + * emitted by the source Publisher + * @see ReactiveX operators documentation: StartWith + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable startWith(Iterable items) { + return concatArray(fromIterable(items), this); + } + + /** + * Returns a Flowable that emits the items in a specified {@link Publisher} before it begins to emit + * items emitted by the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both this and the {@code other} {@code Publisher}s + * are expected to honor backpressure as well. If any of then violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code startWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * a Publisher that contains the items you want the modified Publisher to emit first + * @return a Flowable that emits the items in the specified {@link Publisher} and then emits the items + * emitted by the source Publisher + * @see ReactiveX operators documentation: StartWith + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable startWith(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return concatArray(other, this); + } + + /** + * Returns a Flowable that emits a specified item before it begins to emit items emitted by the source + * Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code startWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param value + * the item to emit first + * @return a Flowable that emits the specified item before it begins to emit items emitted by the source + * Publisher + * @see ReactiveX operators documentation: StartWith + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable startWith(T value) { + ObjectHelper.requireNonNull(value, "value is null"); + return concatArray(just(value), this); + } + + /** + * Returns a Flowable that emits the specified items before it begins to emit items emitted by the source + * Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The source {@code Publisher} + * is expected to honor backpressure as well. If it violates this rule, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code startWithArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param items + * the array of values to emit first + * @return a Flowable that emits the specified items before it begins to emit items emitted by the source + * Publisher + * @see ReactiveX operators documentation: StartWith + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable startWithArray(T... items) { + Flowable fromArray = fromArray(items); + if (fromArray == empty()) { + return RxJavaPlugins.onAssembly(this); + } + return concatArray(fromArray, this); + } + + /** + * Subscribes to a Publisher and ignores {@code onNext} and {@code onComplete} emissions. + *

+ * If the Flowable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no + * backpressure is applied to it).
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the Publisher has finished sending them + * @see ReactiveX operators documentation: Subscribe + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe() { + return subscribe(Functions.emptyConsumer(), Functions.ON_ERROR_MISSING, + Functions.EMPTY_ACTION, FlowableInternalHelper.RequestMax.INSTANCE); + } + + /** + * Subscribes to a Publisher and provides a callback to handle the items it emits. + *

+ * If the Flowable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no + * backpressure is applied to it).
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the {@code Consumer} you have designed to accept emissions from the Publisher + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the Publisher has finished sending them + * @throws NullPointerException + * if {@code onNext} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onNext) { + return subscribe(onNext, Functions.ON_ERROR_MISSING, + Functions.EMPTY_ACTION, FlowableInternalHelper.RequestMax.INSTANCE); + } + + /** + * Subscribes to a Publisher and provides callbacks to handle the items it emits and any error + * notification it issues. + *
+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no + * backpressure is applied to it).
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the {@code Consumer} you have designed to accept emissions from the Publisher + * @param onError + * the {@code Consumer} you have designed to accept any error notification from the + * Publisher + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the Publisher has finished sending them + * @see ReactiveX operators documentation: Subscribe + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onNext, Consumer onError) { + return subscribe(onNext, onError, Functions.EMPTY_ACTION, FlowableInternalHelper.RequestMax.INSTANCE); + } + + /** + * Subscribes to a Publisher and provides callbacks to handle the items it emits and any error or + * completion notification it issues. + *
+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no + * backpressure is applied to it).
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the {@code Consumer} you have designed to accept emissions from the Publisher + * @param onError + * the {@code Consumer} you have designed to accept any error notification from the + * Publisher + * @param onComplete + * the {@code Action} you have designed to accept a completion notification from the + * Publisher + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the Publisher has finished sending them + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null, or + * if {@code onComplete} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onNext, Consumer onError, + Action onComplete) { + return subscribe(onNext, onError, onComplete, FlowableInternalHelper.RequestMax.INSTANCE); + } + + /** + * Subscribes to a Publisher and provides callbacks to handle the items it emits and any error or + * completion notification it issues. + *
+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no + * backpressure is applied to it).
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the {@code Consumer} you have designed to accept emissions from the Publisher + * @param onError + * the {@code Consumer} you have designed to accept any error notification from the + * Publisher + * @param onComplete + * the {@code Action} you have designed to accept a completion notification from the + * Publisher + * @param onSubscribe + * the {@code Consumer} that receives the upstream's Subscription + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the Publisher has finished sending them + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null, or + * if {@code onComplete} is null, or + * if {@code onSubscribe} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onNext, Consumer onError, + Action onComplete, Consumer onSubscribe) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + + LambdaSubscriber ls = new LambdaSubscriber(onNext, onError, onComplete, onSubscribe); + + subscribe(ls); + + return ls; + } + + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + @Override + public final void subscribe(Subscriber s) { + if (s instanceof FlowableSubscriber) { + subscribe((FlowableSubscriber)s); + } else { + ObjectHelper.requireNonNull(s, "s is null"); + subscribe(new StrictSubscriber(s)); + } + } + + /** + * Establish a connection between this Flowable and the given FlowableSubscriber and + * start streaming events based on the demand of the FlowableSubscriber. + *

+ * This is a "factory method" and can be called multiple times, each time starting a new {@link Subscription}. + *

+ * Each {@link Subscription} will work for only a single {@link FlowableSubscriber}. + *

+ * If the same {@link FlowableSubscriber} instance is subscribed to multiple {@link Flowable}s and/or the + * same {@link Flowable} multiple times, it must ensure the serialization over its {@code onXXX} + * methods manually. + *

+ * If the {@link Flowable} rejects the subscription attempt or otherwise fails it will signal + * the error via {@link FlowableSubscriber#onError(Throwable)}. + *

+ * This subscribe method relaxes the following Reactive Streams rules: + *

    + *
  • §1.3: onNext should not be called concurrently until onSubscribe returns. + * FlowableSubscriber.onSubscribe should make sure a sync or async call triggered by request() is safe.
  • + *
  • §2.3: onError or onComplete must not call cancel. + * Calling request() or cancel() is NOP at this point.
  • + *
  • §2.12: onSubscribe must be called at most once on the same instance. + * FlowableSubscriber reuse is not checked and if happens, it is the responsibility of + * the FlowableSubscriber to ensure proper serialization of its onXXX methods.
  • + *
  • §3.9: negative requests should emit an onError(IllegalArgumentException). + * Non-positive requests signal via RxJavaPlugins.onError and the stream is not affected.
  • + *
+ *
+ *
Backpressure:
+ *
The backpressure behavior/expectation is determined by the supplied {@code FlowableSubscriber}.
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.7 - experimental; 2.1 - beta + * @param s the FlowableSubscriber that will consume signals from this Flowable + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final void subscribe(FlowableSubscriber s) { + ObjectHelper.requireNonNull(s, "s is null"); + try { + Subscriber z = RxJavaPlugins.onSubscribe(this, s); + + ObjectHelper.requireNonNull(z, "The RxJavaPlugins.onSubscribe hook returned a null FlowableSubscriber. Please check the handler provided to RxJavaPlugins.setOnFlowableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); + + subscribeActual(z); + } catch (NullPointerException e) { // NOPMD + throw e; + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because no way to know if a Subscription has been set or not + // can't call onSubscribe because the call might have set a Subscription already + RxJavaPlugins.onError(e); + + NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS"); + npe.initCause(e); + throw npe; + } + } + + /** + * Operator implementations (both source and intermediate) should implement this method that + * performs the necessary business logic and handles the incoming {@link Subscriber}s. + *

There is no need to call any of the plugin hooks on the current {@code Flowable} instance or + * the {@code Subscriber}; all hooks and basic safeguards have been + * applied by {@link #subscribe(Subscriber)} before this method gets called. + * @param s the incoming Subscriber, never null + */ + protected abstract void subscribeActual(Subscriber s); + + /** + * Subscribes a given Subscriber (subclass) to this Flowable and returns the given + * Subscriber as is. + *

Usage example: + *


+     * Flowable<Integer> source = Flowable.range(1, 10);
+     * CompositeDisposable composite = new CompositeDisposable();
+     *
+     * ResourceSubscriber<Integer> rs = new ResourceSubscriber<>() {
+     *     // ...
+     * };
+     *
+     * composite.add(source.subscribeWith(rs));
+     * 
+ * + *
+ *
Backpressure:
+ *
The backpressure behavior/expectation is determined by the supplied {@code Subscriber}.
+ *
Scheduler:
+ *
{@code subscribeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the type of the Subscriber to use and return + * @param subscriber the Subscriber (subclass) to use and return, not null + * @return the input {@code subscriber} + * @throws NullPointerException if {@code subscriber} is null + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final > E subscribeWith(E subscriber) { + subscribe(subscriber); + return subscriber; + } + + /** + * Asynchronously subscribes Subscribers to this Publisher on the specified {@link Scheduler}. + *

+ * If there is a {@link #create(FlowableOnSubscribe, BackpressureStrategy)} type source up in the + * chain, it is recommended to use {@code subscribeOn(scheduler, false)} instead + * to avoid same-pool deadlock because requests may pile up behind an eager/blocking emitter. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to perform subscription actions on + * @return the source Publisher modified so that its subscriptions happen on the + * specified {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + * @see RxJava Threading Examples + * @see #observeOn + * @see #subscribeOn(Scheduler, boolean) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable subscribeOn(@NonNull Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return subscribeOn(scheduler, !(this instanceof FlowableCreate)); + } + + /** + * Asynchronously subscribes Subscribers to this Publisher on the specified {@link Scheduler} + * optionally reroutes requests from other threads to the same {@link Scheduler} thread. + *

+ * If there is a {@link #create(FlowableOnSubscribe, BackpressureStrategy)} type source up in the + * chain, it is recommended to have {@code requestOn} false to avoid same-pool deadlock + * because requests may pile up behind an eager/blocking emitter. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ *

History: 2.1.1 - experimental + * @param scheduler + * the {@link Scheduler} to perform subscription actions on + * @param requestOn if true, requests are rerouted to the given Scheduler as well (strong pipelining) + * if false, requests coming from any thread are simply forwarded to + * the upstream on the same thread (weak pipelining) + * @return the source Publisher modified so that its subscriptions happen on the + * specified {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + * @see RxJava Threading Examples + * @see #observeOn + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable subscribeOn(@NonNull Scheduler scheduler, boolean requestOn) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableSubscribeOn(this, scheduler, requestOn)); + } + + /** + * Returns a Flowable that emits the items emitted by the source Publisher or the items of an alternate + * Publisher if the source Publisher is empty. + *

+ * + *

+ *
Backpressure:
+ *
If the source {@code Publisher} is empty, the alternate {@code Publisher} is expected to honor backpressure. + * If the source {@code Publisher} is non-empty, it is expected to honor backpressure as instead. + * In either case, if violated, a {@code MissingBackpressureException} may get + * signaled somewhere downstream. + *
+ *
Scheduler:
+ *
{@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the alternate Publisher to subscribe to if the source does not emit any items + * @return a Publisher that emits the items emitted by the source Publisher or the items of an + * alternate Publisher if the source Publisher is empty. + * @since 1.1.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable switchIfEmpty(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchIfEmpty(this, other)); + } + + /** + * Returns a new Publisher by applying a function that you supply to each item emitted by the source + * Publisher that returns a Publisher, and then emitting the items emitted by the most recently emitted + * of these Publishers. + *

+ * The resulting Publisher completes if both the upstream Publisher and the last inner Publisher, if any, complete. + * If the upstream Publisher signals an onError, the inner Publisher is canceled and the error delivered in-sequence. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Publisher}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code switchMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the inner Publishers and the output + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @return a Flowable that emits the items emitted by the Publisher returned from applying {@code func} to the most recently emitted item emitted by the source Publisher + * @see ReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable switchMap(Function> mapper) { + return switchMap(mapper, bufferSize()); + } + + /** + * Returns a new Publisher by applying a function that you supply to each item emitted by the source + * Publisher that returns a Publisher, and then emitting the items emitted by the most recently emitted + * of these Publishers. + *

+ * The resulting Publisher completes if both the upstream Publisher and the last inner Publisher, if any, complete. + * If the upstream Publisher signals an onError, the inner Publisher is canceled and the error delivered in-sequence. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Publisher}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code switchMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the inner Publishers and the output + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @param bufferSize + * the number of elements to prefetch from the current active inner Publisher + * @return a Flowable that emits the items emitted by the Publisher returned from applying {@code func} to the most recently emitted item emitted by the source Publisher + * @see ReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function, int) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable switchMap(Function> mapper, int bufferSize) { + return switchMap0(mapper, bufferSize, false); + } + + /** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running. + *

+ * + *

+ * Since a {@code CompletableSource} doesn't produce any items, the resulting reactive type of + * this operator is a {@link Completable} that can only indicate successful completion or + * a failure in any of the inner {@code CompletableSource}s or the failure of the current + * {@link Flowable}. + *

+ *
Backpressure:
+ *
The operator consumes the current {@link Flowable} in an unbounded manner and otherwise + * does not have backpressure in its return type because no items are ever produced.
+ *
Scheduler:
+ *
{@code switchMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If either this {@code Flowable} or the active {@code CompletableSource} signals an {@code onError}, + * the resulting {@code Completable} is terminated immediately with that {@code Throwable}. + * Use the {@link #switchMapCompletableDelayError(Function)} to delay such inner failures until + * every inner {@code CompletableSource}s and the main {@code Flowable} terminates in some fashion. + * If they fail concurrently, the operator may combine the {@code Throwable}s into a + * {@link io.reactivex.exceptions.CompositeException CompositeException} + * and signal it to the downstream instead. If any inactivated (switched out) {@code CompletableSource} + * signals an {@code onError} late, the {@code Throwable}s will be signaled to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. + *
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with each upstream item and should return a + * {@link CompletableSource} to be subscribed to and awaited for + * (non blockingly) for its terminal event + * @return the new Completable instance + * @see #switchMapCompletableDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable switchMapCompletable(@NonNull Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapCompletable(this, mapper, false)); + } + + /** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running and delaying any main or inner errors until all + * of them terminate. + *

+ * + *

+ * Since a {@code CompletableSource} doesn't produce any items, the resulting reactive type of + * this operator is a {@link Completable} that can only indicate successful completion or + * a failure in any of the inner {@code CompletableSource}s or the failure of the current + * {@link Flowable}. + *

+ *
Backpressure:
+ *
The operator consumes the current {@link Flowable} in an unbounded manner and otherwise + * does not have backpressure in its return type because no items are ever produced.
+ *
Scheduler:
+ *
{@code switchMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
Errors of this {@code Flowable} and all the {@code CompletableSource}s, who had the chance + * to run to their completion, are delayed until + * all of them terminate in some fashion. At this point, if there was only one failure, the respective + * {@code Throwable} is emitted to the downstream. If there was more than one failure, the + * operator combines all {@code Throwable}s into a {@link io.reactivex.exceptions.CompositeException CompositeException} + * and signals that to the downstream. + * If any inactivated (switched out) {@code CompletableSource} + * signals an {@code onError} late, the {@code Throwable}s will be signaled to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. + *
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with each upstream item and should return a + * {@link CompletableSource} to be subscribed to and awaited for + * (non blockingly) for its terminal event + * @return the new Completable instance + * @see #switchMapCompletable(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable switchMapCompletableDelayError(@NonNull Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapCompletable(this, mapper, true)); + } + + /** + * Returns a new Publisher by applying a function that you supply to each item emitted by the source + * Publisher that returns a Publisher, and then emitting the items emitted by the most recently emitted + * of these Publishers and delays any error until all Publishers terminate. + *

+ * The resulting Publisher completes if both the upstream Publisher and the last inner Publisher, if any, complete. + * If the upstream Publisher signals an onError, the termination of the last inner Publisher will emit that error as is + * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signaled. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Publisher}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code switchMapDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the inner Publishers and the output + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @return a Flowable that emits the items emitted by the Publisher returned from applying {@code func} to the most recently emitted item emitted by the source Publisher + * @see ReactiveX operators documentation: FlatMap + * @see #switchMap(Function) + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable switchMapDelayError(Function> mapper) { + return switchMapDelayError(mapper, bufferSize()); + } + + /** + * Returns a new Publisher by applying a function that you supply to each item emitted by the source + * Publisher that returns a Publisher, and then emitting the items emitted by the most recently emitted + * of these Publishers and delays any error until all Publishers terminate. + *

+ * The resulting Publisher completes if both the upstream Publisher and the last inner Publisher, if any, complete. + * If the upstream Publisher signals an onError, the termination of the last inner Publisher will emit that error as is + * or wrapped into a CompositeException along with the other possible errors the former inner Publishers signaled. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed in an + * unbounded manner (i.e., without backpressure) and the inner {@code Publisher}s are expected to honor + * backpressure but it is not enforced; the operator won't signal a {@code MissingBackpressureException} + * but the violation may lead to {@code OutOfMemoryError} due to internal buffer bloat.
+ *
Scheduler:
+ *
{@code switchMapDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the inner Publishers and the output + * @param mapper + * a function that, when applied to an item emitted by the source Publisher, returns a + * Publisher + * @param bufferSize + * the number of elements to prefetch from the current active inner Publisher + * @return a Flowable that emits the items emitted by the Publisher returned from applying {@code func} to the most recently emitted item emitted by the source Publisher + * @see ReactiveX operators documentation: FlatMap + * @see #switchMap(Function, int) + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable switchMapDelayError(Function> mapper, int bufferSize) { + return switchMap0(mapper, bufferSize, true); + } + + Flowable switchMap0(Function> mapper, int bufferSize, boolean delayError) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + if (this instanceof ScalarCallable) { + @SuppressWarnings("unchecked") + T v = ((ScalarCallable)this).call(); + if (v == null) { + return empty(); + } + return FlowableScalarXMap.scalarXMap(v, mapper); + } + return RxJavaPlugins.onAssembly(new FlowableSwitchMap(this, mapper, bufferSize, delayError)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one if + * available while failing immediately if this {@code Flowable} or any of the + * active inner {@code MaybeSource}s fail. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The main {@code Flowable} is consumed in an + * unbounded manner (i.e., without backpressure).
+ *
Scheduler:
+ *
{@code switchMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
This operator terminates with an {@code onError} if this {@code Flowable} or any of + * the inner {@code MaybeSource}s fail while they are active. When this happens concurrently, their + * individual {@code Throwable} errors may get combined and emitted as a single + * {@link io.reactivex.exceptions.CompositeException CompositeException}. Otherwise, a late + * (i.e., inactive or switched out) {@code onError} from this {@code Flowable} or from any of + * the inner {@code MaybeSource}s will be forwarded to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as + * {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}
+ *
+ *

History: 2.1.11 - experimental + * @param the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code MaybeSource} to replace the current active inner source + * and get subscribed to. + * @return the new Flowable instance + * @see #switchMapMaybe(Function) + * @see #switchMapMaybeDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable switchMapMaybe(@NonNull Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapMaybe(this, mapper, false)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one if + * available, delaying errors from this {@code Flowable} or the inner {@code MaybeSource}s until all terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The main {@code Flowable} is consumed in an + * unbounded manner (i.e., without backpressure).
+ *
Scheduler:
+ *
{@code switchMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code MaybeSource} to replace the current active inner source + * and get subscribed to. + * @return the new Flowable instance + * @see #switchMapMaybe(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable switchMapMaybeDelayError(@NonNull Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapMaybe(this, mapper, true)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one + * while failing immediately if this {@code Flowable} or any of the + * active inner {@code SingleSource}s fail. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The main {@code Flowable} is consumed in an + * unbounded manner (i.e., without backpressure).
+ *
Scheduler:
+ *
{@code switchMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
This operator terminates with an {@code onError} if this {@code Flowable} or any of + * the inner {@code SingleSource}s fail while they are active. When this happens concurrently, their + * individual {@code Throwable} errors may get combined and emitted as a single + * {@link io.reactivex.exceptions.CompositeException CompositeException}. Otherwise, a late + * (i.e., inactive or switched out) {@code onError} from this {@code Flowable} or from any of + * the inner {@code SingleSource}s will be forwarded to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as + * {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}
+ *
+ *

History: 2.1.11 - experimental + * @param the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code SingleSource} to replace the current active inner source + * and get subscribed to. + * @return the new Flowable instance + * @see #switchMapSingleDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable switchMapSingle(@NonNull Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapSingle(this, mapper, false)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one, + * delaying errors from this {@code Flowable} or the inner {@code SingleSource}s until all terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The main {@code Flowable} is consumed in an + * unbounded manner (i.e., without backpressure).
+ *
Scheduler:
+ *
{@code switchMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code SingleSource} to replace the current active inner source + * and get subscribed to. + * @return the new Flowable instance + * @see #switchMapSingle(Function) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable switchMapSingleDelayError(@NonNull Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new FlowableSwitchMapSingle(this, mapper, true)); + } + + /** + * Returns a Flowable that emits only the first {@code count} items emitted by the source Publisher. If the source emits fewer than + * {@code count} items then all of its items are emitted. + *

+ * + *

+ * This method returns a Publisher that will invoke a subscribing {@link Subscriber}'s + * {@link Subscriber#onNext onNext} function a maximum of {@code count} times before invoking + * {@link Subscriber#onComplete onComplete}. + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior in case the first request is smaller than the {@code count}. Otherwise, the source {@code Publisher} + * is consumed in an unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
This version of {@code take} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum number of items to emit + * @return a Flowable that emits only the first {@code count} items emitted by the source Publisher, or + * all of the items from the source Publisher if that Publisher emits fewer than {@code count} items + * @see ReactiveX operators documentation: Take + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) // may trigger UNBOUNDED_IN + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable take(long count) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } + return RxJavaPlugins.onAssembly(new FlowableTake(this, count)); + } + + /** + * Returns a Flowable that emits those items emitted by source Publisher before a specified time runs + * out. + *

+ * If time runs out before the {@code Flowable} completes normally, the {@code onComplete} event will be + * signaled on the default {@code computation} {@link Scheduler}. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
This version of {@code take} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return a Flowable that emits those items emitted by the source Publisher before the time runs out + * @see ReactiveX operators documentation: Take + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable take(long time, TimeUnit unit) { + return takeUntil(timer(time, unit)); + } + + /** + * Returns a Flowable that emits those items emitted by source Publisher before a specified time (on a + * specified Scheduler) runs out. + *

+ * If time runs out before the {@code Flowable} completes normally, the {@code onComplete} event will be + * signaled on the provided {@link Scheduler}. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler used for time source + * @return a Flowable that emits those items emitted by the source Publisher before the time runs out, + * according to the specified Scheduler + * @see ReactiveX operators documentation: Take + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable take(long time, TimeUnit unit, Scheduler scheduler) { + return takeUntil(timer(time, unit, scheduler)); + } + + /** + * Returns a Flowable that emits at most the last {@code count} items emitted by the source Publisher. If the source emits fewer than + * {@code count} items then all of its items are emitted. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream if the {@code count} is non-zero; ignores + * backpressure if the {@code count} is zero as it doesn't signal any values.
+ *
Scheduler:
+ *
This version of {@code takeLast} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum number of items to emit from the end of the sequence of items emitted by the source + * Publisher + * @return a Flowable that emits at most the last {@code count} items emitted by the source Publisher + * @throws IndexOutOfBoundsException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable takeLast(int count) { + if (count < 0) { + throw new IndexOutOfBoundsException("count >= 0 required but it was " + count); + } else + if (count == 0) { + return RxJavaPlugins.onAssembly(new FlowableIgnoreElements(this)); + } else + if (count == 1) { + return RxJavaPlugins.onAssembly(new FlowableTakeLastOne(this)); + } + return RxJavaPlugins.onAssembly(new FlowableTakeLast(this, count)); + } + + /** + * Returns a Flowable that emits at most a specified number of items from the source Publisher that were + * emitted in a specified window of time before the Publisher completed. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., no backpressure is applied to it).
+ *
Scheduler:
+ *
{@code takeLast} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return a Flowable that emits at most {@code count} items from the source Publisher that were emitted + * in a specified window of time before the Publisher completed + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable takeLast(long count, long time, TimeUnit unit) { + return takeLast(count, time, unit, Schedulers.computation(), false, bufferSize()); + } + + /** + * Returns a Flowable that emits at most a specified number of items from the source Publisher that were + * emitted in a specified window of time before the Publisher completed, where the timing information is + * provided by a given Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., no backpressure is applied to it).
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use for tracking the current time
+ *
+ * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the {@link Scheduler} that provides the timestamps for the observed items + * @return a Flowable that emits at most {@code count} items from the source Publisher that were emitted + * in a specified window of time before the Publisher completed, where the timing information is + * provided by the given {@code scheduler} + * @throws IndexOutOfBoundsException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable takeLast(long count, long time, TimeUnit unit, Scheduler scheduler) { + return takeLast(count, time, unit, scheduler, false, bufferSize()); + } + + /** + * Returns a Flowable that emits at most a specified number of items from the source Publisher that were + * emitted in a specified window of time before the Publisher completed, where the timing information is + * provided by a given Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., no backpressure is applied to it).
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use for tracking the current time
+ *
+ * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the {@link Scheduler} that provides the timestamps for the observed items + * @param delayError + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped + * @param bufferSize + * the hint about how many elements to expect to be last + * @return a Flowable that emits at most {@code count} items from the source Publisher that were emitted + * in a specified window of time before the Publisher completed, where the timing information is + * provided by the given {@code scheduler} + * @throws IndexOutOfBoundsException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable takeLast(long count, long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + if (count < 0) { + throw new IndexOutOfBoundsException("count >= 0 required but it was " + count); + } + return RxJavaPlugins.onAssembly(new FlowableTakeLastTimed(this, count, time, unit, scheduler, bufferSize, delayError)); + } + + /** + * Returns a Flowable that emits the items from the source Publisher that were emitted in a specified + * window of time before the Publisher completed. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., no backpressure is applied to it) but note that this may + * lead to {@code OutOfMemoryError} due to internal buffer bloat. + * Consider using {@link #takeLast(long, long, TimeUnit)} in this case.
+ *
Scheduler:
+ *
This version of {@code takeLast} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return a Flowable that emits the items from the source Publisher that were emitted in the window of + * time before the Publisher completed specified by {@code time} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable takeLast(long time, TimeUnit unit) { + return takeLast(time, unit, Schedulers.computation(), false, bufferSize()); + } + + /** + * Returns a Flowable that emits the items from the source Publisher that were emitted in a specified + * window of time before the Publisher completed. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., no backpressure is applied to it) but note that this may + * lead to {@code OutOfMemoryError} due to internal buffer bloat. + * Consider using {@link #takeLast(long, long, TimeUnit)} in this case.
+ *
Scheduler:
+ *
This version of {@code takeLast} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param delayError + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped + * @return a Flowable that emits the items from the source Publisher that were emitted in the window of + * time before the Publisher completed specified by {@code time} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable takeLast(long time, TimeUnit unit, boolean delayError) { + return takeLast(time, unit, Schedulers.computation(), delayError, bufferSize()); + } + + /** + * Returns a Flowable that emits the items from the source Publisher that were emitted in a specified + * window of time before the Publisher completed, where the timing information is provided by a specified + * Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., no backpressure is applied to it) but note that this may + * lead to {@code OutOfMemoryError} due to internal buffer bloat. + * Consider using {@link #takeLast(long, long, TimeUnit, Scheduler)} in this case.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the Observed items + * @return a Flowable that emits the items from the source Publisher that were emitted in the window of + * time before the Publisher completed specified by {@code time}, where the timing information is + * provided by {@code scheduler} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable takeLast(long time, TimeUnit unit, Scheduler scheduler) { + return takeLast(time, unit, scheduler, false, bufferSize()); + } + + /** + * Returns a Flowable that emits the items from the source Publisher that were emitted in a specified + * window of time before the Publisher completed, where the timing information is provided by a specified + * Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., no backpressure is applied to it) but note that this may + * lead to {@code OutOfMemoryError} due to internal buffer bloat. + * Consider using {@link #takeLast(long, long, TimeUnit, Scheduler)} in this case.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the Observed items + * @param delayError + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped + * @return a Flowable that emits the items from the source Publisher that were emitted in the window of + * time before the Publisher completed specified by {@code time}, where the timing information is + * provided by {@code scheduler} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable takeLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError) { + return takeLast(time, unit, scheduler, delayError, bufferSize()); + } + + /** + * Returns a Flowable that emits the items from the source Publisher that were emitted in a specified + * window of time before the Publisher completed, where the timing information is provided by a specified + * Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., no backpressure is applied to it) but note that this may + * lead to {@code OutOfMemoryError} due to internal buffer bloat. + * Consider using {@link #takeLast(long, long, TimeUnit, Scheduler)} in this case.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the Observed items + * @param delayError + * if true, an exception signaled by the current Flowable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signaled and all regular elements dropped + * @param bufferSize + * the hint about how many elements to expect to be last + * @return a Flowable that emits the items from the source Publisher that were emitted in the window of + * time before the Publisher completed specified by {@code time}, where the timing information is + * provided by {@code scheduler} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable takeLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { + return takeLast(Long.MAX_VALUE, time, unit, scheduler, delayError, bufferSize); + } + + /** + * Returns a Flowable that emits items emitted by the source Publisher, checks the specified predicate + * for each item, and then completes when the condition is satisfied. + *

+ * + *

+ * The difference between this operator and {@link #takeWhile(Predicate)} is that here, the condition is + * evaluated after the item is emitted. + * + *

+ *
Backpressure:
+ *
The operator is a pass-through for backpressure; the backpressure behavior is determined by the upstream + * source and the downstream consumer.
+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param stopPredicate + * a function that evaluates an item emitted by the source Publisher and returns a Boolean + * @return a Flowable that first emits items emitted by the source Publisher, checks the specified + * condition after each item, and then completes when the condition is satisfied. + * @see ReactiveX operators documentation: TakeUntil + * @see Flowable#takeWhile(Predicate) + * @since 1.1.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable takeUntil(Predicate stopPredicate) { + ObjectHelper.requireNonNull(stopPredicate, "stopPredicate is null"); + return RxJavaPlugins.onAssembly(new FlowableTakeUntilPredicate(this, stopPredicate)); + } + + /** + * Returns a Flowable that emits the items emitted by the source Publisher until a second Publisher + * emits an item. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the Publisher whose first emitted item will cause {@code takeUntil} to stop emitting items + * from the source Publisher + * @param + * the type of items emitted by {@code other} + * @return a Flowable that emits the items emitted by the source Publisher until such time as {@code other} emits its first item + * @see ReactiveX operators documentation: TakeUntil + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable takeUntil(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new FlowableTakeUntil(this, other)); + } + + /** + * Returns a Flowable that emits items emitted by the source Publisher so long as each item satisfied a + * specified condition, and then completes as soon as this condition is not satisfied. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code takeWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function that evaluates an item emitted by the source Publisher and returns a Boolean + * @return a Flowable that emits the items from the source Publisher so long as each item satisfies the + * condition defined by {@code predicate}, then completes + * @see ReactiveX operators documentation: TakeWhile + * @see Flowable#takeUntil(Predicate) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable takeWhile(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new FlowableTakeWhile(this, predicate)); + } + + /** + * Returns a Flowable that emits only the first item emitted by the source Publisher during sequential + * time windows of a specified duration. + *

+ * This differs from {@link #throttleLast} in that this only tracks the passage of time whereas + * {@link #throttleLast} ticks at scheduled intervals. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
{@code throttleFirst} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param windowDuration + * time to wait before emitting another item after emitting the last item + * @param unit + * the unit of time of {@code windowDuration} + * @return a Flowable that performs the throttle operation + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable throttleFirst(long windowDuration, TimeUnit unit) { + return throttleFirst(windowDuration, unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits only the first item emitted by the source Publisher during sequential + * time windows of a specified duration, where the windows are managed by a specified Scheduler. + *

+ * This differs from {@link #throttleLast} in that this only tracks the passage of time whereas + * {@link #throttleLast} ticks at scheduled intervals. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param skipDuration + * time to wait before emitting another item after emitting the last item + * @param unit + * the unit of time of {@code skipDuration} + * @param scheduler + * the {@link Scheduler} to use internally to manage the timers that handle timeout for each + * event + * @return a Flowable that performs the throttle operation + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable throttleFirst(long skipDuration, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableThrottleFirstTimed(this, skipDuration, unit, scheduler)); + } + + /** + * Returns a Flowable that emits only the last item emitted by the source Publisher during sequential + * time windows of a specified duration. + *

+ * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas + * {@link #throttleFirst} does not tick, it just tracks the passage of time. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
{@code throttleLast} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param intervalDuration + * duration of windows within which the last item emitted by the source Publisher will be + * emitted + * @param unit + * the unit of time of {@code intervalDuration} + * @return a Flowable that performs the throttle operation + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + * @see #sample(long, TimeUnit) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable throttleLast(long intervalDuration, TimeUnit unit) { + return sample(intervalDuration, unit); + } + + /** + * Returns a Flowable that emits only the last item emitted by the source Publisher during sequential + * time windows of a specified duration, where the duration is governed by a specified Scheduler. + *

+ * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas + * {@link #throttleFirst} does not tick, it just tracks the passage of time. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param intervalDuration + * duration of windows within which the last item emitted by the source Publisher will be + * emitted + * @param unit + * the unit of time of {@code intervalDuration} + * @param scheduler + * the {@link Scheduler} to use internally to manage the timers that handle timeout for each + * event + * @return a Flowable that performs the throttle operation + * @see ReactiveX operators documentation: Sample + * @see RxJava wiki: Backpressure + * @see #sample(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable throttleLast(long intervalDuration, TimeUnit unit, Scheduler scheduler) { + return sample(intervalDuration, unit, scheduler); + } + + /** + * Throttles items from the upstream {@code Flowable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + *

+ * + *

+ * Unlike the option with {@link #throttleLatest(long, TimeUnit, boolean)}, the very last item being held back + * (if any) is not emitted when the upstream completes. + *

+ * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow. + * If the downstream is not ready to receive items, a + * {@link io.reactivex.exceptions.MissingBackpressureException MissingBackpressureException} + * will be signaled.
+ *
Scheduler:
+ *
{@code throttleLatest} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @return the new Flowable instance + * @since 2.2 + * @see #throttleLatest(long, TimeUnit, boolean) + * @see #throttleLatest(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable throttleLatest(long timeout, TimeUnit unit) { + return throttleLatest(timeout, unit, Schedulers.computation(), false); + } + + /** + * Throttles items from the upstream {@code Flowable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + *

+ * + *

+ * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow. + * If the downstream is not ready to receive items, a + * {@link io.reactivex.exceptions.MissingBackpressureException MissingBackpressureException} + * will be signaled.
+ *
Scheduler:
+ *
{@code throttleLatest} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param emitLast If {@code true}, the very last item from the upstream will be emitted + * immediately when the upstream completes, regardless if there is + * a timeout window active or not. If {@code false}, the very last + * upstream item is ignored and the flow terminates. + * @return the new Flowable instance + * @see #throttleLatest(long, TimeUnit, Scheduler, boolean) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable throttleLatest(long timeout, TimeUnit unit, boolean emitLast) { + return throttleLatest(timeout, unit, Schedulers.computation(), emitLast); + } + + /** + * Throttles items from the upstream {@code Flowable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + *

+ * + *

+ * Unlike the option with {@link #throttleLatest(long, TimeUnit, Scheduler, boolean)}, the very last item being held back + * (if any) is not emitted when the upstream completes. + *

+ * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow. + * If the downstream is not ready to receive items, a + * {@link io.reactivex.exceptions.MissingBackpressureException MissingBackpressureException} + * will be signaled.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param scheduler the {@link Scheduler} where the timed wait and latest item + * emission will be performed + * @return the new Flowable instance + * @see #throttleLatest(long, TimeUnit, Scheduler, boolean) + * @since 2.2 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable throttleLatest(long timeout, TimeUnit unit, Scheduler scheduler) { + return throttleLatest(timeout, unit, scheduler, false); + } + + /** + * Throttles items from the upstream {@code Flowable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + *

+ * + *

+ * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow. + * If the downstream is not ready to receive items, a + * {@link io.reactivex.exceptions.MissingBackpressureException MissingBackpressureException} + * will be signaled.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param scheduler the {@link Scheduler} where the timed wait and latest item + * emission will be performed + * @param emitLast If {@code true}, the very last item from the upstream will be emitted + * immediately when the upstream completes, regardless if there is + * a timeout window active or not. If {@code false}, the very last + * upstream item is ignored and the flow terminates. + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable throttleLatest(long timeout, TimeUnit unit, Scheduler scheduler, boolean emitLast) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableThrottleLatest(this, timeout, unit, scheduler, emitLast)); + } + + /** + * Returns a Flowable that mirrors the source Publisher, except that it drops items emitted by the + * source Publisher that are followed by newer items before a timeout value expires. The timer resets on + * each emission (alias to {@link #debounce(long, TimeUnit)}). + *

+ * Note: If items keep being emitted by the source Publisher faster than the timeout then no items + * will be emitted by the resulting Publisher. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
{@code throttleWithTimeout} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * the length of the window of time that must pass after the emission of an item from the source + * Publisher in which that Publisher emits no items in order for the item to be emitted by the + * resulting Publisher + * @param unit + * the unit of time for the specified {@code timeout} + * @return a Flowable that filters out items from the source Publisher that are too quickly followed by + * newer items + * @see ReactiveX operators documentation: Debounce + * @see RxJava wiki: Backpressure + * @see #debounce(long, TimeUnit) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable throttleWithTimeout(long timeout, TimeUnit unit) { + return debounce(timeout, unit); + } + + /** + * Returns a Flowable that mirrors the source Publisher, except that it drops items emitted by the + * source Publisher that are followed by newer items before a timeout value expires on a specified + * Scheduler. The timer resets on each emission (alias to {@link #debounce(long, TimeUnit, Scheduler)}). + *

+ * Note: If items keep being emitted by the source Publisher faster than the timeout then no items + * will be emitted by the resulting Publisher. + *

+ * + *

+ *
Backpressure:
+ *
This operator does not support backpressure as it uses time to control data flow.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * the length of the window of time that must pass after the emission of an item from the source + * Publisher in which that Publisher emits no items in order for the item to be emitted by the + * resulting Publisher + * @param unit + * the unit of time for the specified {@code timeout} + * @param scheduler + * the {@link Scheduler} to use internally to manage the timers that handle the timeout for each + * item + * @return a Flowable that filters out items from the source Publisher that are too quickly followed by + * newer items + * @see ReactiveX operators documentation: Debounce + * @see RxJava wiki: Backpressure + * @see #debounce(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable throttleWithTimeout(long timeout, TimeUnit unit, Scheduler scheduler) { + return debounce(timeout, unit, scheduler); + } + + /** + * Returns a Flowable that emits records of the time interval between consecutive items emitted by the + * source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code timeInterval} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @return a Flowable that emits time interval information items + * @see ReactiveX operators documentation: TimeInterval + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> timeInterval() { + return timeInterval(TimeUnit.MILLISECONDS, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits records of the time interval between consecutive items emitted by the + * source Publisher, where this interval is computed on a specified Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
The operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
+ *
+ * + * @param scheduler + * the {@link Scheduler} used to compute time intervals + * @return a Flowable that emits time interval information items + * @see ReactiveX operators documentation: TimeInterval + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. + public final Flowable> timeInterval(Scheduler scheduler) { + return timeInterval(TimeUnit.MILLISECONDS, scheduler); + } + + /** + * Returns a Flowable that emits records of the time interval between consecutive items emitted by the + * source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code timeInterval} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param unit the time unit for the current time + * @return a Flowable that emits time interval information items + * @see ReactiveX operators documentation: TimeInterval + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> timeInterval(TimeUnit unit) { + return timeInterval(unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits records of the time interval between consecutive items emitted by the + * source Publisher, where this interval is computed on a specified Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
The operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
+ *
+ * + * @param unit the time unit for the current time + * @param scheduler + * the {@link Scheduler} used to compute time intervals + * @return a Flowable that emits time interval information items + * @see ReactiveX operators documentation: TimeInterval + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. + public final Flowable> timeInterval(TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableTimeInterval(this, unit, scheduler)); + } + + /** + * Returns a Flowable that mirrors the source Publisher, but notifies Subscribers of a + * {@code TimeoutException} if an item emitted by the source Publisher doesn't arrive within a window of + * time after the emission of the previous item, where that period of time is measured by a Publisher that + * is a function of the previous item. + *

+ * + *

+ * Note: The arrival of the first source item is never timed out. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
+ *
+ * + * @param + * the timeout value type (ignored) + * @param itemTimeoutIndicator + * a function that returns a Publisher for each item emitted by the source + * Publisher and that determines the timeout window for the subsequent item + * @return a Flowable that mirrors the source Publisher, but notifies Subscribers of a + * {@code TimeoutException} if an item emitted by the source Publisher takes longer to arrive than + * the time window defined by the selector for the previously emitted item + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable timeout(Function> itemTimeoutIndicator) { + return timeout0(null, itemTimeoutIndicator, null); + } + + /** + * Returns a Flowable that mirrors the source Publisher, but that switches to a fallback Publisher if + * an item emitted by the source Publisher doesn't arrive within a window of time after the emission of the + * previous item, where that period of time is measured by a Publisher that is a function of the previous + * item. + *

+ * + *

+ * Note: The arrival of the first source item is never timed out. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
+ *
+ * + * @param + * the timeout value type (ignored) + * @param itemTimeoutIndicator + * a function that returns a Publisher, for each item emitted by the source Publisher, that + * determines the timeout window for the subsequent item + * @param other + * the fallback Publisher to switch to if the source Publisher times out + * @return a Flowable that mirrors the source Publisher, but switches to mirroring a fallback Publisher + * if an item emitted by the source Publisher takes longer to arrive than the time window defined + * by the selector for the previously emitted item + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable timeout(Function> itemTimeoutIndicator, Flowable other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(null, itemTimeoutIndicator, other); + } + + /** + * Returns a Flowable that mirrors the source Publisher but applies a timeout policy for each emitted + * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, + * the resulting Publisher terminates and notifies Subscribers of a {@code TimeoutException}. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * maximum duration between emitted items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument. + * @return the source Publisher modified to notify Subscribers of a {@code TimeoutException} in case of a + * timeout + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable timeout(long timeout, TimeUnit timeUnit) { + return timeout0(timeout, timeUnit, null, Schedulers.computation()); + } + + /** + * Returns a Flowable that mirrors the source Publisher but applies a timeout policy for each emitted + * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, + * the source Publisher is disposed and resulting Publisher begins instead to mirror a fallback Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param other + * the fallback Publisher to use in case of a timeout + * @return the source Publisher modified to switch to the fallback Publisher in case of a timeout + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable timeout(long timeout, TimeUnit timeUnit, Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(timeout, timeUnit, other, Schedulers.computation()); + } + + /** + * Returns a Flowable that mirrors the source Publisher but applies a timeout policy for each emitted + * item using a specified Scheduler. If the next item isn't emitted within the specified timeout duration + * starting from its predecessor, the source Publisher is disposed and resulting Publisher begins + * instead to mirror a fallback Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param scheduler + * the {@link Scheduler} to run the timeout timers on + * @param other + * the Publisher to use as the fallback in case of a timeout + * @return the source Publisher modified so that it will switch to the fallback Publisher in case of a + * timeout + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler, Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(timeout, timeUnit, other, scheduler); + } + + /** + * Returns a Flowable that mirrors the source Publisher but applies a timeout policy for each emitted + * item, where this policy is governed by a specified Scheduler. If the next item isn't emitted within the + * specified timeout duration starting from its predecessor, the resulting Publisher terminates and + * notifies Subscribers of a {@code TimeoutException}. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param scheduler + * the Scheduler to run the timeout timers on + * @return the source Publisher modified to notify Subscribers of a {@code TimeoutException} in case of a + * timeout + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler) { + return timeout0(timeout, timeUnit, null, scheduler); + } + + /** + * Returns a Flowable that mirrors the source Publisher, but notifies Subscribers of a + * {@code TimeoutException} if either the first item emitted by the source Publisher or any subsequent item + * doesn't arrive within time windows defined by other Publishers. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. Both this and the returned {@code Publisher}s + * are expected to honor backpressure as well. If any of then violates this rule, it may throw an + * {@code IllegalStateException} when the {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code timeout} does not operate by default on any {@link Scheduler}.
+ *
+ * + * @param + * the first timeout value type (ignored) + * @param + * the subsequent timeout value type (ignored) + * @param firstTimeoutIndicator + * a function that returns a Publisher that determines the timeout window for the first source + * item + * @param itemTimeoutIndicator + * a function that returns a Publisher for each item emitted by the source Publisher and that + * determines the timeout window in which the subsequent source item must arrive in order to + * continue the sequence + * @return a Flowable that mirrors the source Publisher, but notifies Subscribers of a + * {@code TimeoutException} if either the first item or any subsequent item doesn't arrive within + * the time windows specified by the timeout selectors + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable timeout(Publisher firstTimeoutIndicator, + Function> itemTimeoutIndicator) { + ObjectHelper.requireNonNull(firstTimeoutIndicator, "firstTimeoutIndicator is null"); + return timeout0(firstTimeoutIndicator, itemTimeoutIndicator, null); + } + + /** + * Returns a Flowable that mirrors the source Publisher, but switches to a fallback Publisher if either + * the first item emitted by the source Publisher or any subsequent item doesn't arrive within time windows + * defined by other Publishers. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The {@code Publisher} + * sources are expected to honor backpressure as well. + * If any of the source {@code Publisher}s violate this, it may throw an + * {@code IllegalStateException} when the source {@code Publisher} completes.
+ *
Scheduler:
+ *
{@code timeout} does not operate by default on any {@link Scheduler}.
+ *
+ * + * @param + * the first timeout value type (ignored) + * @param + * the subsequent timeout value type (ignored) + * @param firstTimeoutIndicator + * a function that returns a Publisher which determines the timeout window for the first source + * item + * @param itemTimeoutIndicator + * a function that returns a Publisher for each item emitted by the source Publisher and that + * determines the timeout window in which the subsequent source item must arrive in order to + * continue the sequence + * @param other + * the fallback Publisher to switch to if the source Publisher times out + * @return a Flowable that mirrors the source Publisher, but switches to the {@code other} Publisher if + * either the first item emitted by the source Publisher or any subsequent item doesn't arrive + * within time windows defined by the timeout selectors + * @throws NullPointerException + * if {@code itemTimeoutIndicator} is null + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable timeout( + Publisher firstTimeoutIndicator, + Function> itemTimeoutIndicator, + Publisher other) { + ObjectHelper.requireNonNull(firstTimeoutIndicator, "firstTimeoutSelector is null"); + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(firstTimeoutIndicator, itemTimeoutIndicator, other); + } + + private Flowable timeout0(long timeout, TimeUnit timeUnit, Publisher other, + Scheduler scheduler) { + ObjectHelper.requireNonNull(timeUnit, "timeUnit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableTimeoutTimed(this, timeout, timeUnit, scheduler, other)); + } + + private Flowable timeout0( + Publisher firstTimeoutIndicator, + Function> itemTimeoutIndicator, + Publisher other) { + ObjectHelper.requireNonNull(itemTimeoutIndicator, "itemTimeoutIndicator is null"); + return RxJavaPlugins.onAssembly(new FlowableTimeout(this, firstTimeoutIndicator, itemTimeoutIndicator, other)); + } + + /** + * Returns a Flowable that emits each item emitted by the source Publisher, wrapped in a + * {@link Timed} object. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code timestamp} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @return a Flowable that emits timestamped items from the source Publisher + * @see ReactiveX operators documentation: Timestamp + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> timestamp() { + return timestamp(TimeUnit.MILLISECONDS, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits each item emitted by the source Publisher, wrapped in a + * {@link Timed} object whose timestamps are provided by a specified Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
This operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to use as a time source + * @return a Flowable that emits timestamped items from the source Publisher with timestamps provided by + * the {@code scheduler} + * @see ReactiveX operators documentation: Timestamp + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. + public final Flowable> timestamp(Scheduler scheduler) { + return timestamp(TimeUnit.MILLISECONDS, scheduler); + } + + /** + * Returns a Flowable that emits each item emitted by the source Publisher, wrapped in a + * {@link Timed} object. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
{@code timestamp} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param unit the time unit for the current time + * @return a Flowable that emits timestamped items from the source Publisher + * @see ReactiveX operators documentation: Timestamp + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> timestamp(TimeUnit unit) { + return timestamp(unit, Schedulers.computation()); + } + + /** + * Returns a Flowable that emits each item emitted by the source Publisher, wrapped in a + * {@link Timed} object whose timestamps are provided by a specified Scheduler. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
This operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
+ *
+ * + * @param unit the time unit for the current time + * @param scheduler + * the {@link Scheduler} to use as a time source + * @return a Flowable that emits timestamped items from the source Publisher with timestamps provided by + * the {@code scheduler} + * @see ReactiveX operators documentation: Timestamp + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. + public final Flowable> timestamp(final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return map(Functions.timestampWith(unit, scheduler)); + } + + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * This allows fluent conversion to any other type. + *

+ *
Backpressure:
+ *
The backpressure behavior depends on what happens in the {@code converter} function.
+ *
Scheduler:
+ *
{@code to} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the resulting object type + * @param converter the function that receives the current Flowable instance and returns a value + * @return the value returned by the function + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.SPECIAL) + @SchedulerSupport(SchedulerSupport.NONE) + public final R to(Function, R> converter) { + try { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Returns a Single that emits a single item, a list composed of all the items emitted by the + * finite upstream source Publisher. + *

+ * + *

+ * Normally, a Publisher that returns multiple items will do so by invoking its {@link Subscriber}'s + * {@link Subscriber#onNext onNext} method for each such item. You can change this behavior, instructing the + * Publisher to compose a list of all of these items and then to invoke the Subscriber's {@code onNext} + * function once, passing it the entire list, by calling the Publisher's {@code toList} method prior to + * calling its {@link #subscribe} method. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Single that emits a single item: a List containing all of the items emitted by the source + * Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toList() { + return RxJavaPlugins.onAssembly(new FlowableToListSingle>(this)); + } + + /** + * Returns a Single that emits a single item, a list composed of all the items emitted by the + * finite source Publisher. + *

+ * + *

+ * Normally, a Publisher that returns multiple items will do so by invoking its {@link Subscriber}'s + * {@link Subscriber#onNext onNext} method for each such item. You can change this behavior, instructing the + * Publisher to compose a list of all of these items and then to invoke the Subscriber's {@code onNext} + * function once, passing it the entire list, by calling the Publisher's {@code toList} method prior to + * calling its {@link #subscribe} method. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacityHint + * the number of elements expected from the current Flowable + * @return a Flowable that emits a single item: a List containing all of the items emitted by the source + * Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toList(final int capacityHint) { + ObjectHelper.verifyPositive(capacityHint, "capacityHint"); + return RxJavaPlugins.onAssembly(new FlowableToListSingle>(this, Functions.createArrayList(capacityHint))); + } + + /** + * Returns a Single that emits a single item, a list composed of all the items emitted by the + * finite source Publisher. + *

+ * + *

+ * Normally, a Publisher that returns multiple items will do so by invoking its {@link Subscriber}'s + * {@link Subscriber#onNext onNext} method for each such item. You can change this behavior, instructing the + * Publisher to compose a list of all of these items and then to invoke the Subscriber's {@code onNext} + * function once, passing it the entire list, by calling the Publisher's {@code toList} method prior to + * calling its {@link #subscribe} method. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated collection to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the subclass of a collection of Ts + * @param collectionSupplier + * the Callable returning the collection (for each individual Subscriber) to be filled in + * @return a Single that emits a single item: a List containing all of the items emitted by the source + * Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final > Single toList(Callable collectionSupplier) { + ObjectHelper.requireNonNull(collectionSupplier, "collectionSupplier is null"); + return RxJavaPlugins.onAssembly(new FlowableToListSingle(this, collectionSupplier)); + } + + /** + * Returns a Single that emits a single HashMap containing all items emitted by the finite source Publisher, + * mapped by the keys returned by a specified {@code keySelector} function. + *

+ * + *

+ * If more than one source item maps to the same key, the HashMap will contain the latest of those items. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param keySelector + * the function that extracts the key from a source item to be used in the HashMap + * @return a Single that emits a single item: a HashMap containing the mapped items from the source + * Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toMap(final Function keySelector) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + return collect(HashMapSupplier.asCallable(), Functions.toMapKeySelector(keySelector)); + } + + /** + * Returns a Single that emits a single HashMap containing values corresponding to items emitted by the + * finite source Publisher, mapped by the keys returned by a specified {@code keySelector} function. + *

+ * + *

+ * If more than one source item maps to the same key, the HashMap will contain a single entry that + * corresponds to the latest of those items. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts the key from a source item to be used in the HashMap + * @param valueSelector + * the function that extracts the value from a source item to be used in the HashMap + * @return a Single that emits a single item: a HashMap containing the mapped items from the source + * Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toMap(final Function keySelector, final Function valueSelector) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + return collect(HashMapSupplier.asCallable(), Functions.toMapKeyValueSelector(keySelector, valueSelector)); + } + + /** + * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that + * contains keys and values extracted from the items emitted by the finite source Publisher. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts the key from a source item to be used in the Map + * @param valueSelector + * the function that extracts the value from the source items to be used as value in the Map + * @param mapSupplier + * the function that returns a Map instance to be used + * @return a Flowable that emits a single item: a Map that contains the mapped items emitted by the + * source Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toMap(final Function keySelector, + final Function valueSelector, + final Callable> mapSupplier) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + return collect(mapSupplier, Functions.toMapKeyValueSelector(keySelector, valueSelector)); + } + + /** + * Returns a Single that emits a single HashMap that contains an ArrayList of items emitted by the + * finite source Publisher keyed by a specified {@code keySelector} function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
This operator does not support backpressure as by intent it is requesting and buffering everything.
+ *
Scheduler:
+ *
{@code toMultimap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param keySelector + * the function that extracts the key from the source items to be used as key in the HashMap + * @return a Single that emits a single item: a HashMap that contains an ArrayList of items mapped from + * the source Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single>> toMultimap(Function keySelector) { + Function valueSelector = Functions.identity(); + Callable>> mapSupplier = HashMapSupplier.asCallable(); + Function> collectionFactory = ArrayListSupplier.asFunction(); + return toMultimap(keySelector, valueSelector, mapSupplier, collectionFactory); + } + + /** + * Returns a Single that emits a single HashMap that contains an ArrayList of values extracted by a + * specified {@code valueSelector} function from items emitted by the finite source Publisher, keyed by a + * specified {@code keySelector} function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toMultimap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts a key from the source items to be used as key in the HashMap + * @param valueSelector + * the function that extracts a value from the source items to be used as value in the HashMap + * @return a Single that emits a single item: a HashMap that contains an ArrayList of items mapped from + * the source Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single>> toMultimap(Function keySelector, Function valueSelector) { + Callable>> mapSupplier = HashMapSupplier.asCallable(); + Function> collectionFactory = ArrayListSupplier.asFunction(); + return toMultimap(keySelector, valueSelector, mapSupplier, collectionFactory); + } + + /** + * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that + * contains a custom collection of values, extracted by a specified {@code valueSelector} function from + * items emitted by the finite source Publisher, and keyed by the {@code keySelector} function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toMultimap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts a key from the source items to be used as the key in the Map + * @param valueSelector + * the function that extracts a value from the source items to be used as the value in the Map + * @param mapSupplier + * the function that returns a Map instance to be used + * @param collectionFactory + * the function that returns a Collection instance for a particular key to be used in the Map + * @return a Single that emits a single item: a Map that contains the collection of mapped items from + * the source Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single>> toMultimap( + final Function keySelector, + final Function valueSelector, + final Callable>> mapSupplier, + final Function> collectionFactory) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + ObjectHelper.requireNonNull(mapSupplier, "mapSupplier is null"); + ObjectHelper.requireNonNull(collectionFactory, "collectionFactory is null"); + return collect(mapSupplier, Functions.toMultimapKeyValueSelector(keySelector, valueSelector, collectionFactory)); + } + + /** + * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that + * contains an ArrayList of values, extracted by a specified {@code valueSelector} function from items + * emitted by the finite source Publisher and keyed by the {@code keySelector} function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toMultimap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts a key from the source items to be used as the key in the Map + * @param valueSelector + * the function that extracts a value from the source items to be used as the value in the Map + * @param mapSupplier + * the function that returns a Map instance to be used + * @return a Single that emits a single item: a Map that contains a list items mapped from the source + * Publisher + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single>> toMultimap( + Function keySelector, + Function valueSelector, + Callable>> mapSupplier + ) { + return toMultimap(keySelector, valueSelector, mapSupplier, ArrayListSupplier.asFunction()); + } + + /** + * Converts the current Flowable into a non-backpressured {@link Observable}. + *
+ *
Backpressure:
+ *
Observables don't support backpressure thus the current Flowable is consumed in an unbounded + * manner (by requesting Long.MAX_VALUE).
+ *
Scheduler:
+ *
{@code toObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Observable instance + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable toObservable() { + return RxJavaPlugins.onAssembly(new ObservableFromPublisher(this)); + } + + /** + * Returns a Single that emits a list that contains the items emitted by the finite source Publisher, in a + * sorted order. Each item emitted by the Publisher must implement {@link Comparable} with respect to all + * other items in the sequence. + * + *

If any item emitted by this Flowable does not implement {@link Comparable} with respect to + * all other items emitted by this Flowable, no items will be emitted and the + * sequence is terminated with a {@link ClassCastException}. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toSortedList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return a Single that emits a list that contains the items emitted by the source Publisher in + * sorted order + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toSortedList() { + return toSortedList(Functions.naturalComparator()); + } + + /** + * Returns a Single that emits a list that contains the items emitted by the finite source Publisher, in a + * sorted order based on a specified comparison function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toSortedList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param comparator + * a function that compares two items emitted by the source Publisher and returns an Integer + * that indicates their sort order + * @return a Single that emits a list that contains the items emitted by the source Publisher in + * sorted order + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toSortedList(final Comparator comparator) { + ObjectHelper.requireNonNull(comparator, "comparator is null"); + return toList().map(Functions.listSorter(comparator)); + } + + /** + * Returns a Single that emits a list that contains the items emitted by the finite source Publisher, in a + * sorted order based on a specified comparison function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toSortedList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param comparator + * a function that compares two items emitted by the source Publisher and returns an Integer + * that indicates their sort order + * @param capacityHint + * the initial capacity of the ArrayList used to accumulate items before sorting + * @return a Single that emits a list that contains the items emitted by the source Publisher in + * sorted order + * @see ReactiveX operators documentation: To + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toSortedList(final Comparator comparator, int capacityHint) { + ObjectHelper.requireNonNull(comparator, "comparator is null"); + return toList(capacityHint).map(Functions.listSorter(comparator)); + } + + /** + * Returns a Flowable that emits a list that contains the items emitted by the finite source Publisher, in a + * sorted order. Each item emitted by the Publisher must implement {@link Comparable} with respect to all + * other items in the sequence. + * + *

If any item emitted by this Flowable does not implement {@link Comparable} with respect to + * all other items emitted by this Flowable, no items will be emitted and the + * sequence is terminated with a {@link ClassCastException}. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream and consumes the source {@code Publisher} in an + * unbounded manner (i.e., without applying backpressure to it).
+ *
Scheduler:
+ *
{@code toSortedList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacityHint + * the initial capacity of the ArrayList used to accumulate items before sorting + * @return a Flowable that emits a list that contains the items emitted by the source Publisher in + * sorted order + * @see ReactiveX operators documentation: To + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toSortedList(int capacityHint) { + return toSortedList(Functions.naturalComparator(), capacityHint); + } + + /** + * Modifies the source Publisher so that subscribers will cancel it on a specified + * {@link Scheduler}. + *
+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to perform cancellation actions on + * @return the source Publisher modified so that its cancellations happen on the specified + * {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable unsubscribeOn(Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableUnsubscribeOn(this, scheduler)); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows, each containing {@code count} items. When the source + * Publisher completes or encounters an error, the resulting Publisher emits the current window and + * propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure of its inner and outer subscribers, however, the inner Publisher uses an + * unbounded buffer that may hold at most {@code count} elements.
+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum size of each window before it should be emitted + * @return a Flowable that emits connected, non-overlapping windows, each containing at most + * {@code count} items from the source Publisher + * @throws IllegalArgumentException if either count is non-positive + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> window(long count) { + return window(count, count, bufferSize()); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits windows every {@code skip} items, each containing no more than {@code count} items. When + * the source Publisher completes or encounters an error, the resulting Publisher emits the current window + * and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure of its inner and outer subscribers, however, the inner Publisher uses an + * unbounded buffer that may hold at most {@code count} elements.
+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum size of each window before it should be emitted + * @param skip + * how many items need to be skipped before starting a new window. Note that if {@code skip} and + * {@code count} are equal this is the same operation as {@link #window(long)}. + * @return a Flowable that emits windows every {@code skip} items containing at most {@code count} items + * from the source Publisher + * @throws IllegalArgumentException if either count or skip is non-positive + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> window(long count, long skip) { + return window(count, skip, bufferSize()); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits windows every {@code skip} items, each containing no more than {@code count} items. When + * the source Publisher completes or encounters an error, the resulting Publisher emits the current window + * and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure of its inner and outer subscribers, however, the inner Publisher uses an + * unbounded buffer that may hold at most {@code count} elements.
+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum size of each window before it should be emitted + * @param skip + * how many items need to be skipped before starting a new window. Note that if {@code skip} and + * {@code count} are equal this is the same operation as {@link #window(long)}. + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return a Flowable that emits windows every {@code skip} items containing at most {@code count} items + * from the source Publisher + * @throws IllegalArgumentException if either count or skip is non-positive + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> window(long count, long skip, int bufferSize) { + ObjectHelper.verifyPositive(skip, "skip"); + ObjectHelper.verifyPositive(count, "count"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableWindow(this, count, skip, bufferSize)); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher starts a new window periodically, as determined by the {@code timeskip} argument. It emits + * each window after a fixed timespan, specified by the {@code timespan} argument. When the source + * Publisher completes or Publisher completes or encounters an error, the resulting Publisher emits the + * current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
+ *
Scheduler:
+ *
This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted + * @param timeskip + * the period of time after which a new window will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @return a Flowable that emits new windows periodically as a fixed timespan elapses + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable> window(long timespan, long timeskip, TimeUnit unit) { + return window(timespan, timeskip, unit, Schedulers.computation(), bufferSize()); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher starts a new window periodically, as determined by the {@code timeskip} argument. It emits + * each window after a fixed timespan, specified by the {@code timespan} argument. When the source + * Publisher completes or Publisher completes or encounters an error, the resulting Publisher emits the + * current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted + * @param timeskip + * the period of time after which a new window will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @return a Flowable that emits new windows periodically as a fixed timespan elapses + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable> window(long timespan, long timeskip, TimeUnit unit, Scheduler scheduler) { + return window(timespan, timeskip, unit, scheduler, bufferSize()); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher starts a new window periodically, as determined by the {@code timeskip} argument. It emits + * each window after a fixed timespan, specified by the {@code timespan} argument. When the source + * Publisher completes or Publisher completes or encounters an error, the resulting Publisher emits the + * current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted + * @param timeskip + * the period of time after which a new window will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return a Flowable that emits new windows periodically as a fixed timespan elapses + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable> window(long timespan, long timeskip, TimeUnit unit, Scheduler scheduler, int bufferSize) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.verifyPositive(timespan, "timespan"); + ObjectHelper.verifyPositive(timeskip, "timeskip"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + return RxJavaPlugins.onAssembly(new FlowableWindowTimed(this, timespan, timeskip, unit, scheduler, Long.MAX_VALUE, bufferSize, false)); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument. When the source Publisher completes or encounters an error, the resulting + * Publisher emits the current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure and may hold up to {@code count} elements at most.
+ *
Scheduler:
+ *
This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time that applies to the {@code timespan} argument + * @return a Flowable that emits connected, non-overlapping windows representing items emitted by the + * source Publisher during fixed, consecutive durations + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable> window(long timespan, TimeUnit unit) { + return window(timespan, unit, Schedulers.computation(), Long.MAX_VALUE, false); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows, each of a fixed duration as specified by the + * {@code timespan} argument or a maximum size as specified by the {@code count} argument (whichever is + * reached first). When the source Publisher completes or encounters an error, the resulting Publisher + * emits the current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure and may hold up to {@code count} elements at most.
+ *
Scheduler:
+ *
This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time that applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @return a Flowable that emits connected, non-overlapping windows of items from the source Publisher + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable> window(long timespan, TimeUnit unit, + long count) { + return window(timespan, unit, Schedulers.computation(), count, false); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows, each of a fixed duration as specified by the + * {@code timespan} argument or a maximum size as specified by the {@code count} argument (whichever is + * reached first). When the source Publisher completes or encounters an error, the resulting Publisher + * emits the current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure and may hold up to {@code count} elements at most.
+ *
Scheduler:
+ *
This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time that applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @param restart + * if true, when a window reaches the capacity limit, the timer is restarted as well + * @return a Flowable that emits connected, non-overlapping windows of items from the source Publisher + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Flowable> window(long timespan, TimeUnit unit, + long count, boolean restart) { + return window(timespan, unit, Schedulers.computation(), count, restart); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows, each of a fixed duration as specified by the + * {@code timespan} argument. When the source Publisher completes or encounters an error, the resulting + * Publisher emits the current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @return a Flowable that emits connected, non-overlapping windows containing items emitted by the + * source Publisher within a fixed duration + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable> window(long timespan, TimeUnit unit, + Scheduler scheduler) { + return window(timespan, unit, scheduler, Long.MAX_VALUE, false); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source Publisher completes or encounters an error, the resulting Publisher emits the + * current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure and may hold up to {@code count} elements at most.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @return a Flowable that emits connected, non-overlapping windows of items from the source Publisher + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable> window(long timespan, TimeUnit unit, + Scheduler scheduler, long count) { + return window(timespan, unit, scheduler, count, false); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source Publisher completes or encounters an error, the resulting Publisher emits the + * current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure and may hold up to {@code count} elements at most.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @param restart + * if true, when a window reaches the capacity limit, the timer is restarted as well + * @return a Flowable that emits connected, non-overlapping windows of items from the source Publisher + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable> window(long timespan, TimeUnit unit, + Scheduler scheduler, long count, boolean restart) { + return window(timespan, unit, scheduler, count, restart, bufferSize()); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source Publisher completes or encounters an error, the resulting Publisher emits the + * current window and propagates the notification from the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * time to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure and may hold up to {@code count} elements at most.
+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @param restart + * if true, when a window reaches the capacity limit, the timer is restarted as well + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return a Flowable that emits connected, non-overlapping windows of items from the source Publisher + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Flowable> window( + long timespan, TimeUnit unit, Scheduler scheduler, + long count, boolean restart, int bufferSize) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.verifyPositive(count, "count"); + return RxJavaPlugins.onAssembly(new FlowableWindowTimed(this, timespan, timespan, unit, scheduler, count, bufferSize, restart)); + } + + /** + * Returns a Flowable that emits non-overlapping windows of items it collects from the source Publisher + * where the boundary of each window is determined by the items emitted from a specified boundary-governing + * Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The outer Publisher of this operator does not support backpressure as it uses a {@code boundary} Publisher to control data + * flow. The inner Publishers honor backpressure and buffer everything until the boundary signals the next element.
+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the window element type (ignored) + * @param boundaryIndicator + * a Publisher whose emitted items close and open windows + * @return a Flowable that emits non-overlapping windows of items it collects from the source Publisher + * where the boundary of each window is determined by the items emitted from the {@code boundary} + * Publisher + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> window(Publisher boundaryIndicator) { + return window(boundaryIndicator, bufferSize()); + } + + /** + * Returns a Flowable that emits non-overlapping windows of items it collects from the source Publisher + * where the boundary of each window is determined by the items emitted from a specified boundary-governing + * Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The outer Publisher of this operator does not support backpressure as it uses a {@code boundary} Publisher to control data + * flow. The inner Publishers honor backpressure and buffer everything until the boundary signals the next element.
+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the window element type (ignored) + * @param boundaryIndicator + * a Publisher whose emitted items close and open windows + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return a Flowable that emits non-overlapping windows of items it collects from the source Publisher + * where the boundary of each window is determined by the items emitted from the {@code boundary} + * Publisher + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> window(Publisher boundaryIndicator, int bufferSize) { + ObjectHelper.requireNonNull(boundaryIndicator, "boundaryIndicator is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableWindowBoundary(this, boundaryIndicator, bufferSize)); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits windows that contain those items emitted by the source Publisher between the time when + * the {@code windowOpenings} Publisher emits an item and when the Publisher returned by + * {@code closingSelector} emits an item. + *

+ * + *

+ *
Backpressure:
+ *
The outer Publisher of this operator doesn't support backpressure because the emission of new + * inner Publishers are controlled by the {@code windowOpenings} Publisher. + * The inner Publishers honor backpressure and buffer everything until the associated closing + * Publisher signals or completes.
+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the window-opening Publisher + * @param the element type of the window-closing Publishers + * @param openingIndicator + * a Publisher that, when it emits an item, causes another window to be created + * @param closingIndicator + * a {@link Function} that produces a Publisher for every window created. When this Publisher + * emits an item, the associated window is closed and emitted + * @return a Flowable that emits windows of items emitted by the source Publisher that are governed by + * the specified window-governing Publishers + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> window( + Publisher openingIndicator, + Function> closingIndicator) { + return window(openingIndicator, closingIndicator, bufferSize()); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits windows that contain those items emitted by the source Publisher between the time when + * the {@code windowOpenings} Publisher emits an item and when the Publisher returned by + * {@code closingSelector} emits an item. + *

+ * + *

+ *
Backpressure:
+ *
The outer Publisher of this operator doesn't support backpressure because the emission of new + * inner Publishers are controlled by the {@code windowOpenings} Publisher. + * The inner Publishers honor backpressure and buffer everything until the associated closing + * Publisher signals or completes.
+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the window-opening Publisher + * @param the element type of the window-closing Publishers + * @param openingIndicator + * a Publisher that, when it emits an item, causes another window to be created + * @param closingIndicator + * a {@link Function} that produces a Publisher for every window created. When this Publisher + * emits an item, the associated window is closed and emitted + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return a Flowable that emits windows of items emitted by the source Publisher that are governed by + * the specified window-governing Publishers + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> window( + Publisher openingIndicator, + Function> closingIndicator, int bufferSize) { + ObjectHelper.requireNonNull(openingIndicator, "openingIndicator is null"); + ObjectHelper.requireNonNull(closingIndicator, "closingIndicator is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableWindowBoundarySelector(this, openingIndicator, closingIndicator, bufferSize)); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows. It emits the current window and opens a new one + * whenever the Publisher produced by the specified {@code closingSelector} emits an item. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * the {@code closingSelector} to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the boundary Publisher + * @param boundaryIndicatorSupplier + * a {@link Callable} that returns a {@code Publisher} that governs the boundary between windows. + * When the source {@code Publisher} emits an item, {@code window} emits the current window and begins + * a new one. + * @return a Flowable that emits connected, non-overlapping windows of items from the source Publisher + * whenever {@code closingSelector} emits an item + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> window(Callable> boundaryIndicatorSupplier) { + return window(boundaryIndicatorSupplier, bufferSize()); + } + + /** + * Returns a Flowable that emits windows of items it collects from the source Publisher. The resulting + * Publisher emits connected, non-overlapping windows. It emits the current window and opens a new one + * whenever the Publisher produced by the specified {@code closingSelector} emits an item. + *

+ * + *

+ *
Backpressure:
+ *
The operator consumes the source {@code Publisher} in an unbounded manner. + * The returned {@code Publisher} doesn't support backpressure as it uses + * the {@code closingSelector} to control the creation of windows. The returned inner {@code Publisher}s honor + * backpressure but have an unbounded inner buffer that may lead to {@code OutOfMemoryError} + * if left unconsumed.
+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the boundary Publisher + * @param boundaryIndicatorSupplier + * a {@link Callable} that returns a {@code Publisher} that governs the boundary between windows. + * When the source {@code Publisher} emits an item, {@code window} emits the current window and begins + * a new one. + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return a Flowable that emits connected, non-overlapping windows of items from the source Publisher + * whenever {@code closingSelector} emits an item + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.ERROR) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable> window(Callable> boundaryIndicatorSupplier, int bufferSize) { + ObjectHelper.requireNonNull(boundaryIndicatorSupplier, "boundaryIndicatorSupplier is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new FlowableWindowBoundarySupplier(this, boundaryIndicatorSupplier, bufferSize)); + } + + /** + * Merges the specified Publisher into this Publisher sequence by using the {@code resultSelector} + * function only when the source Publisher (this instance) emits an item. + *

+ * + * + *

+ *
Backpressure:
+ *
The operator is a pass-through for backpressure: the backpressure support + * depends on the upstream and downstream's backpressure behavior. The other Publisher + * is consumed in an unbounded fashion.
+ *
Scheduler:
+ *
This operator, by default, doesn't run any particular {@link Scheduler}.
+ *
+ * + * @param the element type of the other Publisher + * @param the result type of the combination + * @param other + * the other Publisher + * @param combiner + * the function to call when this Publisher emits an item and the other Publisher has already + * emitted an item, to generate the item to be emitted by the resulting Publisher + * @return a Flowable that merges the specified Publisher into this Publisher by using the + * {@code resultSelector} function only when the source Publisher sequence (this instance) emits an + * item + * @since 2.0 + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable withLatestFrom(Publisher other, + BiFunction combiner) { + ObjectHelper.requireNonNull(other, "other is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + + return RxJavaPlugins.onAssembly(new FlowableWithLatestFrom(this, combiner, other)); + } + + /** + * Combines the value emission from this Publisher with the latest emissions from the + * other Publishers via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this Publisher emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + * + *

+ *
Backpressure:
+ *
This operator is a pass-through for backpressure behavior between the source {@code Publisher} + * and the downstream Subscriber. The other {@code Publisher}s are consumed in an unbounded manner.
+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first other source's value type + * @param the second other source's value type + * @param the result value type + * @param source1 the first other Publisher + * @param source2 the second other Publisher + * @param combiner the function called with an array of values from each participating Publisher + * @return the new Publisher instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable withLatestFrom(Publisher source1, Publisher source2, + Function3 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + Function f = Functions.toFunction(combiner); + return withLatestFrom(new Publisher[] { source1, source2 }, f); + } + + /** + * Combines the value emission from this Publisher with the latest emissions from the + * other Publishers via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this Publisher emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + * + *

+ *
Backpressure:
+ *
This operator is a pass-through for backpressure behavior between the source {@code Publisher} + * and the downstream Subscriber. The other {@code Publisher}s are consumed in an unbounded manner.
+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first other source's value type + * @param the second other source's value type + * @param the third other source's value type + * @param the result value type + * @param source1 the first other Publisher + * @param source2 the second other Publisher + * @param source3 the third other Publisher + * @param combiner the function called with an array of values from each participating Publisher + * @return the new Publisher instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable withLatestFrom( + Publisher source1, Publisher source2, + Publisher source3, + Function4 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + Function f = Functions.toFunction(combiner); + return withLatestFrom(new Publisher[] { source1, source2, source3 }, f); + } + + /** + * Combines the value emission from this Publisher with the latest emissions from the + * other Publishers via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this Publisher emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + * + *

+ *
Backpressure:
+ *
This operator is a pass-through for backpressure behavior between the source {@code Publisher} + * and the downstream Subscriber. The other {@code Publisher}s are consumed in an unbounded manner.
+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first other source's value type + * @param the second other source's value type + * @param the third other source's value type + * @param the fourth other source's value type + * @param the result value type + * @param source1 the first other Publisher + * @param source2 the second other Publisher + * @param source3 the third other Publisher + * @param source4 the fourth other Publisher + * @param combiner the function called with an array of values from each participating Publisher + * @return the new Publisher instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable withLatestFrom( + Publisher source1, Publisher source2, + Publisher source3, Publisher source4, + Function5 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + Function f = Functions.toFunction(combiner); + return withLatestFrom(new Publisher[] { source1, source2, source3, source4 }, f); + } + + /** + * Combines the value emission from this Publisher with the latest emissions from the + * other Publishers via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this Publisher emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + * + *

+ *
Backpressure:
+ *
This operator is a pass-through for backpressure behavior between the source {@code Publisher} + * and the downstream Subscriber. The other {@code Publisher}s are consumed in an unbounded manner.
+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param others the array of other sources + * @param combiner the function called with an array of values from each participating Publisher + * @return the new Publisher instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable withLatestFrom(Publisher[] others, Function combiner) { + ObjectHelper.requireNonNull(others, "others is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + return RxJavaPlugins.onAssembly(new FlowableWithLatestFromMany(this, others, combiner)); + } + + /** + * Combines the value emission from this Publisher with the latest emissions from the + * other Publishers via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this Publisher emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + * + *

+ *
Backpressure:
+ *
This operator is a pass-through for backpressure behavior between the source {@code Publisher} + * and the downstream Subscriber. The other {@code Publisher}s are consumed in an unbounded manner.
+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param others the iterable of other sources + * @param combiner the function called with an array of values from each participating Publisher + * @return the new Publisher instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable withLatestFrom(Iterable> others, Function combiner) { + ObjectHelper.requireNonNull(others, "others is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + return RxJavaPlugins.onAssembly(new FlowableWithLatestFromMany(this, others, combiner)); + } + + /** + * Returns a Flowable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source Publisher and a specified Iterable sequence. + *

+ * + *

+ * Note that the {@code other} Iterable is evaluated as items are observed from the source Publisher; it is + * not pre-consumed. This allows you to zip infinite streams on either side. + *

+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items in the {@code other} Iterable + * @param + * the type of items emitted by the resulting Publisher + * @param other + * the Iterable sequence + * @param zipper + * a function that combines the pairs of items from the Publisher and the Iterable to generate + * the items to be emitted by the resulting Publisher + * @return a Flowable that pairs up values from the source Publisher and the {@code other} Iterable + * sequence and emits the results of {@code zipFunction} applied to these pairs + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable zipWith(Iterable other, BiFunction zipper) { + ObjectHelper.requireNonNull(other, "other is null"); + ObjectHelper.requireNonNull(zipper, "zipper is null"); + return RxJavaPlugins.onAssembly(new FlowableZipIterable(this, other, zipper)); + } + + /** + * Returns a Flowable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source Publisher and another specified Publisher. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

range(1, 5).doOnComplete(action1).zipWith(range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the {@code other} Publisher + * @param + * the type of items emitted by the resulting Publisher + * @param other + * the other Publisher + * @param zipper + * a function that combines the pairs of items from the two Publishers to generate the items to + * be emitted by the resulting Publisher + * @return a Flowable that pairs up values from the source Publisher and the {@code other} Publisher + * and emits the results of {@code zipFunction} applied to these pairs + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable zipWith(Publisher other, BiFunction zipper) { + ObjectHelper.requireNonNull(other, "other is null"); + return zip(this, other, zipper); + } + + /** + * Returns a Flowable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source Publisher and another specified Publisher. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

range(1, 5).doOnComplete(action1).zipWith(range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the {@code other} Publisher + * @param + * the type of items emitted by the resulting Publisher + * @param other + * the other Publisher + * @param zipper + * a function that combines the pairs of items from the two Publishers to generate the items to + * be emitted by the resulting Publisher + * @param delayError + * if true, errors from the current Flowable or the other Publisher is delayed until both terminate + * @return a Flowable that pairs up values from the source Publisher and the {@code other} Publisher + * and emits the results of {@code zipFunction} applied to these pairs + * @see ReactiveX operators documentation: Zip + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable zipWith(Publisher other, + BiFunction zipper, boolean delayError) { + return zip(this, other, zipper, delayError); + } + + /** + * Returns a Flowable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source Publisher and another specified Publisher. + *

+ * The operator subscribes to its sources in the order they are specified and completes eagerly if + * one of the sources is shorter than the rest while canceling the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will cancel B immediately. For example: + *

range(1, 5).doOnComplete(action1).zipWith(range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion + * or cancellation. + *

+ * + *

+ *
Backpressure:
+ *
The operator expects backpressure from the sources and honors backpressure from the downstream. + * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use + * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the {@code other} Publisher + * @param + * the type of items emitted by the resulting Publisher + * @param other + * the other Publisher + * @param zipper + * a function that combines the pairs of items from the two Publishers to generate the items to + * be emitted by the resulting Publisher + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @param delayError + * if true, errors from the current Flowable or the other Publisher is delayed until both terminate + * @return a Flowable that pairs up values from the source Publisher and the {@code other} Publisher + * and emits the results of {@code zipFunction} applied to these pairs + * @see ReactiveX operators documentation: Zip + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable zipWith(Publisher other, + BiFunction zipper, boolean delayError, int bufferSize) { + return zip(this, other, zipper, delayError, bufferSize); + } + + // ------------------------------------------------------------------------- + // Fluent test support, super handy and reduces test preparation boilerplate + // ------------------------------------------------------------------------- + /** + * Creates a TestSubscriber that requests Long.MAX_VALUE and subscribes + * it to this Flowable. + *
+ *
Backpressure:
+ *
The returned TestSubscriber consumes this Flowable in an unbounded fashion.
+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new TestSubscriber instance + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @SchedulerSupport(SchedulerSupport.NONE) + public final TestSubscriber test() { // NoPMD + TestSubscriber ts = new TestSubscriber(); + subscribe(ts); + return ts; + } + + /** + * Creates a TestSubscriber with the given initial request amount and subscribes + * it to this Flowable. + *
+ *
Backpressure:
+ *
The returned TestSubscriber requests the given {@code initialRequest} amount upfront.
+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param initialRequest the initial request amount, positive + * @return the new TestSubscriber instance + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final TestSubscriber test(long initialRequest) { // NoPMD + TestSubscriber ts = new TestSubscriber(initialRequest); + subscribe(ts); + return ts; + } + + /** + * Creates a TestSubscriber with the given initial request amount, + * optionally cancels it before the subscription and subscribes + * it to this Flowable. + *
+ *
Backpressure:
+ *
The returned TestSubscriber requests the given {@code initialRequest} amount upfront.
+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param initialRequest the initial request amount, positive + * @param cancel should the TestSubscriber be canceled before the subscription? + * @return the new TestSubscriber instance + * @since 2.0 + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public final TestSubscriber test(long initialRequest, boolean cancel) { // NoPMD + TestSubscriber ts = new TestSubscriber(initialRequest); + if (cancel) { + ts.cancel(); + } + subscribe(ts); + return ts; + } + +} diff --git a/src/main/java/io/reactivex/FlowableConverter.java b/src/main/java/io/reactivex/FlowableConverter.java new file mode 100755 index 0000000..cf9176b --- /dev/null +++ b/src/main/java/io/reactivex/FlowableConverter.java @@ -0,0 +1,35 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Flowable#as} operator to turn a Flowable into another + * value fluently. + *

History: 2.1.7 - experimental + * @param the upstream type + * @param the output type + * @since 2.2 + */ +public interface FlowableConverter { + /** + * Applies a function to the upstream Flowable and returns a converted value of type {@code R}. + * + * @param upstream the upstream Flowable instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Flowable upstream); +} diff --git a/src/main/java/io/reactivex/FlowableEmitter.java b/src/main/java/io/reactivex/FlowableEmitter.java new file mode 100755 index 0000000..1cd91e1 --- /dev/null +++ b/src/main/java/io/reactivex/FlowableEmitter.java @@ -0,0 +1,104 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Cancellable; + +/** + * Abstraction over a Reactive Streams {@link org.reactivestreams.Subscriber} that allows associating + * a resource with it and exposes the current number of downstream + * requested amount. + *

+ * The {@link #onNext(Object)}, {@link #onError(Throwable)}, {@link #tryOnError(Throwable)} + * and {@link #onComplete()} methods should be called in a sequential manner, just like + * the {@link org.reactivestreams.Subscriber Subscriber}'s methods. + * Use the {@code FlowableEmitter} the {@link #serialize()} method returns instead of the original + * {@code FlowableEmitter} instance provided by the generator routine if you want to ensure this. + * The other methods are thread-safe. + *

+ * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls {@link #onError(Throwable)}, + * {@link #onComplete()} or when {@link #tryOnError(Throwable)} succeeds. + *

+ * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + *

+ * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * + * @param the value type to emit + */ +public interface FlowableEmitter extends Emitter { + + /** + * Sets a Disposable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param d the disposable, null is allowed + */ + void setDisposable(@Nullable Disposable d); + + /** + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param c the cancellable resource, null is allowed + */ + void setCancellable(@Nullable Cancellable c); + + /** + * The current outstanding request amount. + *

This method is thread-safe. + * @return the current outstanding request amount + */ + long requested(); + + /** + * Returns true if the downstream cancelled the sequence or the + * emitter was terminated via {@link #onError(Throwable)}, {@link #onComplete} or a + * successful {@link #tryOnError(Throwable)}. + *

This method is thread-safe. + * @return true if the downstream cancelled the sequence or the emitter was terminated + */ + boolean isCancelled(); + + /** + * Ensures that calls to onNext, onError and onComplete are properly serialized. + * @return the serialized FlowableEmitter + */ + @NonNull + FlowableEmitter serialize(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

+ * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); +} diff --git a/src/main/java/io/reactivex/FlowableOnSubscribe.java b/src/main/java/io/reactivex/FlowableOnSubscribe.java new file mode 100755 index 0000000..b5b7b83 --- /dev/null +++ b/src/main/java/io/reactivex/FlowableOnSubscribe.java @@ -0,0 +1,33 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * A functional interface that has a {@code subscribe()} method that receives + * an instance of a {@link FlowableEmitter} instance that allows pushing + * events in a backpressure-safe and cancellation-safe manner. + * + * @param the value type pushed + */ +public interface FlowableOnSubscribe { + + /** + * Called for each Subscriber that subscribes. + * @param emitter the safe emitter instance, never null + * @throws Exception on error + */ + void subscribe(@NonNull FlowableEmitter emitter) throws Exception; +} + diff --git a/src/main/java/io/reactivex/FlowableOperator.java b/src/main/java/io/reactivex/FlowableOperator.java new file mode 100755 index 0000000..b81a0b6 --- /dev/null +++ b/src/main/java/io/reactivex/FlowableOperator.java @@ -0,0 +1,34 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import org.reactivestreams.Subscriber; + +/** + * Interface to map/wrap a downstream subscriber to an upstream subscriber. + * + * @param the value type of the downstream + * @param the value type of the upstream + */ +public interface FlowableOperator { + /** + * Applies a function to the child Subscriber and returns a new parent Subscriber. + * @param subscriber the child Subscriber instance + * @return the parent Subscriber instance + * @throws Exception on failure + */ + @NonNull + Subscriber apply(@NonNull Subscriber subscriber) throws Exception; +} diff --git a/src/main/java/io/reactivex/FlowableSubscriber.java b/src/main/java/io/reactivex/FlowableSubscriber.java new file mode 100755 index 0000000..e263640 --- /dev/null +++ b/src/main/java/io/reactivex/FlowableSubscriber.java @@ -0,0 +1,40 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import org.reactivestreams.*; + +/** + * Represents a Reactive Streams inspired Subscriber that is RxJava 2 only + * and weakens rules §1.3 and §3.9 of the specification for gaining performance. + * + *

History: 2.0.7 - experimental; 2.1 - beta + * @param the value type + * @since 2.2 + */ +public interface FlowableSubscriber extends Subscriber { + + /** + * Implementors of this method should make sure everything that needs + * to be visible in {@link #onNext(Object)} is established before + * calling {@link Subscription#request(long)}. In practice this means + * no initialization should happen after the {@code request()} call and + * additional behavior is thread safe in respect to {@code onNext}. + * + * {@inheritDoc} + */ + @Override + void onSubscribe(@NonNull Subscription s); +} diff --git a/src/main/java/io/reactivex/FlowableTransformer.java b/src/main/java/io/reactivex/FlowableTransformer.java new file mode 100755 index 0000000..78a0f43 --- /dev/null +++ b/src/main/java/io/reactivex/FlowableTransformer.java @@ -0,0 +1,34 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import org.reactivestreams.Publisher; + +/** + * Interface to compose Flowables. + * + * @param the upstream value type + * @param the downstream value type + */ +public interface FlowableTransformer { + /** + * Applies a function to the upstream Flowable and returns a Publisher with + * optionally different element type. + * @param upstream the upstream Flowable instance + * @return the transformed Publisher instance + */ + @NonNull + Publisher apply(@NonNull Flowable upstream); +} diff --git a/src/main/java/io/reactivex/Maybe.java b/src/main/java/io/reactivex/Maybe.java new file mode 100755 index 0000000..47c7b4d --- /dev/null +++ b/src/main/java/io/reactivex/Maybe.java @@ -0,0 +1,4767 @@ +/** + * 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; + +import java.util.NoSuchElementException; +import java.util.concurrent.*; + +import org.reactivestreams.*; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.observers.BlockingMultiObserver; +import io.reactivex.internal.operators.flowable.*; +import io.reactivex.internal.operators.maybe.*; +import io.reactivex.internal.operators.mixed.*; +import io.reactivex.internal.util.*; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; + +/** + * The {@code Maybe} class represents a deferred computation and emission of a single value, no value at all or an exception. + *

+ * The {@code Maybe} class implements the {@link MaybeSource} base interface and the default consumer + * type it interacts with is the {@link MaybeObserver} via the {@link #subscribe(MaybeObserver)} method. + *

+ * The {@code Maybe} operates with the following sequential protocol: + *


+ *     onSubscribe (onSuccess | onError | onComplete)?
+ * 
+ *

+ * Note that {@code onSuccess}, {@code onError} and {@code onComplete} are mutually exclusive events; unlike {@code Observable}, + * {@code onSuccess} is never followed by {@code onError} or {@code onComplete}. + *

+ * Like {@link Observable}, a running {@code Maybe} can be stopped through the {@link Disposable} instance + * provided to consumers through {@link MaybeObserver#onSubscribe}. + *

+ * Like an {@code Observable}, a {@code Maybe} is lazy, can be either "hot" or "cold", synchronous or + * asynchronous. {@code Maybe} instances returned by the methods of this class are cold + * and there is a standard hot implementation in the form of a subject: + * {@link io.reactivex.subjects.MaybeSubject MaybeSubject}. + *

+ * The documentation for this class makes use of marble diagrams. The following legend explains these diagrams: + *

+ * + *

+ * See {@link Flowable} or {@link Observable} for the + * implementation of the Reactive Pattern for a stream or vector of values. + *

+ * Example: + *


+ * Disposable d = Maybe.just("Hello World")
+ *    .delay(10, TimeUnit.SECONDS, Schedulers.io())
+ *    .subscribeWith(new DisposableMaybeObserver<String>() {
+ *        @Override
+ *        public void onStart() {
+ *            System.out.println("Started");
+ *        }
+ *
+ *        @Override
+ *        public void onSuccess(String value) {
+ *            System.out.println("Success: " + value);
+ *        }
+ *
+ *        @Override
+ *        public void onError(Throwable error) {
+ *            error.printStackTrace();
+ *        }
+ *
+ *        @Override
+ *        public void onComplete() {
+ *            System.out.println("Done!");
+ *        }
+ *    });
+ *
+ * Thread.sleep(5000);
+ *
+ * d.dispose();
+ * 
+ *

+ * Note that by design, subscriptions via {@link #subscribe(MaybeObserver)} can't be disposed + * from the outside (hence the + * {@code void} return of the {@link #subscribe(MaybeObserver)} method) and it is the + * responsibility of the implementor of the {@code MaybeObserver} to allow this to happen. + * RxJava supports such usage with the standard + * {@link io.reactivex.observers.DisposableMaybeObserver DisposableMaybeObserver} instance. + * For convenience, the {@link #subscribeWith(MaybeObserver)} method is provided as well to + * allow working with a {@code MaybeObserver} (or subclass) instance to be applied with in + * a fluent manner (such as in the example above). + * + * @param the value type + * @since 2.0 + * @see io.reactivex.observers.DisposableMaybeObserver + */ +public abstract class Maybe implements MaybeSource { + + /** + * Runs multiple MaybeSources and signals the events of the first one that signals (disposing + * the rest). + *

+ * + *

+ *
Scheduler:
+ *
{@code amb} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the Iterable sequence of sources. A subscription to each source will + * occur in the same order as in the Iterable. + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe amb(final Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new MaybeAmb(null, sources)); + } + + /** + * Runs multiple MaybeSources and signals the events of the first one that signals (disposing + * the rest). + *

+ * + *

+ *
Scheduler:
+ *
{@code ambArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the array of sources. A subscription to each source will + * occur in the same order as in the array. + * @return the new Maybe instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Maybe ambArray(final MaybeSource... sources) { + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + return wrap((MaybeSource)sources[0]); + } + return RxJavaPlugins.onAssembly(new MaybeAmb(sources, null)); + } + + /** + * Concatenate the single values, in a non-overlapping fashion, of the MaybeSource sources provided by + * an Iterable sequence. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the Iterable sequence of MaybeSource instances + * @return the new Flowable instance + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concat(Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new MaybeConcatIterable(sources)); + } + + /** + * Returns a Flowable that emits the items emitted by two MaybeSources, one after the other. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common value type + * @param source1 + * a MaybeSource to be concatenated + * @param source2 + * a MaybeSource to be concatenated + * @return a Flowable that emits items emitted by the two source MaybeSources, one after the other. + * @see ReactiveX operators documentation: Concat + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable concat(MaybeSource source1, MaybeSource source2) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return concatArray(source1, source2); + } + + /** + * Returns a Flowable that emits the items emitted by three MaybeSources, one after the other. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common value type + * @param source1 + * a MaybeSource to be concatenated + * @param source2 + * a MaybeSource to be concatenated + * @param source3 + * a MaybeSource to be concatenated + * @return a Flowable that emits items emitted by the three source MaybeSources, one after the other. + * @see ReactiveX operators documentation: Concat + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable concat( + MaybeSource source1, MaybeSource source2, MaybeSource source3) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return concatArray(source1, source2, source3); + } + + /** + * Returns a Flowable that emits the items emitted by four MaybeSources, one after the other. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common value type + * @param source1 + * a MaybeSource to be concatenated + * @param source2 + * a MaybeSource to be concatenated + * @param source3 + * a MaybeSource to be concatenated + * @param source4 + * a MaybeSource to be concatenated + * @return a Flowable that emits items emitted by the four source MaybeSources, one after the other. + * @see ReactiveX operators documentation: Concat + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable concat( + MaybeSource source1, MaybeSource source2, MaybeSource source3, MaybeSource source4) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return concatArray(source1, source2, source3, source4); + } + + /** + * Concatenate the single values, in a non-overlapping fashion, of the MaybeSource sources provided by + * a Publisher sequence. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer and + * expects the {@code Publisher} to honor backpressure as well. If the sources {@code Publisher} + * violates this, a {@link io.reactivex.exceptions.MissingBackpressureException} is signalled.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the Publisher of MaybeSource instances + * @return the new Flowable instance + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concat(Publisher> sources) { + return concat(sources, 2); + } + + /** + * Concatenate the single values, in a non-overlapping fashion, of the MaybeSource sources provided by + * a Publisher sequence. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer and + * expects the {@code Publisher} to honor backpressure as well. If the sources {@code Publisher} + * violates this, a {@link io.reactivex.exceptions.MissingBackpressureException} is signalled.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the Publisher of MaybeSource instances + * @param prefetch the number of MaybeSources to prefetch from the Publisher + * @return the new Flowable instance + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Flowable concat(Publisher> sources, int prefetch) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapPublisher(sources, MaybeToPublisher.instance(), prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Concatenate the single values, in a non-overlapping fashion, of the MaybeSource sources in the array. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concatArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the array of MaybeSource instances + * @return the new Flowable instance + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable concatArray(MaybeSource... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + if (sources.length == 0) { + return Flowable.empty(); + } + if (sources.length == 1) { + return RxJavaPlugins.onAssembly(new MaybeToFlowable((MaybeSource)sources[0])); + } + return RxJavaPlugins.onAssembly(new MaybeConcatArray(sources)); + } + + /** + * Concatenates a variable number of MaybeSource sources and delays errors from any of them + * till all terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code concatArrayDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the array of sources + * @param the common base value type + * @return the new Flowable instance + * @throws NullPointerException if sources is null + */ + @SuppressWarnings("unchecked") + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatArrayDelayError(MaybeSource... sources) { + if (sources.length == 0) { + return Flowable.empty(); + } else + if (sources.length == 1) { + return RxJavaPlugins.onAssembly(new MaybeToFlowable((MaybeSource)sources[0])); + } + return RxJavaPlugins.onAssembly(new MaybeConcatArrayDelayError(sources)); + } + + /** + * Concatenates a sequence of MaybeSource eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source MaybeSources. The operator buffers the value emitted by these MaybeSources and then drains them + * in order, each one after the previous one completes. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of MaybeSources that need to be eagerly concatenated + * @return the new Flowable instance with the specified concatenation behavior + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatArrayEager(MaybeSource... sources) { + return Flowable.fromArray(sources).concatMapEager((Function)MaybeToPublisher.instance()); + } + + /** + * Concatenates the Iterable sequence of MaybeSources into a single sequence by subscribing to each MaybeSource, + * one after the other, one at a time and delays any errors till the all inner MaybeSources terminate. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources the Iterable sequence of MaybeSources + * @return the new Flowable with the concatenating behavior + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatDelayError(Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return Flowable.fromIterable(sources).concatMapDelayError((Function)MaybeToPublisher.instance()); + } + + /** + * Concatenates the Publisher sequence of Publishers into a single sequence by subscribing to each inner Publisher, + * one after the other, one at a time and delays any errors till the all inner and the outer Publishers terminate. + *

+ * + *

+ *
Backpressure:
+ *
{@code concatDelayError} fully supports backpressure.
+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources the Publisher sequence of Publishers + * @return the new Publisher with the concatenating behavior + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatDelayError(Publisher> sources) { + return Flowable.fromPublisher(sources).concatMapDelayError((Function)MaybeToPublisher.instance()); + } + + /** + * Concatenates a sequence of MaybeSources eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source MaybeSources. The operator buffers the values emitted by these MaybeSources and then drains them + * in order, each one after the previous one completes. + *

+ * + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of MaybeSource that need to be eagerly concatenated + * @return the new Flowable instance with the specified concatenation behavior + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatEager(Iterable> sources) { + return Flowable.fromIterable(sources).concatMapEager((Function)MaybeToPublisher.instance()); + } + + /** + * Concatenates a Publisher sequence of MaybeSources eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * emitted source Publishers as they are observed. The operator buffers the values emitted by these + * Publishers and then drains them in order, each one after the previous one completes. + *

+ * + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream and the outer Publisher is + * expected to support backpressure. Violating this assumption, the operator will + * signal {@link io.reactivex.exceptions.MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of Publishers that need to be eagerly concatenated + * @return the new Publisher instance with the specified concatenation behavior + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatEager(Publisher> sources) { + return Flowable.fromPublisher(sources).concatMapEager((Function)MaybeToPublisher.instance()); + } + + /** + * Provides an API (via a cold Maybe) that bridges the reactive world with the callback-style world. + *

+ * Example: + *


+     * Maybe.<Event>create(emitter -> {
+     *     Callback listener = new Callback() {
+     *         @Override
+     *         public void onEvent(Event e) {
+     *             if (e.isNothing()) {
+     *                 emitter.onComplete();
+     *             } else {
+     *                 emitter.onSuccess(e);
+     *             }
+     *         }
+     *
+     *         @Override
+     *         public void onFailure(Exception e) {
+     *             emitter.onError(e);
+     *         }
+     *     };
+     *
+     *     AutoCloseable c = api.someMethod(listener);
+     *
+     *     emitter.setCancellable(c::close);
+     *
+     * });
+     * 
+ *
+ *
Scheduler:
+ *
{@code create} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param onSubscribe the emitter that is called when a MaybeObserver subscribes to the returned {@code Maybe} + * @return the new Maybe instance + * @see MaybeOnSubscribe + * @see Cancellable + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe create(MaybeOnSubscribe onSubscribe) { + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + return RxJavaPlugins.onAssembly(new MaybeCreate(onSubscribe)); + } + + /** + * Calls a Callable for each individual MaybeObserver to return the actual MaybeSource source to + * be subscribed to. + *
+ *
Scheduler:
+ *
{@code defer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param maybeSupplier the Callable that is called for each individual MaybeObserver and + * returns a MaybeSource instance to subscribe to + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe defer(final Callable> maybeSupplier) { + ObjectHelper.requireNonNull(maybeSupplier, "maybeSupplier is null"); + return RxJavaPlugins.onAssembly(new MaybeDefer(maybeSupplier)); + } + + /** + * Returns a (singleton) Maybe instance that calls {@link MaybeObserver#onComplete onComplete} + * immediately. + *

+ * + *

+ *
Scheduler:
+ *
{@code empty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @return the new Maybe instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Maybe empty() { + return RxJavaPlugins.onAssembly((Maybe)MaybeEmpty.INSTANCE); + } + + /** + * Returns a Maybe that invokes a subscriber's {@link MaybeObserver#onError onError} method when the + * subscriber subscribes to it. + *

+ * + *

+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param exception + * the particular Throwable to pass to {@link MaybeObserver#onError onError} + * @param + * the type of the item (ostensibly) emitted by the Maybe + * @return a Maybe that invokes the subscriber's {@link MaybeObserver#onError onError} method when + * the subscriber subscribes to it + * @see ReactiveX operators documentation: Throw + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe error(Throwable exception) { + ObjectHelper.requireNonNull(exception, "exception is null"); + return RxJavaPlugins.onAssembly(new MaybeError(exception)); + } + + /** + * Returns a Maybe that invokes a {@link MaybeObserver}'s {@link MaybeObserver#onError onError} method when the + * MaybeObserver subscribes to it. + *

+ * + *

+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param supplier + * a Callable factory to return a Throwable for each individual MaybeObserver + * @param + * the type of the items (ostensibly) emitted by the Maybe + * @return a Maybe that invokes the {@link MaybeObserver}'s {@link MaybeObserver#onError onError} method when + * the MaybeObserver subscribes to it + * @see ReactiveX operators documentation: Throw + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe error(Callable supplier) { + ObjectHelper.requireNonNull(supplier, "errorSupplier is null"); + return RxJavaPlugins.onAssembly(new MaybeErrorCallable(supplier)); + } + + /** + * Returns a Maybe instance that runs the given Action for each subscriber and + * emits either its exception or simply completes. + *
+ *
Scheduler:
+ *
{@code fromAction} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Action} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link MaybeObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Maybe} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + *
+ *
+ * @param the target type + * @param run the runnable to run for each subscriber + * @return the new Maybe instance + * @throws NullPointerException if run is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe fromAction(final Action run) { + ObjectHelper.requireNonNull(run, "run is null"); + return RxJavaPlugins.onAssembly(new MaybeFromAction(run)); + } + + /** + * Wraps a CompletableSource into a Maybe. + * + *
+ *
Scheduler:
+ *
{@code fromCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the target type + * @param completableSource the CompletableSource to convert from + * @return the new Maybe instance + * @throws NullPointerException if completable is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe fromCompletable(CompletableSource completableSource) { + ObjectHelper.requireNonNull(completableSource, "completableSource is null"); + return RxJavaPlugins.onAssembly(new MaybeFromCompletable(completableSource)); + } + + /** + * Wraps a SingleSource into a Maybe. + * + *
+ *
Scheduler:
+ *
{@code fromSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the target type + * @param singleSource the SingleSource to convert from + * @return the new Maybe instance + * @throws NullPointerException if single is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe fromSingle(SingleSource singleSource) { + ObjectHelper.requireNonNull(singleSource, "singleSource is null"); + return RxJavaPlugins.onAssembly(new MaybeFromSingle(singleSource)); + } + + /** + * Returns a {@link Maybe} that invokes the given {@link Callable} for each individual {@link MaybeObserver} that + * subscribes and emits the resulting non-null item via {@code onSuccess} while + * considering a {@code null} result from the {@code Callable} as indication for valueless completion + * via {@code onComplete}. + *

+ * This operator allows you to defer the execution of the given {@code Callable} until a {@code MaybeObserver} + * subscribes to the returned {@link Maybe}. In other terms, this source operator evaluates the given + * {@code Callable} "lazily". + *

+ * Note that the {@code null} handling of this operator differs from the similar source operators in the other + * {@link io.reactivex base reactive classes}. Those operators signal a {@code NullPointerException} if the value returned by their + * {@code Callable} is {@code null} while this {@code fromCallable} considers it to indicate the + * returned {@code Maybe} is empty. + *

+ *
Scheduler:
+ *
{@code fromCallable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
Any non-fatal exception thrown by {@link Callable#call()} will be forwarded to {@code onError}, + * except if the {@code MaybeObserver} disposed the subscription in the meantime. In this latter case, + * the exception is forwarded to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} wrapped into a + * {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + * Fatal exceptions are rethrown and usually will end up in the executing thread's + * {@link Thread.UncaughtExceptionHandler#uncaughtException(Thread, Throwable)} handler.
+ *
+ * + * @param callable + * a {@link Callable} instance whose execution should be deferred and performed for each individual + * {@code MaybeObserver} that subscribes to the returned {@link Maybe}. + * @param + * the type of the item emitted by the {@link Maybe}. + * @return a new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe fromCallable(@NonNull final Callable callable) { + ObjectHelper.requireNonNull(callable, "callable is null"); + return RxJavaPlugins.onAssembly(new MaybeFromCallable(callable)); + } + + /** + * Converts a {@link Future} into a Maybe, treating a null result as an indication of emptiness. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a Maybe that emits the + * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} + * method. + *

+ * Important note: This Maybe is blocking; you cannot dispose it. + *

+ * Unlike 1.x, disposing the Maybe won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureMaybe.doOnDispose(() -> future.cancel(true));}. + *

+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting Maybe + * @return a Maybe that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe fromFuture(Future future) { + ObjectHelper.requireNonNull(future, "future is null"); + return RxJavaPlugins.onAssembly(new MaybeFromFuture(future, 0L, null)); + } + + /** + * Converts a {@link Future} into a Maybe, with a timeout on the Future. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a Maybe that emits the + * return value of the {@link Future#get} method of that object, by passing the object into the {@code fromFuture} + * method. + *

+ * Unlike 1.x, disposing the Maybe won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureMaybe.doOnCancel(() -> future.cancel(true));}. + *

+ * Important note: This Maybe is blocking on the thread it gets subscribed on; you cannot dispose it. + *

+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param timeout + * the maximum time to wait before calling {@code get} + * @param unit + * the {@link TimeUnit} of the {@code timeout} argument + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting Maybe + * @return a Maybe that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe fromFuture(Future future, long timeout, TimeUnit unit) { + ObjectHelper.requireNonNull(future, "future is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + return RxJavaPlugins.onAssembly(new MaybeFromFuture(future, timeout, unit)); + } + + /** + * Returns a Maybe instance that runs the given Action for each subscriber and + * emits either its exception or simply completes. + *
+ *
Scheduler:
+ *
{@code fromRunnable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the target type + * @param run the runnable to run for each subscriber + * @return the new Maybe instance + * @throws NullPointerException if run is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe fromRunnable(final Runnable run) { + ObjectHelper.requireNonNull(run, "run is null"); + return RxJavaPlugins.onAssembly(new MaybeFromRunnable(run)); + } + + /** + * Returns a {@code Maybe} that emits a specified item. + *

+ * + *

+ * To convert any object into a {@code Maybe} that emits that object, pass that object into the + * {@code just} method. + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the item to emit + * @param + * the type of that item + * @return a {@code Maybe} that emits {@code item} + * @see ReactiveX operators documentation: Just + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe just(T item) { + ObjectHelper.requireNonNull(item, "item is null"); + return RxJavaPlugins.onAssembly(new MaybeJust(item)); + } + + /** + * Merges an Iterable sequence of MaybeSource instances into a single Flowable sequence, + * running all MaybeSources at once. + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + *
+ *
+ * @param the common and resulting value type + * @param sources the Iterable sequence of MaybeSource sources + * @return the new Flowable instance + * @see #mergeDelayError(Iterable) + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Iterable> sources) { + return merge(Flowable.fromIterable(sources)); + } + + /** + * Merges a Flowable sequence of MaybeSource instances into a single Flowable sequence, + * running all MaybeSources at once. + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + *
+ *
+ * @param the common and resulting value type + * @param sources the Flowable sequence of MaybeSource sources + * @return the new Flowable instance + * @see #mergeDelayError(Publisher) + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Publisher> sources) { + return merge(sources, Integer.MAX_VALUE); + } + + /** + * Merges a Flowable sequence of MaybeSource instances into a single Flowable sequence, + * running at most maxConcurrency MaybeSources at once. + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher, int)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + *
+ *
+ * @param the common and resulting value type + * @param sources the Flowable sequence of MaybeSource sources + * @param maxConcurrency the maximum number of concurrently running MaybeSources + * @return the new Flowable instance + * @see #mergeDelayError(Publisher, int) + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Flowable merge(Publisher> sources, int maxConcurrency) { + ObjectHelper.requireNonNull(sources, "source is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + return RxJavaPlugins.onAssembly(new FlowableFlatMapPublisher(sources, MaybeToPublisher.instance(), false, maxConcurrency, 1)); + } + + /** + * Flattens a {@code MaybeSource} that emits a {@code MaybeSource} into a single {@code MaybeSource} that emits the item + * emitted by the nested {@code MaybeSource}, without any transformation. + *

+ * + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
The resulting {@code Maybe} emits the outer source's or the inner {@code MaybeSource}'s {@code Throwable} as is. + * Unlike the other {@code merge()} operators, this operator won't and can't produce a {@code CompositeException} because there is + * only one possibility for the outer or the inner {@code MaybeSource} to emit an {@code onError} signal. + * Therefore, there is no need for a {@code mergeDelayError(MaybeSource>)} operator. + *
+ *
+ * + * @param the value type of the sources and the output + * @param source + * a {@code MaybeSource} that emits a {@code MaybeSource} + * @return a {@code Maybe} that emits the item that is the result of flattening the {@code MaybeSource} emitted + * by {@code source} + * @see ReactiveX operators documentation: Merge + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Maybe merge(MaybeSource> source) { + ObjectHelper.requireNonNull(source, "source is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatten(source, Functions.identity())); + } + + /** + * Flattens two MaybeSources into a single Flowable, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple MaybeSources so that they appear as a single Flowable, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(MaybeSource, MaybeSource)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common value type + * @param source1 + * a MaybeSource to be merged + * @param source2 + * a MaybeSource to be merged + * @return a Flowable that emits all of the items emitted by the source MaybeSources + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(MaybeSource, MaybeSource) + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable merge( + MaybeSource source1, MaybeSource source2 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return mergeArray(source1, source2); + } + + /** + * Flattens three MaybeSources into a single Flowable, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple MaybeSources so that they appear as a single Flowable, by using + * the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(MaybeSource, MaybeSource, MaybeSource)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common value type + * @param source1 + * a MaybeSource to be merged + * @param source2 + * a MaybeSource to be merged + * @param source3 + * a MaybeSource to be merged + * @return a Flowable that emits all of the items emitted by the source MaybeSources + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(MaybeSource, MaybeSource, MaybeSource) + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable merge( + MaybeSource source1, MaybeSource source2, + MaybeSource source3 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return mergeArray(source1, source2, source3); + } + + /** + * Flattens four MaybeSources into a single Flowable, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple MaybeSources so that they appear as a single Flowable, by using + * the {@code merge} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(MaybeSource, MaybeSource, MaybeSource, MaybeSource)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common value type + * @param source1 + * a MaybeSource to be merged + * @param source2 + * a MaybeSource to be merged + * @param source3 + * a MaybeSource to be merged + * @param source4 + * a MaybeSource to be merged + * @return a Flowable that emits all of the items emitted by the source MaybeSources + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(MaybeSource, MaybeSource, MaybeSource, MaybeSource) + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable merge( + MaybeSource source1, MaybeSource source2, + MaybeSource source3, MaybeSource source4 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return mergeArray(source1, source2, source3, source4); + } + + /** + * Merges an array sequence of MaybeSource instances into a single Flowable sequence, + * running all MaybeSources at once. + *
+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code mergeArray} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code MaybeSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code MaybeSource}s are disposed. + * If more than one {@code MaybeSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(MaybeSource...)} to merge sources and terminate only when all source {@code MaybeSource}s + * have completed or failed with an error. + *
+ *
+ * @param the common and resulting value type + * @param sources the array sequence of MaybeSource sources + * @return the new Flowable instance + * @see #mergeArrayDelayError(MaybeSource...) + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable mergeArray(MaybeSource... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + if (sources.length == 0) { + return Flowable.empty(); + } + if (sources.length == 1) { + return RxJavaPlugins.onAssembly(new MaybeToFlowable((MaybeSource)sources[0])); + } + return RxJavaPlugins.onAssembly(new MaybeMergeArray(sources)); + } + + /** + * Flattens an array of MaybeSources into one Flowable, in a way that allows a Subscriber to receive all + * successfully emitted items from each of the source MaybeSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged MaybeSources notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged MaybeSources have finished emitting items. + *

+ * + *

+ * Even if multiple merged MaybeSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code mergeArrayDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of MaybeSources + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * MaybeSources in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeArrayDelayError(MaybeSource... sources) { + if (sources.length == 0) { + return Flowable.empty(); + } + return Flowable.fromArray(sources).flatMap((Function)MaybeToPublisher.instance(), true, sources.length); + } + + /** + * Flattens an Iterable of MaybeSources into one Flowable, in a way that allows a Subscriber to receive all + * successfully emitted items from each of the source MaybeSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged MaybeSources notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged MaybeSources have finished emitting items. + *

+ * + *

+ * Even if multiple merged MaybeSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of MaybeSources + * @return a Flowable that emits items that are the result of flattening the items emitted by the + * MaybeSources in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Iterable> sources) { + return Flowable.fromIterable(sources).flatMap((Function)MaybeToPublisher.instance(), true); + } + + /** + * Flattens a Publisher that emits MaybeSources into one Publisher, in a way that allows a Subscriber to + * receive all successfully emitted items from all of the source MaybeSources without being interrupted by + * an error notification from one of them or even the main Publisher. + *

+ * This behaves like {@link #merge(Publisher)} except that if any of the merged MaybeSources notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged MaybeSources and the main Publisher have finished emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed + * in unbounded mode (i.e., no backpressure is applied to it).
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * a Publisher that emits MaybeSources + * @return a Flowable that emits all of the items emitted by the Publishers emitted by the + * {@code source} Publisher + * @see ReactiveX operators documentation: Merge + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Publisher> sources) { + return mergeDelayError(sources, Integer.MAX_VALUE); + } + + /** + * Flattens a Publisher that emits MaybeSources into one Publisher, in a way that allows a Subscriber to + * receive all successfully emitted items from all of the source MaybeSources without being interrupted by + * an error notification from one of them or even the main Publisher as well as limiting the total number of active MaybeSources. + *

+ * This behaves like {@link #merge(Publisher, int)} except that if any of the merged MaybeSources notify of an + * error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged MaybeSources and the main Publisher have finished emitting items. + *

+ * + *

+ * Even if multiple merged Publishers send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream. The outer {@code Publisher} is consumed + * in unbounded mode (i.e., no backpressure is applied to it).
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.9 - experimental + * @param the common element base type + * @param sources + * a Publisher that emits MaybeSources + * @param maxConcurrency the maximum number of active inner MaybeSources to be merged at a time + * @return a Flowable that emits all of the items emitted by the Publishers emitted by the + * {@code source} Publisher + * @see ReactiveX operators documentation: Merge + * @since 2.2 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Publisher> sources, int maxConcurrency) { + ObjectHelper.requireNonNull(sources, "source is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + return RxJavaPlugins.onAssembly(new FlowableFlatMapPublisher(sources, MaybeToPublisher.instance(), true, maxConcurrency, 1)); + } + + /** + * Flattens two MaybeSources into one Flowable, in a way that allows a Subscriber to receive all + * successfully emitted items from each of the source MaybeSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(MaybeSource, MaybeSource)} except that if any of the merged MaybeSources + * notify of an error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain from + * propagating that error notification until all of the merged MaybeSources have finished emitting items. + *

+ * + *

+ * Even if both merged MaybeSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * a MaybeSource to be merged + * @param source2 + * a MaybeSource to be merged + * @return a Flowable that emits all of the items that are emitted by the two source MaybeSources + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(MaybeSource source1, MaybeSource source2) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return mergeArrayDelayError(source1, source2); + } + + /** + * Flattens three MaybeSource into one Flowable, in a way that allows a Subscriber to receive all + * successfully emitted items from all of the source MaybeSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(MaybeSource, MaybeSource, MaybeSource)} except that if any of the merged + * MaybeSources notify of an error via {@link Subscriber#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged MaybeSources have finished emitting + * items. + *

+ * + *

+ * Even if multiple merged MaybeSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * a MaybeSource to be merged + * @param source2 + * a MaybeSource to be merged + * @param source3 + * a MaybeSource to be merged + * @return a Flowable that emits all of the items that are emitted by the source MaybeSources + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(MaybeSource source1, + MaybeSource source2, MaybeSource source3) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return mergeArrayDelayError(source1, source2, source3); + } + + /** + * Flattens four MaybeSources into one Flowable, in a way that allows a Subscriber to receive all + * successfully emitted items from all of the source MaybeSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(MaybeSource, MaybeSource, MaybeSource, MaybeSource)} except that if any of + * the merged MaybeSources notify of an error via {@link Subscriber#onError onError}, {@code mergeDelayError} + * will refrain from propagating that error notification until all of the merged MaybeSources have finished + * emitting items. + *

+ * + *

+ * Even if multiple merged MaybeSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Subscribers once. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * a MaybeSource to be merged + * @param source2 + * a MaybeSource to be merged + * @param source3 + * a MaybeSource to be merged + * @param source4 + * a MaybeSource to be merged + * @return a Flowable that emits all of the items that are emitted by the source MaybeSources + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked" }) + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError( + MaybeSource source1, MaybeSource source2, + MaybeSource source3, MaybeSource source4) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return mergeArrayDelayError(source1, source2, source3, source4); + } + + /** + * Returns a Maybe that never sends any items or notifications to a {@link MaybeObserver}. + *

+ * + *

+ * This Maybe is useful primarily for testing purposes. + *

+ *
Scheduler:
+ *
{@code never} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items (not) emitted by the Maybe + * @return a Maybe that never emits any items or sends any notifications to a {@link MaybeObserver} + * @see ReactiveX operators documentation: Never + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Maybe never() { + return RxJavaPlugins.onAssembly((Maybe)MaybeNever.INSTANCE); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two MaybeSource sequences are the + * same by comparing the items emitted by each MaybeSource pairwise. + *

+ * + *

+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first MaybeSource to compare + * @param source2 + * the second MaybeSource to compare + * @param + * the type of items emitted by each MaybeSource + * @return a Single that emits a Boolean value that indicates whether the two sequences are the same + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(MaybeSource source1, MaybeSource source2) { + return sequenceEqual(source1, source2, ObjectHelper.equalsPredicate()); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two MaybeSources are the + * same by comparing the items emitted by each MaybeSource pairwise based on the results of a specified + * equality function. + *

+ * + *

+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first MaybeSource to compare + * @param source2 + * the second MaybeSource to compare + * @param isEqual + * a function used to compare items emitted by each MaybeSource + * @param + * the type of items emitted by each MaybeSource + * @return a Single that emits a Boolean value that indicates whether the two MaybeSource sequences + * are the same according to the specified function + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(MaybeSource source1, MaybeSource source2, + BiPredicate isEqual) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(isEqual, "isEqual is null"); + return RxJavaPlugins.onAssembly(new MaybeEqualSingle(source1, source2, isEqual)); + } + + /** + * Returns a Maybe that emits {@code 0L} after a specified delay. + *

+ * + *

+ *
Scheduler:
+ *
{@code timer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the initial delay before emitting a single {@code 0L} + * @param unit + * time units to use for {@code delay} + * @return a Maybe that emits {@code 0L} after a specified delay + * @see ReactiveX operators documentation: Timer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Maybe timer(long delay, TimeUnit unit) { + return timer(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Maybe that emits {@code 0L} after a specified delay on a specified Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the initial delay before emitting a single 0L + * @param unit + * time units to use for {@code delay} + * @param scheduler + * the {@link Scheduler} to use for scheduling the item + * @return a Maybe that emits {@code 0L} after a specified delay, on a specified Scheduler + * @see ReactiveX operators documentation: Timer + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Maybe timer(long delay, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + + return RxJavaPlugins.onAssembly(new MaybeTimer(Math.max(0L, delay), unit, scheduler)); + } + + /** + * Advanced use only: creates a Maybe instance without + * any safeguards by using a callback that is called with a MaybeObserver. + *
+ *
Scheduler:
+ *
{@code unsafeCreate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param onSubscribe the function that is called with the subscribing MaybeObserver + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe unsafeCreate(MaybeSource onSubscribe) { + if (onSubscribe instanceof Maybe) { + throw new IllegalArgumentException("unsafeCreate(Maybe) should be upgraded"); + } + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + return RxJavaPlugins.onAssembly(new MaybeUnsafeCreate(onSubscribe)); + } + + /** + * Constructs a Maybe that creates a dependent resource object which is disposed of when the + * upstream terminates or the downstream calls dispose(). + *

+ * + *

+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the generated MaybeSource + * @param the type of the resource associated with the output sequence + * @param resourceSupplier + * the factory function to create a resource object that depends on the Maybe + * @param sourceSupplier + * the factory function to create a MaybeSource + * @param resourceDisposer + * the function that will dispose of the resource + * @return the Maybe whose lifetime controls the lifetime of the dependent resource object + * @see ReactiveX operators documentation: Using + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe using(Callable resourceSupplier, + Function> sourceSupplier, + Consumer resourceDisposer) { + return using(resourceSupplier, sourceSupplier, resourceDisposer, true); + } + + /** + * Constructs a Maybe that creates a dependent resource object which is disposed of just before + * termination if you have set {@code disposeEagerly} to {@code true} and a downstream dispose() does not occur + * before termination. Otherwise resource disposal will occur on call to dispose(). Eager disposal is + * particularly appropriate for a synchronous Maybe that reuses resources. {@code disposeAction} will + * only be called once per subscription. + *

+ * + *

+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the generated MaybeSource + * @param the type of the resource associated with the output sequence + * @param resourceSupplier + * the factory function to create a resource object that depends on the Maybe + * @param sourceSupplier + * the factory function to create a MaybeSource + * @param resourceDisposer + * the function that will dispose of the resource + * @param eager + * if {@code true} then disposal will happen either on a dispose() call or just before emission of + * a terminal event ({@code onComplete} or {@code onError}). + * @return the Maybe whose lifetime controls the lifetime of the dependent resource object + * @see ReactiveX operators documentation: Using + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe using(Callable resourceSupplier, + Function> sourceSupplier, + Consumer resourceDisposer, boolean eager) { + ObjectHelper.requireNonNull(resourceSupplier, "resourceSupplier is null"); + ObjectHelper.requireNonNull(sourceSupplier, "sourceSupplier is null"); + ObjectHelper.requireNonNull(resourceDisposer, "disposer is null"); + return RxJavaPlugins.onAssembly(new MaybeUsing(resourceSupplier, sourceSupplier, resourceDisposer, eager)); + } + + /** + * Wraps a MaybeSource instance into a new Maybe instance if not already a Maybe + * instance. + *
+ *
Scheduler:
+ *
{@code wrap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param source the source to wrap + * @return the Maybe wrapper or the source cast to Maybe (if possible) + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe wrap(MaybeSource source) { + if (source instanceof Maybe) { + return RxJavaPlugins.onAssembly((Maybe)source); + } + ObjectHelper.requireNonNull(source, "onSubscribe is null"); + return RxJavaPlugins.onAssembly(new MaybeUnsafeCreate(source)); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * items emitted, in sequence, by an Iterable of other MaybeSources. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + * + *

+ * + *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common value type + * @param the zipped result type + * @param sources + * an Iterable of source MaybeSources + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results in + * an item that will be emitted by the resulting Maybe + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zip(Iterable> sources, Function zipper) { + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new MaybeZipIterable(sources, zipper)); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * two items emitted, in sequence, by two other MaybeSources. + *

+ * + *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the zipped result type + * @param source1 + * the first source MaybeSource + * @param source2 + * a second source MaybeSource + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results + * in an item that will be emitted by the resulting Maybe + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zip( + MaybeSource source1, MaybeSource source2, + BiFunction zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * three items emitted, in sequence, by three other MaybeSources. + *

+ * + *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the zipped result type + * @param source1 + * the first source MaybeSource + * @param source2 + * a second source MaybeSource + * @param source3 + * a third source MaybeSource + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results in + * an item that will be emitted by the resulting Maybe + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zip( + MaybeSource source1, MaybeSource source2, MaybeSource source3, + Function3 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * four items emitted, in sequence, by four other MaybeSources. + *

+ * + *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the zipped result type + * @param source1 + * the first source MaybeSource + * @param source2 + * a second source MaybeSource + * @param source3 + * a third source MaybeSource + * @param source4 + * a fourth source MaybeSource + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results in + * an item that will be emitted by the resulting Maybe + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zip( + MaybeSource source1, MaybeSource source2, MaybeSource source3, + MaybeSource source4, + Function4 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * five items emitted, in sequence, by five other MaybeSources. + *

+ * + *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the zipped result type + * @param source1 + * the first source MaybeSource + * @param source2 + * a second source MaybeSource + * @param source3 + * a third source MaybeSource + * @param source4 + * a fourth source MaybeSource + * @param source5 + * a fifth source MaybeSource + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results in + * an item that will be emitted by the resulting Maybe + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zip( + MaybeSource source1, MaybeSource source2, MaybeSource source3, + MaybeSource source4, MaybeSource source5, + Function5 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * six items emitted, in sequence, by six other MaybeSources. + *

+ * + *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the zipped result type + * @param source1 + * the first source MaybeSource + * @param source2 + * a second source MaybeSource + * @param source3 + * a third source MaybeSource + * @param source4 + * a fourth source MaybeSource + * @param source5 + * a fifth source MaybeSource + * @param source6 + * a sixth source MaybeSource + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results in + * an item that will be emitted by the resulting Maybe + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zip( + MaybeSource source1, MaybeSource source2, MaybeSource source3, + MaybeSource source4, MaybeSource source5, MaybeSource source6, + Function6 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5, source6); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * seven items emitted, in sequence, by seven other MaybeSources. + *

+ * + *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the value type of the seventh source + * @param the zipped result type + * @param source1 + * the first source MaybeSource + * @param source2 + * a second source MaybeSource + * @param source3 + * a third source MaybeSource + * @param source4 + * a fourth source MaybeSource + * @param source5 + * a fifth source MaybeSource + * @param source6 + * a sixth source MaybeSource + * @param source7 + * a seventh source MaybeSource + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results in + * an item that will be emitted by the resulting Maybe + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zip( + MaybeSource source1, MaybeSource source2, MaybeSource source3, + MaybeSource source4, MaybeSource source5, MaybeSource source6, + MaybeSource source7, + Function7 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5, source6, source7); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * eight items emitted, in sequence, by eight other MaybeSources. + *

+ * + *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the value type of the seventh source + * @param the value type of the eighth source + * @param the zipped result type + * @param source1 + * the first source MaybeSource + * @param source2 + * a second source MaybeSource + * @param source3 + * a third source MaybeSource + * @param source4 + * a fourth source MaybeSource + * @param source5 + * a fifth source MaybeSource + * @param source6 + * a sixth source MaybeSource + * @param source7 + * a seventh source MaybeSource + * @param source8 + * an eighth source MaybeSource + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results in + * an item that will be emitted by the resulting Maybe + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zip( + MaybeSource source1, MaybeSource source2, MaybeSource source3, + MaybeSource source4, MaybeSource source5, MaybeSource source6, + MaybeSource source7, MaybeSource source8, + Function8 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5, source6, source7, source8); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * nine items emitted, in sequence, by nine other MaybeSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the value type of the seventh source + * @param the value type of the eighth source + * @param the value type of the ninth source + * @param the zipped result type + * @param source1 + * the first source MaybeSource + * @param source2 + * a second source MaybeSource + * @param source3 + * a third source MaybeSource + * @param source4 + * a fourth source MaybeSource + * @param source5 + * a fifth source MaybeSource + * @param source6 + * a sixth source MaybeSource + * @param source7 + * a seventh source MaybeSource + * @param source8 + * an eighth source MaybeSource + * @param source9 + * a ninth source MaybeSource + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results in + * an item that will be emitted by the resulting MaybeSource + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zip( + MaybeSource source1, MaybeSource source2, MaybeSource source3, + MaybeSource source4, MaybeSource source5, MaybeSource source6, + MaybeSource source7, MaybeSource source8, MaybeSource source9, + Function9 zipper) { + + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + ObjectHelper.requireNonNull(source9, "source9 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5, source6, source7, source8, source9); + } + + /** + * Returns a Maybe that emits the results of a specified combiner function applied to combinations of + * items emitted, in sequence, by an array of other MaybeSources. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + * + *

+ * + *

This operator terminates eagerly if any of the source MaybeSources signal an onError or onComplete. This + * also means it is possible some sources may not get subscribed to at all. + *

+ *
Scheduler:
+ *
{@code zipArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element type + * @param the result type + * @param sources + * an array of source MaybeSources + * @param zipper + * a function that, when applied to an item emitted by each of the source MaybeSources, results in + * an item that will be emitted by the resulting MaybeSource + * @return a Maybe that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Maybe zipArray(Function zipper, + MaybeSource... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + if (sources.length == 0) { + return empty(); + } + ObjectHelper.requireNonNull(zipper, "zipper is null"); + return RxJavaPlugins.onAssembly(new MaybeZipArray(sources, zipper)); + } + + // ------------------------------------------------------------------ + // Instance methods + // ------------------------------------------------------------------ + + /** + * Mirrors the MaybeSource (current or provided) that first signals an event. + *

+ * + *

+ *
Scheduler:
+ *
{@code ambWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * a MaybeSource competing to react first. A subscription to this provided source will occur after + * subscribing to the current source. + * @return a Maybe that emits the same sequence as whichever of the source MaybeSources first + * signalled + * @see ReactiveX operators documentation: Amb + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe ambWith(MaybeSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return ambArray(this, other); + } + + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * This allows fluent conversion to any other type. + *

+ *
Scheduler:
+ *
{@code as} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.7 - experimental + * @param the resulting object type + * @param converter the function that receives the current Maybe instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final R as(@NonNull MaybeConverter converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + + /** + * Waits in a blocking fashion until the current Maybe signals a success value (which is returned), + * null if completed or an exception (which is propagated). + *

+ *
Scheduler:
+ *
{@code blockingGet} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * @return the success value + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingGet() { + BlockingMultiObserver observer = new BlockingMultiObserver(); + subscribe(observer); + return observer.blockingGet(); + } + + /** + * Waits in a blocking fashion until the current Maybe signals a success value (which is returned), + * defaultValue if completed or an exception (which is propagated). + *
+ *
Scheduler:
+ *
{@code blockingGet} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * @param defaultValue the default item to return if this Maybe is empty + * @return the success value + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingGet(T defaultValue) { + ObjectHelper.requireNonNull(defaultValue, "defaultValue is null"); + BlockingMultiObserver observer = new BlockingMultiObserver(); + subscribe(observer); + return observer.blockingGet(defaultValue); + } + + /** + * Returns a Maybe that subscribes to this Maybe lazily, caches its event + * and replays it, to all the downstream subscribers. + *

+ * + *

+ * The operator subscribes only when the first downstream subscriber subscribes and maintains + * a single subscription towards this Maybe. + *

+ * Note: You sacrifice the ability to dispose the origin when you use the {@code cache}. + *

+ *
Scheduler:
+ *
{@code cache} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Maybe that, when first subscribed to, caches all of its items and notifications for the + * benefit of subsequent subscribers + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe cache() { + return RxJavaPlugins.onAssembly(new MaybeCache(this)); + } + + /** + * Casts the success value of the current Maybe into the target type or signals a + * ClassCastException if not compatible. + *
+ *
Scheduler:
+ *
{@code cast} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the target type + * @param clazz the type token to use for casting the success result from the current Maybe + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe cast(final Class clazz) { + ObjectHelper.requireNonNull(clazz, "clazz is null"); + return map(Functions.castFunction(clazz)); + } + + /** + * Transform a Maybe by applying a particular Transformer function to it. + *

+ * This method operates on the Maybe itself whereas {@link #lift} operates on the Maybe's MaybeObservers. + *

+ * If the operator you are creating is designed to act on the individual item emitted by a Maybe, use + * {@link #lift}. If your operator is designed to transform the source Maybe as a whole (for instance, by + * applying a particular set of existing RxJava operators to it) use {@code compose}. + *

+ *
Scheduler:
+ *
{@code compose} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the Maybe returned by the transformer function + * @param transformer the transformer function, not null + * @return a Maybe, transformed by the transformer function + * @see RxJava wiki: Implementing Your Own Operators + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe compose(MaybeTransformer transformer) { + return wrap(((MaybeTransformer) ObjectHelper.requireNonNull(transformer, "transformer is null")).apply(this)); + } + + /** + * Returns a Maybe that is based on applying a specified function to the item emitted by the source Maybe, + * where that function returns a MaybeSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

Note that flatMap and concatMap for Maybe is the same operation. + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Maybe, returns a MaybeSource + * @return the Maybe returned from {@code func} when applied to the item emitted by the source Maybe + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe concatMap(Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatten(this, mapper)); + } + + /** + * Returns a Flowable that emits the items emitted from the current MaybeSource, then the next, one after + * the other, without interleaving them. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * a MaybeSource to be concatenated after the current + * @return a Flowable that emits items emitted by the two source MaybeSources, one after the other, + * without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatWith(MaybeSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return concat(this, other); + } + + /** + * Returns a Single that emits a Boolean that indicates whether the source Maybe emitted a + * specified item. + *

+ * + *

+ *
Scheduler:
+ *
{@code contains} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the item to search for in the emissions from the source Maybe, not null + * @return a Single that emits {@code true} if the specified item is emitted by the source Maybe, + * or {@code false} if the source Maybe completes without emitting that item + * @see ReactiveX operators documentation: Contains + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single contains(final Object item) { + ObjectHelper.requireNonNull(item, "item is null"); + return RxJavaPlugins.onAssembly(new MaybeContains(this, item)); + } + + /** + * Returns a Single that counts the total number of items emitted (0 or 1) by the source Maybe and emits + * this count as a 64-bit Long. + *

+ * + *

+ *
Scheduler:
+ *
{@code count} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Single that emits a single item: the number of items emitted by the source Maybe as a + * 64-bit Long item + * @see ReactiveX operators documentation: Count + * @see #count() + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single count() { + return RxJavaPlugins.onAssembly(new MaybeCount(this)); + } + + /** + * Returns a Maybe that emits the item emitted by the source Maybe or a specified default item + * if the source Maybe is empty. + *

+ * Note that the result Maybe is semantically equivalent to a {@code Single}, since it's guaranteed + * to emit exactly one item or an error. See {@link #toSingle(Object)} for a method with equivalent + * behavior which returns a {@code Single}. + *

+ * + *

+ *
Scheduler:
+ *
{@code defaultIfEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * the item to emit if the source Maybe emits no items + * @return a Maybe that emits either the specified default item if the source Maybe emits no + * items, or the items emitted by the source Maybe + * @see ReactiveX operators documentation: DefaultIfEmpty + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe defaultIfEmpty(T defaultItem) { + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); + return switchIfEmpty(just(defaultItem)); + } + + /** + * Returns a Maybe that signals the events emitted by the source Maybe shifted forward in time by a + * specified delay. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code delay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @return the new Maybe instance + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Maybe delay(long delay, TimeUnit unit) { + return delay(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Maybe that signals the events emitted by the source Maybe shifted forward in time by a + * specified delay running on the specified Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
you specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the {@link Scheduler} to use for delaying + * @return the new Maybe instance + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Maybe delay(long delay, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new MaybeDelay(this, Math.max(0L, delay), unit, scheduler)); + } + + /** + * Delays the emission of this Maybe until the given Publisher signals an item or completes. + *

+ * + *

+ *
Backpressure:
+ *
The {@code delayIndicator} is consumed in an unbounded manner but is cancelled after + * the first item it produces.
+ *
Scheduler:
+ *
This version of {@code delay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the subscription delay value type (ignored) + * @param + * the item delay value type (ignored) + * @param delayIndicator + * the Publisher that gets subscribed to when this Maybe signals an event and that + * signal is emitted when the Publisher signals an item or completes + * @return the new Maybe instance + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + public final Maybe delay(Publisher delayIndicator) { + ObjectHelper.requireNonNull(delayIndicator, "delayIndicator is null"); + return RxJavaPlugins.onAssembly(new MaybeDelayOtherPublisher(this, delayIndicator)); + } + + /** + * Returns a Maybe that delays the subscription to this Maybe + * until the other Publisher emits an element or completes normally. + *
+ *
Backpressure:
+ *
The {@code Publisher} source is consumed in an unbounded fashion (without applying backpressure).
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the other Publisher, irrelevant + * @param subscriptionIndicator the other Publisher that should trigger the subscription + * to this Publisher. + * @return a Maybe that delays the subscription to this Maybe + * until the other Publisher emits an element or completes normally. + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe delaySubscription(Publisher subscriptionIndicator) { + ObjectHelper.requireNonNull(subscriptionIndicator, "subscriptionIndicator is null"); + return RxJavaPlugins.onAssembly(new MaybeDelaySubscriptionOtherPublisher(this, subscriptionIndicator)); + } + + /** + * Returns a Maybe that delays the subscription to the source Maybe by a given amount of time. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code delaySubscription} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the time to delay the subscription + * @param unit + * the time unit of {@code delay} + * @return a Maybe that delays the subscription to the source Maybe by the given amount + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Maybe delaySubscription(long delay, TimeUnit unit) { + return delaySubscription(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Maybe that delays the subscription to the source Maybe by a given amount of time, + * both waiting and subscribing on a given Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the time to delay the subscription + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the Scheduler on which the waiting and subscription will happen + * @return a Maybe that delays the subscription to the source Maybe by a given + * amount, waiting and subscribing on the given Scheduler + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Maybe delaySubscription(long delay, TimeUnit unit, Scheduler scheduler) { + return delaySubscription(Flowable.timer(delay, unit, scheduler)); + } + + /** + * Calls the specified consumer with the success item after this item has been emitted to the downstream. + *

Note that the {@code onAfterNext} action is shared between subscriptions and as such + * should be thread-safe. + *

+ *
Scheduler:
+ *
{@code doAfterSuccess} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.1 - experimental + * @param onAfterSuccess the Consumer that will be called after emitting an item from upstream to the downstream + * @return the new Maybe instance + * @since 2.1 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doAfterSuccess(Consumer onAfterSuccess) { + ObjectHelper.requireNonNull(onAfterSuccess, "onAfterSuccess is null"); + return RxJavaPlugins.onAssembly(new MaybeDoAfterSuccess(this, onAfterSuccess)); + } + + /** + * Registers an {@link Action} to be called when this Maybe invokes either + * {@link MaybeObserver#onComplete onSuccess}, + * {@link MaybeObserver#onComplete onComplete} or {@link MaybeObserver#onError onError}. + *

+ * + *

+ *
Scheduler:
+ *
{@code doAfterTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onAfterTerminate + * an {@link Action} to be invoked when the source Maybe finishes + * @return a Maybe that emits the same items as the source Maybe, then invokes the + * {@link Action} + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doAfterTerminate(Action onAfterTerminate) { + return RxJavaPlugins.onAssembly(new MaybePeek(this, + Functions.emptyConsumer(), // onSubscribe + Functions.emptyConsumer(), // onSuccess + Functions.emptyConsumer(), // onError + Functions.EMPTY_ACTION, // onComplete + ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"), + Functions.EMPTY_ACTION // dispose + )); + } + + /** + * Calls the specified action after this Maybe signals onSuccess, onError or onComplete or gets disposed by + * the downstream. + *

In case of a race between a terminal event and a dispose call, the provided {@code onFinally} action + * is executed once per subscription. + *

Note that the {@code onFinally} action is shared between subscriptions and as such + * should be thread-safe. + *

+ *
Scheduler:
+ *
{@code doFinally} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.1 - experimental + * @param onFinally the action called when this Maybe terminates or gets disposed + * @return the new Maybe instance + * @since 2.1 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doFinally(Action onFinally) { + ObjectHelper.requireNonNull(onFinally, "onFinally is null"); + return RxJavaPlugins.onAssembly(new MaybeDoFinally(this, onFinally)); + } + + /** + * Calls the shared {@code Action} if a MaybeObserver subscribed to the current Maybe + * disposes the common Disposable it received via onSubscribe. + *

+ *
Scheduler:
+ *
{@code doOnDispose} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onDispose the action called when the subscription is disposed + * @throws NullPointerException if onDispose is null + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doOnDispose(Action onDispose) { + return RxJavaPlugins.onAssembly(new MaybePeek(this, + Functions.emptyConsumer(), // onSubscribe + Functions.emptyConsumer(), // onSuccess + Functions.emptyConsumer(), // onError + Functions.EMPTY_ACTION, // onComplete + Functions.EMPTY_ACTION, // (onSuccess | onError | onComplete) after + ObjectHelper.requireNonNull(onDispose, "onDispose is null") + )); + } + + /** + * Modifies the source Maybe so that it invokes an action when it calls {@code onComplete}. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnComplete} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onComplete + * the action to invoke when the source Maybe calls {@code onComplete} + * @return the new Maybe with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doOnComplete(Action onComplete) { + return RxJavaPlugins.onAssembly(new MaybePeek(this, + Functions.emptyConsumer(), // onSubscribe + Functions.emptyConsumer(), // onSuccess + Functions.emptyConsumer(), // onError + ObjectHelper.requireNonNull(onComplete, "onComplete is null"), + Functions.EMPTY_ACTION, // (onSuccess | onError | onComplete) + Functions.EMPTY_ACTION // dispose + )); + } + + /** + * Calls the shared consumer with the error sent via onError for each + * MaybeObserver that subscribes to the current Maybe. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onError the consumer called with the success value of onError + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doOnError(Consumer onError) { + return RxJavaPlugins.onAssembly(new MaybePeek(this, + Functions.emptyConsumer(), // onSubscribe + Functions.emptyConsumer(), // onSuccess + ObjectHelper.requireNonNull(onError, "onError is null"), + Functions.EMPTY_ACTION, // onComplete + Functions.EMPTY_ACTION, // (onSuccess | onError | onComplete) + Functions.EMPTY_ACTION // dispose + )); + } + + /** + * Calls the given onEvent callback with the (success value, null) for an onSuccess, (null, throwable) for + * an onError or (null, null) for an onComplete signal from this Maybe before delivering said + * signal to the downstream. + *

+ * Exceptions thrown from the callback will override the event so the downstream receives the + * error instead of the original signal. + *

+ *
Scheduler:
+ *
{@code doOnEvent} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onEvent the callback to call with the terminal event tuple + * @return the new Maybe instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doOnEvent(BiConsumer onEvent) { + ObjectHelper.requireNonNull(onEvent, "onEvent is null"); + return RxJavaPlugins.onAssembly(new MaybeDoOnEvent(this, onEvent)); + } + + /** + * Calls the shared consumer with the Disposable sent through the onSubscribe for each + * MaybeObserver that subscribes to the current Maybe. + *
+ *
Scheduler:
+ *
{@code doOnSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onSubscribe the consumer called with the Disposable sent via onSubscribe + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doOnSubscribe(Consumer onSubscribe) { + return RxJavaPlugins.onAssembly(new MaybePeek(this, + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"), + Functions.emptyConsumer(), // onSuccess + Functions.emptyConsumer(), // onError + Functions.EMPTY_ACTION, // onComplete + Functions.EMPTY_ACTION, // (onSuccess | onError | onComplete) + Functions.EMPTY_ACTION // dispose + )); + } + + /** + * Returns a Maybe instance that calls the given onTerminate callback + * just before this Maybe completes normally or with an exception. + *

+ * + *

+ * This differs from {@code doAfterTerminate} in that this happens before the {@code onComplete} or + * {@code onError} notification. + *

+ *
Scheduler:
+ *
{@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onTerminate the action to invoke when the consumer calls {@code onComplete} or {@code onError} + * @return the new Maybe instance + * @see ReactiveX operators documentation: Do + * @see #doOnTerminate(Action) + * @since 2.2.7 - experimental + */ + @Experimental + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doOnTerminate(final Action onTerminate) { + ObjectHelper.requireNonNull(onTerminate, "onTerminate is null"); + return RxJavaPlugins.onAssembly(new MaybeDoOnTerminate(this, onTerminate)); + } + + /** + * Calls the shared consumer with the success value sent via onSuccess for each + * MaybeObserver that subscribes to the current Maybe. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnSuccess} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onSuccess the consumer called with the success value of onSuccess + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe doOnSuccess(Consumer onSuccess) { + return RxJavaPlugins.onAssembly(new MaybePeek(this, + Functions.emptyConsumer(), // onSubscribe + ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"), + Functions.emptyConsumer(), // onError + Functions.EMPTY_ACTION, // onComplete + Functions.EMPTY_ACTION, // (onSuccess | onError | onComplete) + Functions.EMPTY_ACTION // dispose + )); + } + + /** + * Filters the success item of the Maybe via a predicate function and emitting it if the predicate + * returns true, completing otherwise. + *

+ * + *

+ *
Scheduler:
+ *
{@code filter} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function that evaluates the item emitted by the source Maybe, returning {@code true} + * if it passes the filter + * @return a Maybe that emit the item emitted by the source Maybe that the filter + * evaluates as {@code true} + * @see ReactiveX operators documentation: Filter + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe filter(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new MaybeFilter(this, predicate)); + } + + /** + * Returns a Maybe that is based on applying a specified function to the item emitted by the source Maybe, + * where that function returns a MaybeSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

Note that flatMap and concatMap for Maybe is the same operation. + * + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Maybe, returns a MaybeSource + * @return the Maybe returned from {@code func} when applied to the item emitted by the source Maybe + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe flatMap(Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatten(this, mapper)); + } + + /** + * Maps the onSuccess, onError or onComplete signals of this Maybe into MaybeSource and emits that + * MaybeSource's signals. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the result type + * @param onSuccessMapper + * a function that returns a MaybeSource to merge for the onSuccess item emitted by this Maybe + * @param onErrorMapper + * a function that returns a MaybeSource to merge for an onError notification from this Maybe + * @param onCompleteSupplier + * a function that returns a MaybeSource to merge for an onComplete notification this Maybe + * @return the new Maybe instance + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe flatMap( + Function> onSuccessMapper, + Function> onErrorMapper, + Callable> onCompleteSupplier) { + ObjectHelper.requireNonNull(onSuccessMapper, "onSuccessMapper is null"); + ObjectHelper.requireNonNull(onErrorMapper, "onErrorMapper is null"); + ObjectHelper.requireNonNull(onCompleteSupplier, "onCompleteSupplier is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapNotification(this, onSuccessMapper, onErrorMapper, onCompleteSupplier)); + } + + /** + * Returns a Maybe that emits the results of a specified function to the pair of values emitted by the + * source Maybe and a specified mapped MaybeSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the MaybeSource returned by the {@code mapper} function + * @param + * the type of items emitted by the resulting Maybe + * @param mapper + * a function that returns a MaybeSource for the item emitted by the source Maybe + * @param resultSelector + * a function that combines one item emitted by each of the source and collection MaybeSource and + * returns an item to be emitted by the resulting MaybeSource + * @return the new Maybe instance + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe flatMap(Function> mapper, + BiFunction resultSelector) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.requireNonNull(resultSelector, "resultSelector is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapBiSelector(this, mapper, resultSelector)); + } + + /** + * Maps the success value of the upstream {@link Maybe} into an {@link Iterable} and emits its items as a + * {@link Flowable} sequence. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code flattenAsFlowable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting Iterable + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Maybe + * @return the new Flowable instance + * @see ReactiveX operators documentation: FlatMap + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flattenAsFlowable(final Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapIterableFlowable(this, mapper)); + } + + /** + * Maps the success value of the upstream {@link Maybe} into an {@link Iterable} and emits its items as an + * {@link Observable} sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code flattenAsObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting Iterable + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Maybe + * @return the new Observable instance + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flattenAsObservable(final Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapIterableObservable(this, mapper)); + } + + /** + * Returns an Observable that is based on applying a specified function to the item emitted by the source Maybe, + * where that function returns an ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Maybe, returns an ObservableSource + * @return the Observable returned from {@code func} when applied to the item emitted by the source Maybe + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMapObservable(Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapObservable(this, mapper)); + } + + /** + * Returns a Flowable that emits items based on applying a specified function to the item emitted by the + * source Maybe, where that function returns a Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The returned Flowable honors the downstream backpressure.
+ *
Scheduler:
+ *
{@code flatMapPublisher} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Maybe, returns a + * Flowable + * @return the Flowable returned from {@code func} when applied to the item emitted by the source Maybe + * @see ReactiveX operators documentation: FlatMap + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapPublisher(Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapPublisher(this, mapper)); + } + + /** + * Returns a {@link Single} based on applying a specified function to the item emitted by the + * source {@link Maybe}, where that function returns a {@link Single}. + * When this Maybe completes a {@link NoSuchElementException} will be thrown. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Maybe, returns a + * Single + * @return the Single returned from {@code mapper} when applied to the item emitted by the source Maybe + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single flatMapSingle(final Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapSingle(this, mapper)); + } + + /** + * Returns a {@link Maybe} based on applying a specified function to the item emitted by the + * source {@link Maybe}, where that function returns a {@link Single}. + * When this Maybe just completes the resulting {@code Maybe} completes as well. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapSingleElement} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + *

History: 2.0.2 - experimental + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Maybe, returns a + * Single + * @return the new Maybe instance + * @see ReactiveX operators documentation: FlatMap + * @since 2.1 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe flatMapSingleElement(final Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapSingleElement(this, mapper)); + } + + /** + * Returns a {@link Completable} that completes based on applying a specified function to the item emitted by the + * source {@link Maybe}, where that function returns a {@link Completable}. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param mapper + * a function that, when applied to the item emitted by the source Maybe, returns a + * Completable + * @return the Completable returned from {@code mapper} when applied to the item emitted by the source Maybe + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable flatMapCompletable(final Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeFlatMapCompletable(this, mapper)); + } + + /** + * Hides the identity of this Maybe and its Disposable. + *

+ * + *

Allows preventing certain identity-based + * optimizations (fusion). + *

+ *
Scheduler:
+ *
{@code hide} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Maybe instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe hide() { + return RxJavaPlugins.onAssembly(new MaybeHide(this)); + } + + /** + * Ignores the item emitted by the source Maybe and only calls {@code onComplete} or {@code onError}. + *

+ * + *

+ *
Scheduler:
+ *
{@code ignoreElement} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an empty Completable that only calls {@code onComplete} or {@code onError}, based on which one is + * called by the source Maybe + * @see ReactiveX operators documentation: IgnoreElements + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable ignoreElement() { + return RxJavaPlugins.onAssembly(new MaybeIgnoreElementCompletable(this)); + } + + /** + * Returns a Single that emits {@code true} if the source Maybe is empty, otherwise {@code false}. + *

+ * + *

+ *
Scheduler:
+ *
{@code isEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Single that emits a Boolean + * @see ReactiveX operators documentation: Contains + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single isEmpty() { + return RxJavaPlugins.onAssembly(new MaybeIsEmptySingle(this)); + } + + /** + * This method requires advanced knowledge about building operators, please consider + * other standard composition methods first; + * Returns a {@code Maybe} which, when subscribed to, invokes the {@link MaybeOperator#apply(MaybeObserver) apply(MaybeObserver)} method + * of the provided {@link MaybeOperator} for each individual downstream {@link Maybe} and allows the + * insertion of a custom operator by accessing the downstream's {@link MaybeObserver} during this subscription phase + * and providing a new {@code MaybeObserver}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + *

+ * Generally, such a new {@code MaybeObserver} will wrap the downstream's {@code MaybeObserver} and forwards the + * {@code onSuccess}, {@code onError} and {@code onComplete} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code dispose} and {@code isDisposed} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. + *

+ * Example: + *


+     * // Step 1: Create the consumer type that will be returned by the MaybeOperator.apply():
+     *
+     * public final class CustomMaybeObserver<T> implements MaybeObserver<T>, Disposable {
+     *
+     *     // The downstream's MaybeObserver that will receive the onXXX events
+     *     final MaybeObserver<? super String> downstream;
+     *
+     *     // The connection to the upstream source that will call this class' onXXX methods
+     *     Disposable upstream;
+     *
+     *     // The constructor takes the downstream subscriber and usually any other parameters
+     *     public CustomMaybeObserver(MaybeObserver<? super String> downstream) {
+     *         this.downstream = downstream;
+     *     }
+     *
+     *     // In the subscription phase, the upstream sends a Disposable to this class
+     *     // and subsequently this class has to send a Disposable to the downstream.
+     *     // Note that relaying the upstream's Disposable directly is not allowed in RxJava
+     *     @Override
+     *     public void onSubscribe(Disposable d) {
+     *         if (upstream != null) {
+     *             d.dispose();
+     *         } else {
+     *             upstream = d;
+     *             downstream.onSubscribe(this);
+     *         }
+     *     }
+     *
+     *     // The upstream calls this with the next item and the implementation's
+     *     // responsibility is to emit an item to the downstream based on the intended
+     *     // business logic, or if it can't do so for the particular item,
+     *     // request more from the upstream
+     *     @Override
+     *     public void onSuccess(T item) {
+     *         String str = item.toString();
+     *         if (str.length() < 2) {
+     *             downstream.onSuccess(str);
+     *         } else {
+     *             // Maybe is usually expected to produce one of the onXXX events
+     *             downstream.onComplete();
+     *         }
+     *     }
+     *
+     *     // Some operators may handle the upstream's error while others
+     *     // could just forward it to the downstream.
+     *     @Override
+     *     public void onError(Throwable throwable) {
+     *         downstream.onError(throwable);
+     *     }
+     *
+     *     // When the upstream completes, usually the downstream should complete as well.
+     *     @Override
+     *     public void onComplete() {
+     *         downstream.onComplete();
+     *     }
+     *
+     *     // Some operators may use their own resources which should be cleaned up if
+     *     // the downstream disposes the flow before it completed. Operators without
+     *     // resources can simply forward the dispose to the upstream.
+     *     // In some cases, a disposed flag may be set by this method so that other parts
+     *     // of this class may detect the dispose and stop sending events
+     *     // to the downstream.
+     *     @Override
+     *     public void dispose() {
+     *         upstream.dispose();
+     *     }
+     *
+     *     // Some operators may simply forward the call to the upstream while others
+     *     // can return the disposed flag set in dispose().
+     *     @Override
+     *     public boolean isDisposed() {
+     *         return upstream.isDisposed();
+     *     }
+     * }
+     *
+     * // Step 2: Create a class that implements the MaybeOperator interface and
+     * //         returns the custom consumer type from above in its apply() method.
+     * //         Such class may define additional parameters to be submitted to
+     * //         the custom consumer type.
+     *
+     * final class CustomMaybeOperator<T> implements MaybeOperator<String> {
+     *     @Override
+     *     public MaybeObserver<? super String> apply(MaybeObserver<? super T> upstream) {
+     *         return new CustomMaybeObserver<T>(upstream);
+     *     }
+     * }
+     *
+     * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it
+     * //         or reusing an existing one.
+     *
+     * Maybe.just(5)
+     * .lift(new CustomMaybeOperator<Integer>())
+     * .test()
+     * .assertResult("5");
+     *
+     * Maybe.just(15)
+     * .lift(new CustomMaybeOperator<Integer>())
+     * .test()
+     * .assertResult();
+     * 
+ *

+ * Creating custom operators can be complicated and it is recommended one consults the + * RxJava wiki: Writing operators page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. + *

+ * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Maybe} + * class and creating a {@link MaybeTransformer} with it is recommended. + *

+ * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code MaybeObserver} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Maybe}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return a {@code MaybeObserver} that should immediately dispose the upstream's {@code Disposable} in its + * {@code onSubscribe} method. Again, using a {@code MaybeTransformer} and extending the {@code Maybe} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. + *

+ *
Scheduler:
+ *
{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link MaybeOperator} may use a {@code Scheduler} to support its own asynchronous behavior.
+ *
+ * + * @param the output value type + * @param lift the {@link MaybeOperator} that receives the downstream's {@code MaybeObserver} and should return + * a {@code MaybeObserver} with custom behavior to be used as the consumer for the current + * {@code Maybe}. + * @return the new Maybe instance + * @see RxJava wiki: Writing operators + * @see #compose(MaybeTransformer) + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe lift(final MaybeOperator lift) { + ObjectHelper.requireNonNull(lift, "lift is null"); + return RxJavaPlugins.onAssembly(new MaybeLift(this, lift)); + } + + /** + * Returns a Maybe that applies a specified function to the item emitted by the source Maybe and + * emits the result of this function application. + *

+ * + *

+ *
Scheduler:
+ *
{@code map} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper + * a function to apply to the item emitted by the Maybe + * @return a Maybe that emits the item from the source Maybe, transformed by the specified function + * @see ReactiveX operators documentation: Map + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe map(Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new MaybeMap(this, mapper)); + } + + /** + * Maps the signal types of this Maybe into a {@link Notification} of the same kind + * and emits it as a single success value to downstream. + *

+ * + *

+ *
Scheduler:
+ *
{@code materialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Single instance + * @since 2.2.4 - experimental + * @see Single#dematerialize(Function) + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> materialize() { + return RxJavaPlugins.onAssembly(new MaybeMaterialize(this)); + } + + /** + * Flattens this and another Maybe into a single Flowable, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Maybes so that they appear as a single Flowable, by + * using the {@code mergeWith} method. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * a MaybeSource to be merged + * @return a new Flowable instance + * @see ReactiveX operators documentation: Merge + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable mergeWith(MaybeSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return merge(this, other); + } + + /** + * Wraps a Maybe to emit its item (or notify of its error) on a specified {@link Scheduler}, + * asynchronously. + *

+ * + *

+ *
Scheduler:
+ *
you specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to notify subscribers on + * @return the new Maybe instance that its subscribers are notified on the specified + * {@link Scheduler} + * @see ReactiveX operators documentation: ObserveOn + * @see RxJava Threading Examples + * @see #subscribeOn + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Maybe observeOn(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new MaybeObserveOn(this, scheduler)); + } + + /** + * Filters the items emitted by a Maybe, only emitting its success value if that + * is an instance of the supplied Class. + *

+ * + *

+ *
Scheduler:
+ *
{@code ofType} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output type + * @param clazz + * the class type to filter the items emitted by the source Maybe + * @return the new Maybe instance + * @see ReactiveX operators documentation: Filter + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe ofType(final Class clazz) { + ObjectHelper.requireNonNull(clazz, "clazz is null"); + return filter(Functions.isInstanceOf(clazz)).cast(clazz); + } + + /** + * Calls the specified converter function with the current Maybe instance + * during assembly time and returns its result. + *
+ *
Scheduler:
+ *
{@code to} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result type + * @param convert the function that is called with the current Maybe instance during + * assembly time that should return some value to be the result + * + * @return the value returned by the convert function + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final R to(Function, R> convert) { + try { + return ObjectHelper.requireNonNull(convert, "convert is null").apply(this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Converts this Maybe into a backpressure-aware Flowable instance composing cancellation + * through. + *
+ *
Backpressure:
+ *
The returned Flowable honors the backpressure of the downstream.
+ *
Scheduler:
+ *
{@code toFlowable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Flowable instance + */ + @SuppressWarnings("unchecked") + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable toFlowable() { + if (this instanceof FuseToFlowable) { + return ((FuseToFlowable)this).fuseToFlowable(); + } + return RxJavaPlugins.onAssembly(new MaybeToFlowable(this)); + } + + /** + * Converts this Maybe into an Observable instance composing disposal + * through. + *
+ *
Scheduler:
+ *
{@code toObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Observable instance + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable toObservable() { + if (this instanceof FuseToObservable) { + return ((FuseToObservable)this).fuseToObservable(); + } + return RxJavaPlugins.onAssembly(new MaybeToObservable(this)); + } + + /** + * Converts this Maybe into a Single instance composing disposal + * through and turning an empty Maybe into a Single that emits the given + * value through onSuccess. + *
+ *
Scheduler:
+ *
{@code toSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param defaultValue the default item to signal in Single if this Maybe is empty + * @return the new Single instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single toSingle(T defaultValue) { + ObjectHelper.requireNonNull(defaultValue, "defaultValue is null"); + return RxJavaPlugins.onAssembly(new MaybeToSingle(this, defaultValue)); + } + + /** + * Converts this Maybe into a Single instance composing disposal + * through and turning an empty Maybe into a signal of NoSuchElementException. + *
+ *
Scheduler:
+ *
{@code toSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Single instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single toSingle() { + return RxJavaPlugins.onAssembly(new MaybeToSingle(this, null)); + } + + /** + * Returns a Maybe instance that if this Maybe emits an error, it will emit an onComplete + * and swallow the throwable. + *
+ *
Scheduler:
+ *
{@code onErrorComplete} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Maybe instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe onErrorComplete() { + return onErrorComplete(Functions.alwaysTrue()); + } + + /** + * Returns a Maybe instance that if this Maybe emits an error and the predicate returns + * true, it will emit an onComplete and swallow the throwable. + *
+ *
Scheduler:
+ *
{@code onErrorComplete} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param predicate the predicate to call when an Throwable is emitted which should return true + * if the Throwable should be swallowed and replaced with an onComplete. + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe onErrorComplete(final Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + + return RxJavaPlugins.onAssembly(new MaybeOnErrorComplete(this, predicate)); + } + + /** + * Instructs a Maybe to pass control to another {@link MaybeSource} rather than invoking + * {@link MaybeObserver#onError onError} if it encounters an error. + *

+ * + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param next + * the next {@code MaybeSource} that will take over if the source Maybe encounters + * an error + * @return the new Maybe instance + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe onErrorResumeNext(final MaybeSource next) { + ObjectHelper.requireNonNull(next, "next is null"); + return onErrorResumeNext(Functions.justFunction(next)); + } + + /** + * Instructs a Maybe to pass control to another Maybe rather than invoking + * {@link MaybeObserver#onError onError} if it encounters an error. + *

+ * + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param resumeFunction + * a function that returns a MaybeSource that will take over if the source Maybe encounters + * an error + * @return the new Maybe instance + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe onErrorResumeNext(Function> resumeFunction) { + ObjectHelper.requireNonNull(resumeFunction, "resumeFunction is null"); + return RxJavaPlugins.onAssembly(new MaybeOnErrorNext(this, resumeFunction, true)); + } + + /** + * Instructs a Maybe to emit an item (returned by a specified function) rather than invoking + * {@link MaybeObserver#onError onError} if it encounters an error. + *

+ * + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorReturn} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param valueSupplier + * a function that returns a single value that will be emitted as success value + * the current Maybe signals an onError event + * @return the new Maybe instance + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe onErrorReturn(Function valueSupplier) { + ObjectHelper.requireNonNull(valueSupplier, "valueSupplier is null"); + return RxJavaPlugins.onAssembly(new MaybeOnErrorReturn(this, valueSupplier)); + } + + /** + * Instructs a Maybe to emit an item (returned by a specified function) rather than invoking + * {@link MaybeObserver#onError onError} if it encounters an error. + *

+ * + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorReturnItem} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the value that is emitted as onSuccess in case this Maybe signals an onError + * @return the new Maybe instance + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe onErrorReturnItem(final T item) { + ObjectHelper.requireNonNull(item, "item is null"); + return onErrorReturn(Functions.justFunction(item)); + } + + /** + * Instructs a Maybe to pass control to another MaybeSource rather than invoking + * {@link MaybeObserver#onError onError} if it encounters an {@link Exception}. + *

+ * This differs from {@link #onErrorResumeNext} in that this one does not handle {@link Throwable} + * or {@link Error} but lets those continue through. + *

+ * + *

+ * You can use this to prevent exceptions from propagating or to supply fallback data should exceptions be + * encountered. + *

+ *
Scheduler:
+ *
{@code onExceptionResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param next + * the next MaybeSource that will take over if the source Maybe encounters + * an exception + * @return the new Maybe instance + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe onExceptionResumeNext(final MaybeSource next) { + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new MaybeOnErrorNext(this, Functions.justFunction(next), false)); + } + + /** + * Nulls out references to the upstream producer and downstream MaybeObserver if + * the sequence is terminated or downstream calls dispose(). + *
+ *
Scheduler:
+ *
{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return a Maybe which nulls out references to the upstream producer and downstream MaybeObserver if + * the sequence is terminated or downstream calls dispose() + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe onTerminateDetach() { + return RxJavaPlugins.onAssembly(new MaybeDetach(this)); + } + + /** + * Returns a Flowable that repeats the sequence of items emitted by the source Maybe indefinitely. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure.
+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Flowable that emits the items emitted by the source Maybe repeatedly and in sequence + * @see ReactiveX operators documentation: Repeat + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeat() { + return repeat(Long.MAX_VALUE); + } + + /** + * Returns a Flowable that repeats the sequence of items emitted by the source Maybe at most + * {@code count} times. + *

+ * + *

+ *
Backpressure:
+ *
This operator honors downstream backpressure.
+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param times + * the number of times the source Maybe items are repeated, a count of 0 will yield an empty + * sequence + * @return a Flowable that repeats the sequence of items emitted by the source Maybe at most + * {@code count} times + * @throws IllegalArgumentException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: Repeat + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeat(long times) { + return toFlowable().repeat(times); + } + + /** + * Returns a Flowable that repeats the sequence of items emitted by the source Maybe until + * the provided stop function returns true. + *

+ * + *

+ *
Backpressure:
+ *
This operator honors downstream backpressure.
+ *
Scheduler:
+ *
{@code repeatUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param stop + * a boolean supplier that is called when the current Flowable completes and unless it returns + * false, the current Flowable is resubscribed + * @return the new Flowable instance + * @throws NullPointerException + * if {@code stop} is null + * @see ReactiveX operators documentation: Repeat + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeatUntil(BooleanSupplier stop) { + return toFlowable().repeatUntil(stop); + } + + /** + * Returns a Flowable that emits the same values as the source Publisher with the exception of an + * {@code onComplete}. An {@code onComplete} notification from the source will result in the emission of + * a {@code void} item to the Publisher provided as an argument to the {@code notificationHandler} + * function. If that Publisher calls {@code onComplete} or {@code onError} then {@code repeatWhen} will + * call {@code onComplete} or {@code onError} on the child subscription. Otherwise, this Publisher will + * resubscribe to the source Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. + * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
+ *
Scheduler:
+ *
{@code repeatWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param handler + * receives a Publisher of notifications with which a user can complete or error, aborting the repeat. + * @return the source Publisher modified with repeat logic + * @see ReactiveX operators documentation: Repeat + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeatWhen(final Function, ? extends Publisher> handler) { + return toFlowable().repeatWhen(handler); + } + + /** + * Returns a Maybe that mirrors the source Maybe, resubscribing to it if it calls {@code onError} + * (infinite retry count). + *

+ * + *

+ * If the source Maybe calls {@link MaybeObserver#onError}, this method will resubscribe to the source + * Maybe rather than propagating the {@code onError} call. + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Maybe instance + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe retry() { + return retry(Long.MAX_VALUE, Functions.alwaysTrue()); + } + + /** + * Returns a Maybe that mirrors the source Maybe, resubscribing to it if it calls {@code onError} + * and the predicate returns true for that specific exception and retry count. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * the predicate that determines if a resubscription may happen in case of a specific exception + * and retry count + * @return the new Maybe instance + * @see #retry() + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe retry(BiPredicate predicate) { + return toFlowable().retry(predicate).singleElement(); + } + + /** + * Returns a Maybe that mirrors the source Maybe, resubscribing to it if it calls {@code onError} + * up to a specified number of retries. + *

+ * + *

+ * If the source Maybe calls {@link MaybeObserver#onError}, this method will resubscribe to the source + * Maybe for a maximum of {@code count} resubscriptions rather than propagating the + * {@code onError} call. + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the number of times to resubscribe if the current Maybe fails + * @return the new Maybe instance + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe retry(long count) { + return retry(count, Functions.alwaysTrue()); + } + + /** + * Retries at most times or until the predicate returns false, whichever happens first. + * + *
+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param times the number of times to resubscribe if the current Maybe fails + * @param predicate the predicate called with the failure Throwable and should return true to trigger a retry. + * @return the new Maybe instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe retry(long times, Predicate predicate) { + return toFlowable().retry(times, predicate).singleElement(); + } + + /** + * Retries the current Maybe if it fails and the predicate returns true. + *
+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate the predicate that receives the failure Throwable and should return true to trigger a retry. + * @return the new Maybe instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe retry(Predicate predicate) { + return retry(Long.MAX_VALUE, predicate); + } + + /** + * Retries until the given stop function returns true. + *
+ *
Scheduler:
+ *
{@code retryUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param stop the function that should return true to stop retrying + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe retryUntil(final BooleanSupplier stop) { + ObjectHelper.requireNonNull(stop, "stop is null"); + return retry(Long.MAX_VALUE, Functions.predicateReverseFor(stop)); + } + + /** + * Returns a Maybe that emits the same values as the source Maybe with the exception of an + * {@code onError}. An {@code onError} notification from the source will result in the emission of a + * {@link Throwable} item to the Publisher provided as an argument to the {@code notificationHandler} + * function. If that Publisher calls {@code onComplete} or {@code onError} then {@code retry} will call + * {@code onComplete} or {@code onError} on the child subscription. Otherwise, this Publisher will + * resubscribe to the source Publisher. + *

+ * + *

+ * Example: + * + * This retries 3 times, each time incrementing the number of seconds it waits. + * + *


+     *  Maybe.create((MaybeEmitter<? super String> s) -> {
+     *      System.out.println("subscribing");
+     *      s.onError(new RuntimeException("always fails"));
+     *  }, BackpressureStrategy.BUFFER).retryWhen(attempts -> {
+     *      return attempts.zipWith(Publisher.range(1, 3), (n, i) -> i).flatMap(i -> {
+     *          System.out.println("delay retry by " + i + " second(s)");
+     *          return Flowable.timer(i, TimeUnit.SECONDS);
+     *      });
+     *  }).blockingForEach(System.out::println);
+     * 
+ * + * Output is: + * + *
 {@code
+     * subscribing
+     * delay retry by 1 second(s)
+     * subscribing
+     * delay retry by 2 second(s)
+     * subscribing
+     * delay retry by 3 second(s)
+     * subscribing
+     * } 
+ *

+ * Note that the inner {@code Publisher} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signalling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code Publisher} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + *

+ * The following example demonstrates how to retry an asynchronous source with a delay: + *


+     * Maybe.timer(1, TimeUnit.SECONDS)
+     *     .doOnSubscribe(s -> System.out.println("subscribing"))
+     *     .map(v -> { throw new RuntimeException(); })
+     *     .retryWhen(errors -> {
+     *         AtomicInteger counter = new AtomicInteger();
+     *         return errors
+     *                   .takeWhile(e -> counter.getAndIncrement() != 3)
+     *                   .flatMap(e -> {
+     *                       System.out.println("delay retry by " + counter.get() + " second(s)");
+     *                       return Flowable.timer(counter.get(), TimeUnit.SECONDS);
+     *                   });
+     *     })
+     *     .blockingGet();
+     * 
+ *
+ *
Scheduler:
+ *
{@code retryWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param handler + * receives a Publisher of notifications with which a user can complete or error, aborting the + * retry + * @return the new Maybe instance + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe retryWhen( + final Function, ? extends Publisher> handler) { + return toFlowable().retryWhen(handler).singleElement(); + } + + /** + * Subscribes to a Maybe and ignores {@code onSuccess} and {@code onComplete} emissions. + *

+ * If the Maybe emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the Maybe has finished sending them + * @see ReactiveX operators documentation: Subscribe + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe() { + return subscribe(Functions.emptyConsumer(), Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to a Maybe and provides a callback to handle the items it emits. + *

+ * If the Maybe emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onSuccess + * the {@code Consumer} you have designed to accept a success value from the Maybe + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the Maybe has finished sending them + * @throws NullPointerException + * if {@code onSuccess} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onSuccess) { + return subscribe(onSuccess, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to a Maybe and provides callbacks to handle the items it emits and any error + * notification it issues. + *
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onSuccess + * the {@code Consumer} you have designed to accept a success value from the Maybe + * @param onError + * the {@code Consumer} you have designed to accept any error notification from the + * Maybe + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the Maybe has finished sending them + * @see ReactiveX operators documentation: Subscribe + * @throws NullPointerException + * if {@code onSuccess} is null, or + * if {@code onError} is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onSuccess, Consumer onError) { + return subscribe(onSuccess, onError, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to a Maybe and provides callbacks to handle the items it emits and any error or + * completion notification it issues. + *
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onSuccess + * the {@code Consumer} you have designed to accept a success value from the Maybe + * @param onError + * the {@code Consumer} you have designed to accept any error notification from the + * Maybe + * @param onComplete + * the {@code Action} you have designed to accept a completion notification from the + * Maybe + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the Maybe has finished sending them + * @throws NullPointerException + * if {@code onSuccess} is null, or + * if {@code onError} is null, or + * if {@code onComplete} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onSuccess, Consumer onError, + Action onComplete) { + ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + return subscribeWith(new MaybeCallbackObserver(onSuccess, onError, onComplete)); + } + + @SchedulerSupport(SchedulerSupport.NONE) + @Override + public final void subscribe(MaybeObserver observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); + + observer = RxJavaPlugins.onSubscribe(this, observer); + + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null MaybeObserver. Please check the handler provided to RxJavaPlugins.setOnMaybeSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); + + try { + subscribeActual(observer); + } catch (NullPointerException ex) { + throw ex; + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + NullPointerException npe = new NullPointerException("subscribeActual failed"); + npe.initCause(ex); + throw npe; + } + } + + /** + * Implement this method in subclasses to handle the incoming {@link MaybeObserver}s. + *

There is no need to call any of the plugin hooks on the current {@code Maybe} instance or + * the {@code MaybeObserver}; all hooks and basic safeguards have been + * applied by {@link #subscribe(MaybeObserver)} before this method gets called. + * @param observer the MaybeObserver to handle, not null + */ + protected abstract void subscribeActual(MaybeObserver observer); + + /** + * Asynchronously subscribes subscribers to this Maybe on the specified {@link Scheduler}. + *

+ * + *

+ *
Scheduler:
+ *
you specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to perform subscription actions on + * @return the new Maybe instance that its subscriptions happen on the specified {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + * @see RxJava Threading Examples + * @see #observeOn + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Maybe subscribeOn(Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new MaybeSubscribeOn(this, scheduler)); + } + + /** + * Subscribes a given MaybeObserver (subclass) to this Maybe and returns the given + * MaybeObserver as is. + *

Usage example: + *


+     * Maybe<Integer> source = Maybe.just(1);
+     * CompositeDisposable composite = new CompositeDisposable();
+     *
+     * DisposableMaybeObserver<Integer> ds = new DisposableMaybeObserver<>() {
+     *     // ...
+     * };
+     *
+     * composite.add(source.subscribeWith(ds));
+     * 
+ *
+ *
Scheduler:
+ *
{@code subscribeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the type of the MaybeObserver to use and return + * @param observer the MaybeObserver (subclass) to use and return, not null + * @return the input {@code subscriber} + * @throws NullPointerException if {@code subscriber} is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final > E subscribeWith(E observer) { + subscribe(observer); + return observer; + } + + /** + * Returns a Maybe that emits the items emitted by the source Maybe or the items of an alternate + * MaybeSource if the current Maybe is empty. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the alternate MaybeSource to subscribe to if the main does not emit any items + * @return a Maybe that emits the items emitted by the source Maybe or the items of an + * alternate MaybeSource if the source Maybe is empty. + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe switchIfEmpty(MaybeSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new MaybeSwitchIfEmpty(this, other)); + } + + /** + * Returns a Single that emits the items emitted by the source Maybe or the item of an alternate + * SingleSource if the current Maybe is empty. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.4 - experimental + * @param other + * the alternate SingleSource to subscribe to if the main does not emit any items + * @return a Single that emits the items emitted by the source Maybe or the item of an + * alternate SingleSource if the source Maybe is empty. + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single switchIfEmpty(SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new MaybeSwitchIfEmptySingle(this, other)); + } + + /** + * Returns a Maybe that emits the items emitted by the source Maybe until a second MaybeSource + * emits an item. + *

+ * + *

+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the MaybeSource whose first emitted item will cause {@code takeUntil} to stop emitting items + * from the source Maybe + * @param + * the type of items emitted by {@code other} + * @return a Maybe that emits the items emitted by the source Maybe until such time as {@code other} emits its first item + * @see ReactiveX operators documentation: TakeUntil + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe takeUntil(MaybeSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new MaybeTakeUntilMaybe(this, other)); + } + + /** + * Returns a Maybe that emits the item emitted by the source Maybe until a second Publisher + * emits an item. + *

+ * + *

+ *
Backpressure:
+ *
The {@code Publisher} is consumed in an unbounded fashion and is cancelled after the first item + * emitted.
+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the Publisher whose first emitted item will cause {@code takeUntil} to stop emitting items + * from the source Publisher + * @param + * the type of items emitted by {@code other} + * @return a Maybe that emits the items emitted by the source Maybe until such time as {@code other} emits its first item + * @see ReactiveX operators documentation: TakeUntil + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe takeUntil(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new MaybeTakeUntilPublisher(this, other)); + } + + /** + * Returns a Maybe that mirrors the source Maybe but applies a timeout policy for each emitted + * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, + * the resulting Maybe terminates and notifies MaybeObservers of a {@code TimeoutException}. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * maximum duration between emitted items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument. + * @return the new Maybe instance + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Maybe timeout(long timeout, TimeUnit timeUnit) { + return timeout(timeout, timeUnit, Schedulers.computation()); + } + + /** + * Returns a Maybe that mirrors the source Maybe but applies a timeout policy for each emitted + * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, + * the source MaybeSource is disposed and resulting Maybe begins instead to mirror a fallback MaybeSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param fallback + * the fallback MaybeSource to use in case of a timeout + * @return the new Maybe instance + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Maybe timeout(long timeout, TimeUnit timeUnit, MaybeSource fallback) { + ObjectHelper.requireNonNull(fallback, "fallback is null"); + return timeout(timeout, timeUnit, Schedulers.computation(), fallback); + } + + /** + * Returns a Maybe that mirrors the source Maybe but applies a timeout policy for each emitted + * item using a specified Scheduler. If the next item isn't emitted within the specified timeout duration + * starting from its predecessor, the source MaybeSource is disposed and resulting Maybe begins instead + * to mirror a fallback MaybeSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param fallback + * the MaybeSource to use as the fallback in case of a timeout + * @param scheduler + * the {@link Scheduler} to run the timeout timers on + * @return the new Maybe instance + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Maybe timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler, MaybeSource fallback) { + ObjectHelper.requireNonNull(fallback, "fallback is null"); + return timeout(timer(timeout, timeUnit, scheduler), fallback); + } + + /** + * Returns a Maybe that mirrors the source Maybe but applies a timeout policy for each emitted + * item, where this policy is governed on a specified Scheduler. If the next item isn't emitted within the + * specified timeout duration starting from its predecessor, the resulting Maybe terminates and + * notifies MaybeObservers of a {@code TimeoutException}. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param scheduler + * the Scheduler to run the timeout timers on + * @return the new Maybe instance + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Maybe timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler) { + return timeout(timer(timeout, timeUnit, scheduler)); + } + + /** + * If the current {@code Maybe} didn't signal an event before the {@code timeoutIndicator} {@link MaybeSource} signals, a + * {@link TimeoutException} is signaled instead. + *
+ *
Scheduler:
+ *
{@code timeout} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the + * @param timeoutIndicator the {@code MaybeSource} that indicates the timeout by signaling onSuccess + * or onComplete. + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe timeout(MaybeSource timeoutIndicator) { + ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); + return RxJavaPlugins.onAssembly(new MaybeTimeoutMaybe(this, timeoutIndicator, null)); + } + + /** + * If the current {@code Maybe} didn't signal an event before the {@code timeoutIndicator} {@link MaybeSource} signals, + * the current {@code Maybe} is disposed and the {@code fallback} {@code MaybeSource} subscribed to + * as a continuation. + *
+ *
Scheduler:
+ *
{@code timeout} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the + * @param timeoutIndicator the {@code MaybeSource} that indicates the timeout by signaling {@code onSuccess} + * or {@code onComplete}. + * @param fallback the {@code MaybeSource} that is subscribed to if the current {@code Maybe} times out + * @return the new Maybe instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe timeout(MaybeSource timeoutIndicator, MaybeSource fallback) { + ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); + ObjectHelper.requireNonNull(fallback, "fallback is null"); + return RxJavaPlugins.onAssembly(new MaybeTimeoutMaybe(this, timeoutIndicator, fallback)); + } + + /** + * If the current {@code Maybe} source didn't signal an event before the {@code timeoutIndicator} {@link Publisher} signals, a + * {@link TimeoutException} is signaled instead. + *
+ *
Backpressure:
+ *
The {@code timeoutIndicator} {@link Publisher} is consumed in an unbounded manner and + * is cancelled after its first item.
+ *
Scheduler:
+ *
{@code timeout} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the + * @param timeoutIndicator the {@code MaybeSource} that indicates the timeout by signaling {@code onSuccess} + * or {@code onComplete}. + * @return the new Maybe instance + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe timeout(Publisher timeoutIndicator) { + ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); + return RxJavaPlugins.onAssembly(new MaybeTimeoutPublisher(this, timeoutIndicator, null)); + } + + /** + * If the current {@code Maybe} didn't signal an event before the {@code timeoutIndicator} {@link Publisher} signals, + * the current {@code Maybe} is disposed and the {@code fallback} {@code MaybeSource} subscribed to + * as a continuation. + *
+ *
Backpressure:
+ *
The {@code timeoutIndicator} {@link Publisher} is consumed in an unbounded manner and + * is cancelled after its first item.
+ *
Scheduler:
+ *
{@code timeout} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the + * @param timeoutIndicator the {@code MaybeSource} that indicates the timeout by signaling {@code onSuccess} + * or {@code onComplete} + * @param fallback the {@code MaybeSource} that is subscribed to if the current {@code Maybe} times out + * @return the new Maybe instance + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe timeout(Publisher timeoutIndicator, MaybeSource fallback) { + ObjectHelper.requireNonNull(timeoutIndicator, "timeoutIndicator is null"); + ObjectHelper.requireNonNull(fallback, "fallback is null"); + return RxJavaPlugins.onAssembly(new MaybeTimeoutPublisher(this, timeoutIndicator, fallback)); + } + + /** + * Returns a Maybe which makes sure when a MaybeObserver disposes the Disposable, + * that call is propagated up on the specified scheduler. + *
+ *
Scheduler:
+ *
{@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.
+ *
+ * @param scheduler the target scheduler where to execute the disposal + * @return the new Maybe instance + * @throws NullPointerException if scheduler is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Maybe unsubscribeOn(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new MaybeUnsubscribeOn(this, scheduler)); + } + + /** + * Waits until this and the other MaybeSource signal a success value then applies the given BiFunction + * to those values and emits the BiFunction's resulting value to downstream. + * + * + * + *

If either this or the other MaybeSource is empty or signals an error, the resulting Maybe will + * terminate immediately and dispose the other source. + * + *

+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the {@code other} MaybeSource + * @param + * the type of items emitted by the resulting Maybe + * @param other + * the other MaybeSource + * @param zipper + * a function that combines the pairs of items from the two MaybeSources to generate the items to + * be emitted by the resulting Maybe + * @return the new Maybe instance + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe zipWith(MaybeSource other, BiFunction zipper) { + ObjectHelper.requireNonNull(other, "other is null"); + return zip(this, other, zipper); + } + + // ------------------------------------------------------------------ + // Test helper + // ------------------------------------------------------------------ + + /** + * Creates a TestObserver and subscribes + * it to this Maybe. + *
+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new TestObserver instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final TestObserver test() { + TestObserver to = new TestObserver(); + subscribe(to); + return to; + } + + /** + * Creates a TestObserver optionally in cancelled state, then subscribes it to this Maybe. + *
+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param cancelled if true, the TestObserver will be cancelled before subscribing to this + * Maybe. + * @return the new TestObserver instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final TestObserver test(boolean cancelled) { + TestObserver to = new TestObserver(); + + if (cancelled) { + to.cancel(); + } + + subscribe(to); + return to; + } +} diff --git a/src/main/java/io/reactivex/MaybeConverter.java b/src/main/java/io/reactivex/MaybeConverter.java new file mode 100755 index 0000000..c997399 --- /dev/null +++ b/src/main/java/io/reactivex/MaybeConverter.java @@ -0,0 +1,35 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Maybe#as} operator to turn a Maybe into another + * value fluently. + *

History: 2.1.7 - experimental + * @param the upstream type + * @param the output type + * @since 2.2 + */ +public interface MaybeConverter { + /** + * Applies a function to the upstream Maybe and returns a converted value of type {@code R}. + * + * @param upstream the upstream Maybe instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Maybe upstream); +} diff --git a/src/main/java/io/reactivex/MaybeEmitter.java b/src/main/java/io/reactivex/MaybeEmitter.java new file mode 100755 index 0000000..4819ce3 --- /dev/null +++ b/src/main/java/io/reactivex/MaybeEmitter.java @@ -0,0 +1,107 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Cancellable; + +/** + * Abstraction over an RxJava {@link MaybeObserver} that allows associating + * a resource with it. + *

+ * All methods are safe to call from multiple threads, but note that there is no guarantee + * whose terminal event will win and get delivered to the downstream. + *

+ * Calling {@link #onSuccess(Object)} or {@link #onComplete()} multiple times has no effect. + * Calling {@link #onError(Throwable)} multiple times or after the other two will route the + * exception into the global error handler via {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + *

+ * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls {@link #onSuccess(Object)}, + * {@link #onError(Throwable)}, {@link #onComplete()} or when {@link #tryOnError(Throwable)} succeeds. + *

+ * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + *

+ * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * + * @param the value type to emit + */ +public interface MaybeEmitter { + + /** + * Signal a success value. + * @param t the value, not null + */ + void onSuccess(@NonNull T t); + + /** + * Signal an exception. + * @param t the exception, not null + */ + void onError(@NonNull Throwable t); + + /** + * Signal the completion. + */ + void onComplete(); + + /** + * Sets a Disposable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param d the disposable, null is allowed + */ + void setDisposable(@Nullable Disposable d); + + /** + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param c the cancellable resource, null is allowed + */ + void setCancellable(@Nullable Cancellable c); + + /** + * Returns true if the downstream disposed the sequence or the + * emitter was terminated via {@link #onSuccess(Object)}, {@link #onError(Throwable)}, + * {@link #onComplete} or a + * successful {@link #tryOnError(Throwable)}. + *

This method is thread-safe. + * @return true if the downstream disposed the sequence or the emitter was terminated + */ + boolean isDisposed(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

+ * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); +} diff --git a/src/main/java/io/reactivex/MaybeObserver.java b/src/main/java/io/reactivex/MaybeObserver.java new file mode 100755 index 0000000..7678448 --- /dev/null +++ b/src/main/java/io/reactivex/MaybeObserver.java @@ -0,0 +1,92 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; + +/** + * Provides a mechanism for receiving push-based notification of a single value, an error or completion without any value. + *

+ * When a {@code MaybeObserver} is subscribed to a {@link MaybeSource} through the {@link MaybeSource#subscribe(MaybeObserver)} method, + * the {@code MaybeSource} calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows + * disposing the sequence at any time. A well-behaved + * {@code MaybeSource} will call a {@code MaybeObserver}'s {@link #onSuccess(Object)}, {@link #onError(Throwable)} + * or {@link #onComplete()} method exactly once as they are considered mutually exclusive terminal signals. + *

+ * Calling the {@code MaybeObserver}'s method must happen in a serialized fashion, that is, they must not + * be invoked concurrently by multiple threads in an overlapping fashion and the invocation pattern must + * adhere to the following protocol: + *

    onSubscribe (onSuccess | onError | onComplete)?
+ *

+ * Note that unlike with the {@code Observable} protocol, {@link #onComplete()} is not called after the success item has been + * signalled via {@link #onSuccess(Object)}. + *

+ * Subscribing a {@code MaybeObserver} to multiple {@code MaybeSource}s is not recommended. If such reuse + * happens, it is the duty of the {@code MaybeObserver} implementation to be ready to receive multiple calls to + * its methods and ensure proper concurrent behavior of its business logic. + *

+ * Calling {@link #onSubscribe(Disposable)}, {@link #onSuccess(Object)} or {@link #onError(Throwable)} with a + * {@code null} argument is forbidden. + *

+ * The implementations of the {@code onXXX} methods should avoid throwing runtime exceptions other than the following cases: + *

    + *
  • If the argument is {@code null}, the methods can throw a {@code NullPointerException}. + * Note though that RxJava prevents {@code null}s to enter into the flow and thus there is generally no + * need to check for nulls in flows assembled from standard sources and intermediate operators. + *
  • + *
  • If there is a fatal error (such as {@code VirtualMachineError}).
  • + *
+ * @see ReactiveX documentation: Observable + * @param + * the type of item the MaybeObserver expects to observe + * @since 2.0 + */ +public interface MaybeObserver { + + /** + * Provides the MaybeObserver with the means of cancelling (disposing) the + * connection (channel) with the Maybe in both + * synchronous (from within {@code onSubscribe(Disposable)} itself) and asynchronous manner. + * @param d the Disposable instance whose {@link Disposable#dispose()} can + * be called anytime to cancel the connection + */ + void onSubscribe(@NonNull Disposable d); + + /** + * Notifies the MaybeObserver with one item and that the {@link Maybe} has finished sending + * push-based notifications. + *

+ * The {@link Maybe} will not call this method if it calls {@link #onError}. + * + * @param t + * the item emitted by the Maybe + */ + void onSuccess(@NonNull T t); + + /** + * Notifies the MaybeObserver that the {@link Maybe} has experienced an error condition. + *

+ * If the {@link Maybe} calls this method, it will not thereafter call {@link #onSuccess}. + * + * @param e + * the exception encountered by the Maybe + */ + void onError(@NonNull Throwable e); + + /** + * Called once the deferred computation completes normally. + */ + void onComplete(); +} diff --git a/src/main/java/io/reactivex/MaybeOnSubscribe.java b/src/main/java/io/reactivex/MaybeOnSubscribe.java new file mode 100755 index 0000000..035a001 --- /dev/null +++ b/src/main/java/io/reactivex/MaybeOnSubscribe.java @@ -0,0 +1,33 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * A functional interface that has a {@code subscribe()} method that receives + * an instance of a {@link MaybeEmitter} instance that allows pushing + * an event in a cancellation-safe manner. + * + * @param the value type pushed + */ +public interface MaybeOnSubscribe { + + /** + * Called for each MaybeObserver that subscribes. + * @param emitter the safe emitter instance, never null + * @throws Exception on error + */ + void subscribe(@NonNull MaybeEmitter emitter) throws Exception; +} + diff --git a/src/main/java/io/reactivex/MaybeOperator.java b/src/main/java/io/reactivex/MaybeOperator.java new file mode 100755 index 0000000..9e7a54f --- /dev/null +++ b/src/main/java/io/reactivex/MaybeOperator.java @@ -0,0 +1,32 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Interface to map/wrap a downstream observer to an upstream observer. + * + * @param the value type of the downstream + * @param the value type of the upstream + */ +public interface MaybeOperator { + /** + * Applies a function to the child MaybeObserver and returns a new parent MaybeObserver. + * @param observer the child MaybeObserver instance + * @return the parent MaybeObserver instance + * @throws Exception on failure + */ + @NonNull + MaybeObserver apply(@NonNull MaybeObserver observer) throws Exception; +} diff --git a/src/main/java/io/reactivex/MaybeSource.java b/src/main/java/io/reactivex/MaybeSource.java new file mode 100755 index 0000000..4694eb0 --- /dev/null +++ b/src/main/java/io/reactivex/MaybeSource.java @@ -0,0 +1,35 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Represents a basic {@link Maybe} source base interface, + * consumable via an {@link MaybeObserver}. + *

+ * This class also serves the base type for custom operators wrapped into + * Maybe via {@link Maybe#create(MaybeOnSubscribe)}. + * + * @param the element type + * @since 2.0 + */ +public interface MaybeSource { + + /** + * Subscribes the given MaybeObserver to this MaybeSource instance. + * @param observer the MaybeObserver, not null + * @throws NullPointerException if {@code observer} is null + */ + void subscribe(@NonNull MaybeObserver observer); +} diff --git a/src/main/java/io/reactivex/MaybeTransformer.java b/src/main/java/io/reactivex/MaybeTransformer.java new file mode 100755 index 0000000..1526913 --- /dev/null +++ b/src/main/java/io/reactivex/MaybeTransformer.java @@ -0,0 +1,33 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Interface to compose Maybes. + * + * @param the upstream value type + * @param the downstream value type + */ +public interface MaybeTransformer { + /** + * Applies a function to the upstream Maybe and returns a MaybeSource with + * optionally different element type. + * @param upstream the upstream Maybe instance + * @return the transformed MaybeSource instance + */ + @NonNull + MaybeSource apply(@NonNull Maybe upstream); +} diff --git a/src/main/java/io/reactivex/Notification.java b/src/main/java/io/reactivex/Notification.java new file mode 100755 index 0000000..84ceb38 --- /dev/null +++ b/src/main/java/io/reactivex/Notification.java @@ -0,0 +1,161 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.NotificationLite; + +/** + * Represents the reactive signal types: onNext, onError and onComplete and + * holds their parameter values (a value, a Throwable, nothing). + * @param the value type + */ +public final class Notification { + + final Object value; + + /** Not meant to be implemented externally. */ + private Notification(Object value) { + this.value = value; + } + + /** + * Returns true if this notification is an onComplete signal. + * @return true if this notification is an onComplete signal + */ + public boolean isOnComplete() { + return value == null; + } + + /** + * Returns true if this notification is an onError signal and + * {@link #getError()} returns the contained Throwable. + * @return true if this notification is an onError signal + * @see #getError() + */ + public boolean isOnError() { + return NotificationLite.isError(value); + } + + /** + * Returns true if this notification is an onNext signal and + * {@link #getValue()} returns the contained value. + * @return true if this notification is an onNext signal + * @see #getValue() + */ + public boolean isOnNext() { + Object o = value; + return o != null && !NotificationLite.isError(o); + } + + /** + * Returns the contained value if this notification is an onNext + * signal, null otherwise. + * @return the value contained or null + * @see #isOnNext() + */ + @SuppressWarnings("unchecked") + @Nullable + public T getValue() { + Object o = value; + if (o != null && !NotificationLite.isError(o)) { + return (T)value; + } + return null; + } + + /** + * Returns the container Throwable error if this notification is an onError + * signal, null otherwise. + * @return the Throwable error contained or null + * @see #isOnError() + */ + @Nullable + public Throwable getError() { + Object o = value; + if (NotificationLite.isError(o)) { + return NotificationLite.getError(o); + } + return null; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Notification) { + Notification n = (Notification) obj; + return ObjectHelper.equals(value, n.value); + } + return false; + } + + @Override + public int hashCode() { + Object o = value; + return o != null ? o.hashCode() : 0; + } + + @Override + public String toString() { + Object o = value; + if (o == null) { + return "OnCompleteNotification"; + } + if (NotificationLite.isError(o)) { + return "OnErrorNotification[" + NotificationLite.getError(o) + "]"; + } + return "OnNextNotification[" + value + "]"; + } + + /** + * Constructs an onNext notification containing the given value. + * @param the value type + * @param value the value to carry around in the notification, not null + * @return the new Notification instance + * @throws NullPointerException if value is null + */ + @NonNull + public static Notification createOnNext(@NonNull T value) { + ObjectHelper.requireNonNull(value, "value is null"); + return new Notification(value); + } + + /** + * Constructs an onError notification containing the error. + * @param the value type + * @param error the error Throwable to carry around in the notification, not null + * @return the new Notification instance + * @throws NullPointerException if error is null + */ + @NonNull + public static Notification createOnError(@NonNull Throwable error) { + ObjectHelper.requireNonNull(error, "error is null"); + return new Notification(NotificationLite.error(error)); + } + + /** + * Returns the empty and stateless shared instance of a notification representing + * an onComplete signal. + * @param the target value type + * @return the shared Notification instance representing an onComplete signal + */ + @SuppressWarnings("unchecked") + @NonNull + public static Notification createOnComplete() { + return (Notification)COMPLETE; + } + + /** The singleton instance for createOnComplete. */ + static final Notification COMPLETE = new Notification(null); +} diff --git a/src/main/java/io/reactivex/Observable.java b/src/main/java/io/reactivex/Observable.java new file mode 100755 index 0000000..d5a6098 --- /dev/null +++ b/src/main/java/io/reactivex/Observable.java @@ -0,0 +1,15513 @@ +/** + * 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; + +import java.util.*; +import java.util.concurrent.*; + +import org.reactivestreams.Publisher; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.fuseable.ScalarCallable; +import io.reactivex.internal.observers.*; +import io.reactivex.internal.operators.flowable.*; +import io.reactivex.internal.operators.mixed.*; +import io.reactivex.internal.operators.observable.*; +import io.reactivex.internal.util.*; +import io.reactivex.observables.*; +import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.*; + +/** + * The Observable class is the non-backpressured, optionally multi-valued base reactive class that + * offers factory methods, intermediate operators and the ability to consume synchronous + * and/or asynchronous reactive dataflows. + *

+ * Many operators in the class accept {@code ObservableSource}(s), the base reactive interface + * for such non-backpressured flows, which {@code Observable} itself implements as well. + *

+ * The Observable's operators, by default, run with a buffer size of 128 elements (see {@link Flowable#bufferSize()}), + * that can be overridden globally via the system parameter {@code rx2.buffer-size}. Most operators, however, have + * overloads that allow setting their internal buffer size explicitly. + *

+ * The documentation for this class makes use of marble diagrams. The following legend explains these diagrams: + *

+ * + *

+ * The design of this class was derived from the + * Reactive Streams design and specification + * by removing any backpressure-related infrastructure and implementation detail, replacing the + * {@code org.reactivestreams.Subscription} with {@link Disposable} as the primary means to dispose of + * a flow. + *

+ * The {@code Observable} follows the protocol + *


+ *      onSubscribe onNext* (onError | onComplete)?
+ * 
+ * where + * the stream can be disposed through the {@code Disposable} instance provided to consumers through + * {@code Observer.onSubscribe}. + *

+ * Unlike the {@code Observable} of version 1.x, {@link #subscribe(Observer)} does not allow external disposal + * of a subscription and the {@code Observer} instance is expected to expose such capability. + *

Example: + *


+ * Disposable d = Observable.just("Hello world!")
+ *     .delay(1, TimeUnit.SECONDS)
+ *     .subscribeWith(new DisposableObserver<String>() {
+ *         @Override public void onStart() {
+ *             System.out.println("Start!");
+ *         }
+ *         @Override public void onNext(String t) {
+ *             System.out.println(t);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *         }
+ *     });
+ *
+ * Thread.sleep(500);
+ * // the sequence can now be disposed via dispose()
+ * d.dispose();
+ * 
+ * + * @param + * the type of the items emitted by the Observable + * @see Flowable + * @see DisposableObserver + */ +public abstract class Observable implements ObservableSource { + + /** + * Mirrors the one ObservableSource in an Iterable of several ObservableSources that first either emits an item or sends + * a termination notification. + *

+ * + *

+ *
Scheduler:
+ *
{@code amb} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element type + * @param sources + * an Iterable of ObservableSource sources competing to react first. A subscription to each source will + * occur in the same order as in the Iterable. + * @return an Observable that emits the same sequence as whichever of the source ObservableSources first + * emitted an item or sent a termination notification + * @see ReactiveX operators documentation: Amb + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable amb(Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new ObservableAmb(null, sources)); + } + + /** + * Mirrors the one ObservableSource in an array of several ObservableSources that first either emits an item or sends + * a termination notification. + *

+ * + *

+ *
Scheduler:
+ *
{@code ambArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element type + * @param sources + * an array of ObservableSource sources competing to react first. A subscription to each source will + * occur in the same order as in the array. + * @return an Observable that emits the same sequence as whichever of the source ObservableSources first + * emitted an item or sent a termination notification + * @see ReactiveX operators documentation: Amb + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable ambArray(ObservableSource... sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + int len = sources.length; + if (len == 0) { + return empty(); + } + if (len == 1) { + return (Observable)wrap(sources[0]); + } + return RxJavaPlugins.onAssembly(new ObservableAmb(sources, null)); + } + + /** + * Returns the default 'island' size or capacity-increment hint for unbounded buffers. + *

Delegates to {@link Flowable#bufferSize} but is public for convenience. + *

The value can be overridden via system parameter {@code rx2.buffer-size} + * before the {@link Flowable} class is loaded. + * @return the default 'island' size or capacity-increment hint + */ + public static int bufferSize() { + return Flowable.bufferSize(); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If there are no ObservableSources provided, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Observable + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest(Function combiner, int bufferSize, ObservableSource... sources) { + return combineLatest(sources, combiner, bufferSize); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest(Iterable> sources, + Function combiner) { + return combineLatest(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Observable + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest(Iterable> sources, + Function combiner, int bufferSize) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + + // the queue holds a pair of values so we need to double the capacity + int s = bufferSize << 1; + return RxJavaPlugins.onAssembly(new ObservableCombineLatest(null, sources, combiner, s, false)); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest(ObservableSource[] sources, + Function combiner) { + return combineLatest(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Observable + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest(ObservableSource[] sources, + Function combiner, int bufferSize) { + ObjectHelper.requireNonNull(sources, "sources is null"); + if (sources.length == 0) { + return empty(); + } + ObjectHelper.requireNonNull(combiner, "combiner is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + + // the queue holds a pair of values so we need to double the capacity + int s = bufferSize << 1; + return RxJavaPlugins.onAssembly(new ObservableCombineLatest(sources, null, combiner, s, false)); + } + + /** + * Combines two source ObservableSources by emitting an item that aggregates the latest values of each of the + * source ObservableSources each time an item is received from either of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the combined output type + * @param source1 + * the first source ObservableSource + * @param source2 + * the second source ObservableSource + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest( + ObservableSource source1, ObservableSource source2, + BiFunction combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return combineLatest(Functions.toFunction(combiner), bufferSize(), source1, source2); + } + + /** + * Combines three source ObservableSources by emitting an item that aggregates the latest values of each of the + * source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the combined output type + * @param source1 + * the first source ObservableSource + * @param source2 + * the second source ObservableSource + * @param source3 + * the third source ObservableSource + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, + Function3 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return combineLatest(Functions.toFunction(combiner), bufferSize(), source1, source2, source3); + } + + /** + * Combines four source ObservableSources by emitting an item that aggregates the latest values of each of the + * source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the combined output type + * @param source1 + * the first source ObservableSource + * @param source2 + * the second source ObservableSource + * @param source3 + * the third source ObservableSource + * @param source4 + * the fourth source ObservableSource + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, ObservableSource source4, + Function4 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return combineLatest(Functions.toFunction(combiner), bufferSize(), source1, source2, source3, source4); + } + + /** + * Combines five source ObservableSources by emitting an item that aggregates the latest values of each of the + * source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the combined output type + * @param source1 + * the first source ObservableSource + * @param source2 + * the second source ObservableSource + * @param source3 + * the third source ObservableSource + * @param source4 + * the fourth source ObservableSource + * @param source5 + * the fifth source ObservableSource + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, ObservableSource source4, + ObservableSource source5, + Function5 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + return combineLatest(Functions.toFunction(combiner), bufferSize(), source1, source2, source3, source4, source5); + } + + /** + * Combines six source ObservableSources by emitting an item that aggregates the latest values of each of the + * source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the element type of the sixth source + * @param the combined output type + * @param source1 + * the first source ObservableSource + * @param source2 + * the second source ObservableSource + * @param source3 + * the third source ObservableSource + * @param source4 + * the fourth source ObservableSource + * @param source5 + * the fifth source ObservableSource + * @param source6 + * the sixth source ObservableSource + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, ObservableSource source4, + ObservableSource source5, ObservableSource source6, + Function6 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + return combineLatest(Functions.toFunction(combiner), bufferSize(), source1, source2, source3, source4, source5, source6); + } + + /** + * Combines seven source ObservableSources by emitting an item that aggregates the latest values of each of the + * source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the element type of the sixth source + * @param the element type of the seventh source + * @param the combined output type + * @param source1 + * the first source ObservableSource + * @param source2 + * the second source ObservableSource + * @param source3 + * the third source ObservableSource + * @param source4 + * the fourth source ObservableSource + * @param source5 + * the fifth source ObservableSource + * @param source6 + * the sixth source ObservableSource + * @param source7 + * the seventh source ObservableSource + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, ObservableSource source4, + ObservableSource source5, ObservableSource source6, + ObservableSource source7, + Function7 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + return combineLatest(Functions.toFunction(combiner), bufferSize(), source1, source2, source3, source4, source5, source6, source7); + } + + /** + * Combines eight source ObservableSources by emitting an item that aggregates the latest values of each of the + * source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the element type of the sixth source + * @param the element type of the seventh source + * @param the element type of the eighth source + * @param the combined output type + * @param source1 + * the first source ObservableSource + * @param source2 + * the second source ObservableSource + * @param source3 + * the third source ObservableSource + * @param source4 + * the fourth source ObservableSource + * @param source5 + * the fifth source ObservableSource + * @param source6 + * the sixth source ObservableSource + * @param source7 + * the seventh source ObservableSource + * @param source8 + * the eighth source ObservableSource + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, ObservableSource source4, + ObservableSource source5, ObservableSource source6, + ObservableSource source7, ObservableSource source8, + Function8 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + return combineLatest(Functions.toFunction(combiner), bufferSize(), source1, source2, source3, source4, source5, source6, source7, source8); + } + + /** + * Combines nine source ObservableSources by emitting an item that aggregates the latest values of each of the + * source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the first source + * @param the element type of the second source + * @param the element type of the third source + * @param the element type of the fourth source + * @param the element type of the fifth source + * @param the element type of the sixth source + * @param the element type of the seventh source + * @param the element type of the eighth source + * @param the element type of the ninth source + * @param the combined output type + * @param source1 + * the first source ObservableSource + * @param source2 + * the second source ObservableSource + * @param source3 + * the third source ObservableSource + * @param source4 + * the fourth source ObservableSource + * @param source5 + * the fifth source ObservableSource + * @param source6 + * the sixth source ObservableSource + * @param source7 + * the seventh source ObservableSource + * @param source8 + * the eighth source ObservableSource + * @param source9 + * the ninth source ObservableSource + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatest( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, ObservableSource source4, + ObservableSource source5, ObservableSource source6, + ObservableSource source7, ObservableSource source8, + ObservableSource source9, + Function9 combiner) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + ObjectHelper.requireNonNull(source9, "source9 is null"); + return combineLatest(Functions.toFunction(combiner), bufferSize(), source1, source2, source3, source4, source5, source6, source7, source8, source9); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function. + *

+ * + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatestDelayError(ObservableSource[] sources, + Function combiner) { + return combineLatestDelayError(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function and delays any error from the sources until + * all source ObservableSources terminate. + *

+ * + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If there are no ObservableSources provided, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Observable + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatestDelayError(Function combiner, + int bufferSize, ObservableSource... sources) { + return combineLatestDelayError(sources, combiner, bufferSize); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function and delays any error from the sources until + * all source ObservableSources terminate. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Observable + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatestDelayError(ObservableSource[] sources, + Function combiner, int bufferSize) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + if (sources.length == 0) { + return empty(); + } + // the queue holds a pair of values so we need to double the capacity + int s = bufferSize << 1; + return RxJavaPlugins.onAssembly(new ObservableCombineLatest(sources, null, combiner, s, true)); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function and delays any error from the sources until + * all source ObservableSources terminate. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatestDelayError(Iterable> sources, + Function combiner) { + return combineLatestDelayError(sources, combiner, bufferSize()); + } + + /** + * Combines a collection of source ObservableSources by emitting an item that aggregates the latest values of each of + * the source ObservableSources each time an item is received from any of the source ObservableSources, where this + * aggregation is defined by a specified function and delays any error from the sources until + * all source ObservableSources terminate. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + *

+ * If any of the sources never produces an item but only terminates (normally or with an error), the + * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). + * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code combineLatestDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the common base type of source values + * @param + * the result type + * @param sources + * the collection of source ObservableSources + * @param combiner + * the aggregation function used to combine the items emitted by the source ObservableSources + * @param bufferSize + * the internal buffer size and prefetch amount applied to every source Observable + * @return an Observable that emits items that are the result of combining the items emitted by the source + * ObservableSources by means of the given aggregation function + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable combineLatestDelayError(Iterable> sources, + Function combiner, int bufferSize) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + + // the queue holds a pair of values so we need to double the capacity + int s = bufferSize << 1; + return RxJavaPlugins.onAssembly(new ObservableCombineLatest(null, sources, combiner, s, true)); + } + + /** + * Concatenates elements of each ObservableSource provided via an Iterable sequence into a single sequence + * of elements without interleaving them. + *

+ * + *

+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the common value type of the sources + * @param sources the Iterable sequence of ObservableSources + * @return the new Observable instance + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concat(Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return fromIterable(sources).concatMapDelayError((Function)Functions.identity(), bufferSize(), false); + } + + /** + * Returns an Observable that emits the items emitted by each of the ObservableSources emitted by the source + * ObservableSource, one after the other, without interleaving them. + *

+ * + *

+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * an ObservableSource that emits ObservableSources + * @return an Observable that emits items all of the items emitted by the ObservableSources emitted by + * {@code ObservableSources}, one after the other, without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concat(ObservableSource> sources) { + return concat(sources, bufferSize()); + } + + /** + * Returns an Observable that emits the items emitted by each of the ObservableSources emitted by the source + * ObservableSource, one after the other, without interleaving them. + *

+ * + *

+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * an ObservableSource that emits ObservableSources + * @param prefetch + * the number of ObservableSources to prefetch from the sources sequence. + * @return an Observable that emits items all of the items emitted by the ObservableSources emitted by + * {@code ObservableSources}, one after the other, without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concat(ObservableSource> sources, int prefetch) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMap(sources, Functions.identity(), prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Returns an Observable that emits the items emitted by two ObservableSources, one after the other, without + * interleaving them. + *

+ * + *

+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * an ObservableSource to be concatenated + * @param source2 + * an ObservableSource to be concatenated + * @return an Observable that emits items emitted by the two source ObservableSources, one after the other, + * without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concat(ObservableSource source1, ObservableSource source2) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return concatArray(source1, source2); + } + + /** + * Returns an Observable that emits the items emitted by three ObservableSources, one after the other, without + * interleaving them. + *

+ * + *

+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * an ObservableSource to be concatenated + * @param source2 + * an ObservableSource to be concatenated + * @param source3 + * an ObservableSource to be concatenated + * @return an Observable that emits items emitted by the three source ObservableSources, one after the other, + * without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concat( + ObservableSource source1, ObservableSource source2, + ObservableSource source3) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return concatArray(source1, source2, source3); + } + + /** + * Returns an Observable that emits the items emitted by four ObservableSources, one after the other, without + * interleaving them. + *

+ * + *

+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * an ObservableSource to be concatenated + * @param source2 + * an ObservableSource to be concatenated + * @param source3 + * an ObservableSource to be concatenated + * @param source4 + * an ObservableSource to be concatenated + * @return an Observable that emits items emitted by the four source ObservableSources, one after the other, + * without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concat( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, ObservableSource source4) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return concatArray(source1, source2, source3, source4); + } + + /** + * Concatenates a variable number of ObservableSource sources. + *

+ * Note: named this way because of overload conflict with concat(ObservableSource<ObservableSource>) + *

+ * + *

+ *
Scheduler:
+ *
{@code concatArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the array of sources + * @param the common base value type + * @return the new Observable instance + * @throws NullPointerException if sources is null + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatArray(ObservableSource... sources) { + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + return wrap((ObservableSource)sources[0]); + } + return RxJavaPlugins.onAssembly(new ObservableConcatMap(fromArray(sources), Functions.identity(), bufferSize(), ErrorMode.BOUNDARY)); + } + + /** + * Concatenates a variable number of ObservableSource sources and delays errors from any of them + * till all terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatArrayDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param sources the array of sources + * @param the common base value type + * @return the new Observable instance + * @throws NullPointerException if sources is null + */ + @SuppressWarnings({ "unchecked" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatArrayDelayError(ObservableSource... sources) { + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + return (Observable)wrap(sources[0]); + } + return concatDelayError(fromArray(sources)); + } + + /** + * Concatenates an array of ObservableSources eagerly into a single stream of values. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them + * in order, each one after the previous one completes. + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources an array of ObservableSources that need to be eagerly concatenated + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatArrayEager(ObservableSource... sources) { + return concatArrayEager(bufferSize(), bufferSize(), sources); + } + + /** + * Concatenates an array of ObservableSources eagerly into a single stream of values. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them + * in order, each one after the previous one completes. + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources an array of ObservableSources that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscriptions at a time, Integer.MAX_VALUE + * is interpreted as indication to subscribe to all sources at once + * @param prefetch the number of elements to prefetch from each ObservableSource source + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatArrayEager(int maxConcurrency, int prefetch, ObservableSource... sources) { + return fromArray(sources).concatMapEagerDelayError((Function)Functions.identity(), maxConcurrency, prefetch, false); + } + + /** + * Concatenates an array of {@link ObservableSource}s eagerly into a single stream of values + * and delaying any errors until all sources terminate. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source {@code ObservableSource}s. The operator buffers the values emitted by these {@code ObservableSource}s + * and then drains them in order, each one after the previous one completes. + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources an array of {@code ObservableSource}s that need to be eagerly concatenated + * @return the new Observable instance with the specified concatenation behavior + * @since 2.2.1 - experimental + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatArrayEagerDelayError(ObservableSource... sources) { + return concatArrayEagerDelayError(bufferSize(), bufferSize(), sources); + } + + /** + * Concatenates an array of {@link ObservableSource}s eagerly into a single stream of values + * and delaying any errors until all sources terminate. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source {@code ObservableSource}s. The operator buffers the values emitted by these {@code ObservableSource}s + * and then drains them in order, each one after the previous one completes. + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources an array of {@code ObservableSource}s that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscriptions at a time, Integer.MAX_VALUE + * is interpreted as indication to subscribe to all sources at once + * @param prefetch the number of elements to prefetch from each {@code ObservableSource} source + * @return the new Observable instance with the specified concatenation behavior + * @since 2.2.1 - experimental + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatArrayEagerDelayError(int maxConcurrency, int prefetch, ObservableSource... sources) { + return fromArray(sources).concatMapEagerDelayError((Function)Functions.identity(), maxConcurrency, prefetch, true); + } + + /** + * Concatenates the Iterable sequence of ObservableSources into a single sequence by subscribing to each ObservableSource, + * one after the other, one at a time and delays any errors till the all inner ObservableSources terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources the Iterable sequence of ObservableSources + * @return the new ObservableSource with the concatenating behavior + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatDelayError(Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return concatDelayError(fromIterable(sources)); + } + + /** + * Concatenates the ObservableSource sequence of ObservableSources into a single sequence by subscribing to each inner ObservableSource, + * one after the other, one at a time and delays any errors till the all inner and the outer ObservableSources terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources the ObservableSource sequence of ObservableSources + * @return the new ObservableSource with the concatenating behavior + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatDelayError(ObservableSource> sources) { + return concatDelayError(sources, bufferSize(), true); + } + + /** + * Concatenates the ObservableSource sequence of ObservableSources into a single sequence by subscribing to each inner ObservableSource, + * one after the other, one at a time and delays any errors till the all inner and the outer ObservableSources terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources the ObservableSource sequence of ObservableSources + * @param prefetch the number of elements to prefetch from the outer ObservableSource + * @param tillTheEnd if true exceptions from the outer and all inner ObservableSources are delayed to the end + * if false, exception from the outer ObservableSource is delayed till the current ObservableSource terminates + * @return the new ObservableSource with the concatenating behavior + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatDelayError(ObservableSource> sources, int prefetch, boolean tillTheEnd) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch is null"); + return RxJavaPlugins.onAssembly(new ObservableConcatMap(sources, Functions.identity(), prefetch, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY)); + } + + /** + * Concatenates an ObservableSource sequence of ObservableSources eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * emitted source ObservableSources as they are observed. The operator buffers the values emitted by these + * ObservableSources and then drains them in order, each one after the previous one completes. + *

+ * + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of ObservableSources that need to be eagerly concatenated + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatEager(ObservableSource> sources) { + return concatEager(sources, bufferSize(), bufferSize()); + } + + /** + * Concatenates an ObservableSource sequence of ObservableSources eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * emitted source ObservableSources as they are observed. The operator buffers the values emitted by these + * ObservableSources and then drains them in order, each one after the previous one completes. + *

+ * + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of ObservableSources that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrently running inner ObservableSources; Integer.MAX_VALUE + * is interpreted as all inner ObservableSources can be active at the same time + * @param prefetch the number of elements to prefetch from each inner ObservableSource source + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatEager(ObservableSource> sources, int maxConcurrency, int prefetch) { + return wrap(sources).concatMapEager((Function)Functions.identity(), maxConcurrency, prefetch); + } + + /** + * Concatenates a sequence of ObservableSources eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them + * in order, each one after the previous one completes. + *

+ * + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of ObservableSources that need to be eagerly concatenated + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatEager(Iterable> sources) { + return concatEager(sources, bufferSize(), bufferSize()); + } + + /** + * Concatenates a sequence of ObservableSources eagerly into a single stream of values. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them + * in order, each one after the previous one completes. + *

+ * + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of ObservableSources that need to be eagerly concatenated + * @param maxConcurrency the maximum number of concurrently running inner ObservableSources; Integer.MAX_VALUE + * is interpreted as all inner ObservableSources can be active at the same time + * @param prefetch the number of elements to prefetch from each inner ObservableSource source + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable concatEager(Iterable> sources, int maxConcurrency, int prefetch) { + return fromIterable(sources).concatMapEagerDelayError((Function)Functions.identity(), maxConcurrency, prefetch, false); + } + + /** + * Provides an API (via a cold Observable) that bridges the reactive world with the callback-style world. + *

+ * Example: + *


+     * Observable.<Event>create(emitter -> {
+     *     Callback listener = new Callback() {
+     *         @Override
+     *         public void onEvent(Event e) {
+     *             emitter.onNext(e);
+     *             if (e.isLast()) {
+     *                 emitter.onComplete();
+     *             }
+     *         }
+     *
+     *         @Override
+     *         public void onFailure(Exception e) {
+     *             emitter.onError(e);
+     *         }
+     *     };
+     *
+     *     AutoCloseable c = api.someMethod(listener);
+     *
+     *     emitter.setCancellable(c::close);
+     *
+     * });
+     * 
+ *

+ * + *

+ * You should call the ObservableEmitter's onNext, onError and onComplete methods in a serialized fashion. The + * rest of its methods are thread-safe. + *

+ *
Scheduler:
+ *
{@code create} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type + * @param source the emitter that is called when an Observer subscribes to the returned {@code Observable} + * @return the new Observable instance + * @see ObservableOnSubscribe + * @see ObservableEmitter + * @see Cancellable + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable create(ObservableOnSubscribe source) { + ObjectHelper.requireNonNull(source, "source is null"); + return RxJavaPlugins.onAssembly(new ObservableCreate(source)); + } + + /** + * Returns an Observable that calls an ObservableSource factory to create an ObservableSource for each new Observer + * that subscribes. That is, for each subscriber, the actual ObservableSource that subscriber observes is + * determined by the factory function. + *

+ * + *

+ * The defer Observer allows you to defer or delay emitting items from an ObservableSource until such time as an + * Observer subscribes to the ObservableSource. This allows an {@link Observer} to easily obtain updates or a + * refreshed version of the sequence. + *

+ *
Scheduler:
+ *
{@code defer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param supplier + * the ObservableSource factory function to invoke for each {@link Observer} that subscribes to the + * resulting ObservableSource + * @param + * the type of the items emitted by the ObservableSource + * @return an Observable whose {@link Observer}s' subscriptions trigger an invocation of the given + * ObservableSource factory function + * @see ReactiveX operators documentation: Defer + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable defer(Callable> supplier) { + ObjectHelper.requireNonNull(supplier, "supplier is null"); + return RxJavaPlugins.onAssembly(new ObservableDefer(supplier)); + } + + /** + * Returns an Observable that emits no items to the {@link Observer} and immediately invokes its + * {@link Observer#onComplete onComplete} method. + *

+ * + *

+ *
Scheduler:
+ *
{@code empty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of the items (ostensibly) emitted by the ObservableSource + * @return an Observable that emits no items to the {@link Observer} but immediately invokes the + * {@link Observer}'s {@link Observer#onComplete() onComplete} method + * @see ReactiveX operators documentation: Empty + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Observable empty() { + return RxJavaPlugins.onAssembly((Observable) ObservableEmpty.INSTANCE); + } + + /** + * Returns an Observable that invokes an {@link Observer}'s {@link Observer#onError onError} method when the + * Observer subscribes to it. + *

+ * + *

+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param errorSupplier + * a Callable factory to return a Throwable for each individual Observer + * @param + * the type of the items (ostensibly) emitted by the ObservableSource + * @return an Observable that invokes the {@link Observer}'s {@link Observer#onError onError} method when + * the Observer subscribes to it + * @see ReactiveX operators documentation: Throw + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable error(Callable errorSupplier) { + ObjectHelper.requireNonNull(errorSupplier, "errorSupplier is null"); + return RxJavaPlugins.onAssembly(new ObservableError(errorSupplier)); + } + + /** + * Returns an Observable that invokes an {@link Observer}'s {@link Observer#onError onError} method when the + * Observer subscribes to it. + *

+ * + *

+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param exception + * the particular Throwable to pass to {@link Observer#onError onError} + * @param + * the type of the items (ostensibly) emitted by the ObservableSource + * @return an Observable that invokes the {@link Observer}'s {@link Observer#onError onError} method when + * the Observer subscribes to it + * @see ReactiveX operators documentation: Throw + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable error(final Throwable exception) { + ObjectHelper.requireNonNull(exception, "exception is null"); + return error(Functions.justCallable(exception)); + } + + /** + * Converts an Array into an ObservableSource that emits the items in the Array. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param items + * the array of elements + * @param + * the type of items in the Array and the type of items to be emitted by the resulting ObservableSource + * @return an Observable that emits each item in the source Array + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public static Observable fromArray(T... items) { + ObjectHelper.requireNonNull(items, "items is null"); + if (items.length == 0) { + return empty(); + } + if (items.length == 1) { + return just(items[0]); + } + return RxJavaPlugins.onAssembly(new ObservableFromArray(items)); + } + + /** + * Returns an Observable that, when an observer subscribes to it, invokes a function you specify and then + * emits the value returned from that function. + *

+ * + *

+ * This allows you to defer the execution of the function you specify until an observer subscribes to the + * ObservableSource. That is to say, it makes the function "lazy." + *

+ *
Scheduler:
+ *
{@code fromCallable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link Observer#onError(Throwable)}, + * except when the downstream has disposed this {@code Observable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + *
+ *
+ * @param supplier + * a function, the execution of which should be deferred; {@code fromCallable} will invoke this + * function only when an observer subscribes to the ObservableSource that {@code fromCallable} returns + * @param + * the type of the item emitted by the ObservableSource + * @return an Observable whose {@link Observer}s' subscriptions trigger an invocation of the given function + * @see #defer(Callable) + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable fromCallable(Callable supplier) { + ObjectHelper.requireNonNull(supplier, "supplier is null"); + return RxJavaPlugins.onAssembly(new ObservableFromCallable(supplier)); + } + + /** + * Converts a {@link Future} into an ObservableSource. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into an ObservableSource that emits the + * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} + * method. + *

+ * Important note: This ObservableSource is blocking; you cannot dispose it. + *

+ * Unlike 1.x, disposing the Observable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureObservableSource.doOnDispose(() -> future.cancel(true));}. + *

+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting ObservableSource + * @return an Observable that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable fromFuture(Future future) { + ObjectHelper.requireNonNull(future, "future is null"); + return RxJavaPlugins.onAssembly(new ObservableFromFuture(future, 0L, null)); + } + + /** + * Converts a {@link Future} into an ObservableSource, with a timeout on the Future. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into an ObservableSource that emits the + * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} + * method. + *

+ * Unlike 1.x, disposing the Observable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureObservableSource.doOnDispose(() -> future.cancel(true));}. + *

+ * Important note: This ObservableSource is blocking; you cannot dispose it. + *

+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param timeout + * the maximum time to wait before calling {@code get} + * @param unit + * the {@link TimeUnit} of the {@code timeout} argument + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting ObservableSource + * @return an Observable that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable fromFuture(Future future, long timeout, TimeUnit unit) { + ObjectHelper.requireNonNull(future, "future is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + return RxJavaPlugins.onAssembly(new ObservableFromFuture(future, timeout, unit)); + } + + /** + * Converts a {@link Future} into an ObservableSource, with a timeout on the Future. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into an ObservableSource that emits the + * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} + * method. + *

+ * Unlike 1.x, disposing the Observable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureObservableSource.doOnDispose(() -> future.cancel(true));}. + *

+ * Important note: This ObservableSource is blocking; you cannot dispose it. + *

+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param timeout + * the maximum time to wait before calling {@code get} + * @param unit + * the {@link TimeUnit} of the {@code timeout} argument + * @param scheduler + * the {@link Scheduler} to wait for the Future on. Use a Scheduler such as + * {@link Schedulers#io()} that can block and wait on the Future + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting ObservableSource + * @return an Observable that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Observable fromFuture(Future future, long timeout, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + Observable o = fromFuture(future, timeout, unit); + return o.subscribeOn(scheduler); + } + + /** + * Converts a {@link Future}, operating on a specified {@link Scheduler}, into an ObservableSource. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into an ObservableSource that emits the + * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} + * method. + *

+ * Unlike 1.x, disposing the Observable won't cancel the future. If necessary, one can use composition to achieve the + * cancellation effect: {@code futureObservableSource.doOnDispose(() -> future.cancel(true));}. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param future + * the source {@link Future} + * @param scheduler + * the {@link Scheduler} to wait for the Future on. Use a Scheduler such as + * {@link Schedulers#io()} that can block and wait on the Future + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting ObservableSource + * @return an Observable that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Observable fromFuture(Future future, Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + Observable o = fromFuture(future); + return o.subscribeOn(scheduler); + } + + /** + * Converts an {@link Iterable} sequence into an ObservableSource that emits the items in the sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source + * the source {@link Iterable} sequence + * @param + * the type of items in the {@link Iterable} sequence and the type of items to be emitted by the + * resulting ObservableSource + * @return an Observable that emits each item in the source {@link Iterable} sequence + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable fromIterable(Iterable source) { + ObjectHelper.requireNonNull(source, "source is null"); + return RxJavaPlugins.onAssembly(new ObservableFromIterable(source)); + } + + /** + * Converts an arbitrary Reactive Streams Publisher into an Observable. + *

+ * + *

+ * The {@link Publisher} must follow the + * Reactive Streams specification. + * Violating the specification may result in undefined behavior. + *

+ * If possible, use {@link #create(ObservableOnSubscribe)} to create a + * source-like {@code Observable} instead. + *

+ * Note that even though {@link Publisher} appears to be a functional interface, it + * is not recommended to implement it through a lambda as the specification requires + * state management that is not achievable with a stateless lambda. + *

+ *
Backpressure:
+ *
The source {@code publisher} is consumed in an unbounded fashion without applying any + * backpressure to it.
+ *
Scheduler:
+ *
{@code fromPublisher} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the flow + * @param publisher the Publisher to convert + * @return the new Observable instance + * @throws NullPointerException if publisher is null + * @see #create(ObservableOnSubscribe) + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable fromPublisher(Publisher publisher) { + ObjectHelper.requireNonNull(publisher, "publisher is null"); + return RxJavaPlugins.onAssembly(new ObservableFromPublisher(publisher)); + } + + /** + * Returns a cold, synchronous and stateless generator of values. + *

+ * + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the generated value type + * @param generator the Consumer called whenever a particular downstream Observer has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event. Signalling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @return the new Observable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable generate(final Consumer> generator) { + ObjectHelper.requireNonNull(generator, "generator is null"); + return generate(Functions.nullSupplier(), + ObservableInternalHelper.simpleGenerator(generator), Functions.emptyConsumer()); + } + + /** + * Returns a cold, synchronous and stateful generator of values. + *

+ * + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the per-Observer state + * @param the generated value type + * @param initialState the Callable to generate the initial state for each Observer + * @param generator the Consumer called with the current state whenever a particular downstream Observer has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event. Signalling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @return the new Observable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable generate(Callable initialState, final BiConsumer> generator) { + ObjectHelper.requireNonNull(generator, "generator is null"); + return generate(initialState, ObservableInternalHelper.simpleBiGenerator(generator), Functions.emptyConsumer()); + } + + /** + * Returns a cold, synchronous and stateful generator of values. + *

+ * + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the per-Observer state + * @param the generated value type + * @param initialState the Callable to generate the initial state for each Observer + * @param generator the Consumer called with the current state whenever a particular downstream Observer has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event. Signalling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @param disposeState the Consumer that is called with the current state when the generator + * terminates the sequence or it gets disposed + * @return the new Observable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable generate( + final Callable initialState, + final BiConsumer> generator, + Consumer disposeState) { + ObjectHelper.requireNonNull(generator, "generator is null"); + return generate(initialState, ObservableInternalHelper.simpleBiGenerator(generator), disposeState); + } + + /** + * Returns a cold, synchronous and stateful generator of values. + *

+ * + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the per-Observer state + * @param the generated value type + * @param initialState the Callable to generate the initial state for each Observer + * @param generator the Function called with the current state whenever a particular downstream Observer has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event and should return a (new) state for + * the next invocation. Signalling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable generate(Callable initialState, BiFunction, S> generator) { + return generate(initialState, generator, Functions.emptyConsumer()); + } + + /** + * Returns a cold, synchronous and stateful generator of values. + *

+ * + *

+ * Note that the {@link Emitter#onNext}, {@link Emitter#onError} and + * {@link Emitter#onComplete} methods provided to the function via the {@link Emitter} instance should be called synchronously, + * never concurrently and only while the function body is executing. Calling them from multiple threads + * or outside the function call is not supported and leads to an undefined behavior. + *

+ *
Scheduler:
+ *
{@code generate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the per-Observer state + * @param the generated value type + * @param initialState the Callable to generate the initial state for each Observer + * @param generator the Function called with the current state whenever a particular downstream Observer has + * requested a value. The callback then should call {@code onNext}, {@code onError} or + * {@code onComplete} to signal a value or a terminal event and should return a (new) state for + * the next invocation. Signalling multiple {@code onNext} + * in a call will make the operator signal {@code IllegalStateException}. + * @param disposeState the Consumer that is called with the current state when the generator + * terminates the sequence or it gets disposed + * @return the new Observable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable generate(Callable initialState, BiFunction, S> generator, + Consumer disposeState) { + ObjectHelper.requireNonNull(initialState, "initialState is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(disposeState, "disposeState is null"); + return RxJavaPlugins.onAssembly(new ObservableGenerate(initialState, generator, disposeState)); + } + + /** + * Returns an Observable that emits a {@code 0L} after the {@code initialDelay} and ever increasing numbers + * after each {@code period} of time thereafter. + *

+ * + *

+ *
Scheduler:
+ *
{@code interval} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param initialDelay + * the initial delay time to wait before emitting the first value of 0L + * @param period + * the period of time between emissions of the subsequent numbers + * @param unit + * the time unit for both {@code initialDelay} and {@code period} + * @return an Observable that emits a 0L after the {@code initialDelay} and ever increasing numbers after + * each {@code period} of time thereafter + * @see ReactiveX operators documentation: Interval + * @since 1.0.12 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Observable interval(long initialDelay, long period, TimeUnit unit) { + return interval(initialDelay, period, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits a {@code 0L} after the {@code initialDelay} and ever increasing numbers + * after each {@code period} of time thereafter, on a specified {@link Scheduler}. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param initialDelay + * the initial delay time to wait before emitting the first value of 0L + * @param period + * the period of time between emissions of the subsequent numbers + * @param unit + * the time unit for both {@code initialDelay} and {@code period} + * @param scheduler + * the Scheduler on which the waiting happens and items are emitted + * @return an Observable that emits a 0L after the {@code initialDelay} and ever increasing numbers after + * each {@code period} of time thereafter, while running on the given Scheduler + * @see ReactiveX operators documentation: Interval + * @since 1.0.12 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Observable interval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + + return RxJavaPlugins.onAssembly(new ObservableInterval(Math.max(0L, initialDelay), Math.max(0L, period), unit, scheduler)); + } + + /** + * Returns an Observable that emits a sequential number every specified interval of time. + *

+ * + *

+ *
Scheduler:
+ *
{@code interval} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param period + * the period size in time units (see below) + * @param unit + * time units to use for the interval size + * @return an Observable that emits a sequential number each time interval + * @see ReactiveX operators documentation: Interval + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Observable interval(long period, TimeUnit unit) { + return interval(period, period, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits a sequential number every specified interval of time, on a + * specified Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param period + * the period size in time units (see below) + * @param unit + * time units to use for the interval size + * @param scheduler + * the Scheduler to use for scheduling the items + * @return an Observable that emits a sequential number each time interval + * @see ReactiveX operators documentation: Interval + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Observable interval(long period, TimeUnit unit, Scheduler scheduler) { + return interval(period, period, unit, scheduler); + } + + /** + * Signals a range of long values, the first after some initial delay and the rest periodically after. + *

+ * The sequence completes immediately after the last value (start + count - 1) has been reached. + *

+ * + *

+ *
Scheduler:
+ *
{@code intervalRange} by default operates on the {@link Schedulers#computation() computation} {@link Scheduler}.
+ *
+ * @param start that start value of the range + * @param count the number of values to emit in total, if zero, the operator emits an onComplete after the initial delay. + * @param initialDelay the initial delay before signalling the first value (the start) + * @param period the period between subsequent values + * @param unit the unit of measure of the initialDelay and period amounts + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Observable intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit) { + return intervalRange(start, count, initialDelay, period, unit, Schedulers.computation()); + } + + /** + * Signals a range of long values, the first after some initial delay and the rest periodically after. + *

+ * The sequence completes immediately after the last value (start + count - 1) has been reached. + *

+ * *

+ *
Scheduler:
+ *
you provide the {@link Scheduler}.
+ *
+ * @param start that start value of the range + * @param count the number of values to emit in total, if zero, the operator emits an onComplete after the initial delay. + * @param initialDelay the initial delay before signalling the first value (the start) + * @param period the period between subsequent values + * @param unit the unit of measure of the initialDelay and period amounts + * @param scheduler the target scheduler where the values and terminal signals will be emitted + * @return the new Observable instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Observable intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } + + if (count == 0L) { + return Observable.empty().delay(initialDelay, unit, scheduler); + } + + long end = start + (count - 1); + if (start > 0 && end < 0) { + throw new IllegalArgumentException("Overflow! start + count is bigger than Long.MAX_VALUE"); + } + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + + return RxJavaPlugins.onAssembly(new ObservableIntervalRange(start, end, Math.max(0L, initialDelay), Math.max(0L, period), unit, scheduler)); + } + + /** + * Returns an Observable that signals the given (constant reference) item and then completes. + *

+ * + *

+ * Note that the item is taken and re-emitted as is and not computed by any means by {@code just}. Use {@link #fromCallable(Callable)} + * to generate a single item on demand (when {@code Observer}s subscribe to it). + *

+ * See the multi-parameter overloads of {@code just} to emit more than one (constant reference) items one after the other. + * Use {@link #fromArray(Object...)} to emit an arbitrary number of items that are known upfront. + *

+ * To emit the items of an {@link Iterable} sequence (such as a {@link List}), use {@link #fromIterable(Iterable)}. + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the item to emit + * @param + * the type of that item + * @return an Observable that emits {@code value} as a single item and then completes + * @see ReactiveX operators documentation: Just + * @see #just(Object, Object) + * @see #fromCallable(Callable) + * @see #fromArray(Object...) + * @see #fromIterable(Iterable) + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item) { + ObjectHelper.requireNonNull(item, "item is null"); + return RxJavaPlugins.onAssembly(new ObservableJust(item)); + } + + /** + * Converts two items into an ObservableSource that emits those items. + *

+ * + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param + * the type of these items + * @return an Observable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item1, T item2) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + + return fromArray(item1, item2); + } + + /** + * Converts three items into an ObservableSource that emits those items. + *

+ * + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param + * the type of these items + * @return an Observable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item1, T item2, T item3) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + + return fromArray(item1, item2, item3); + } + + /** + * Converts four items into an ObservableSource that emits those items. + *

+ * + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param + * the type of these items + * @return an Observable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item1, T item2, T item3, T item4) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + + return fromArray(item1, item2, item3, item4); + } + + /** + * Converts five items into an ObservableSource that emits those items. + *

+ * + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param + * the type of these items + * @return an Observable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item1, T item2, T item3, T item4, T item5) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + + return fromArray(item1, item2, item3, item4, item5); + } + + /** + * Converts six items into an ObservableSource that emits those items. + *

+ * + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param + * the type of these items + * @return an Observable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6); + } + + /** + * Converts seven items into an ObservableSource that emits those items. + *

+ * + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param item7 + * seventh item + * @param + * the type of these items + * @return an Observable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6, item7); + } + + /** + * Converts eight items into an ObservableSource that emits those items. + *

+ * + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param item7 + * seventh item + * @param item8 + * eighth item + * @param + * the type of these items + * @return an Observable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6, item7, item8); + } + + /** + * Converts nine items into an ObservableSource that emits those items. + *

+ * + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param item7 + * seventh item + * @param item8 + * eighth item + * @param item9 + * ninth item + * @param + * the type of these items + * @return an Observable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9); + } + + /** + * Converts ten items into an ObservableSource that emits those items. + *

+ * + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item1 + * first item + * @param item2 + * second item + * @param item3 + * third item + * @param item4 + * fourth item + * @param item5 + * fifth item + * @param item6 + * sixth item + * @param item7 + * seventh item + * @param item8 + * eighth item + * @param item9 + * ninth item + * @param item10 + * tenth item + * @param + * the type of these items + * @return an Observable that emits each item + * @see ReactiveX operators documentation: Just + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9, T item10) { + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); + ObjectHelper.requireNonNull(item10, "item10 is null"); + + return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10); + } + + /** + * Flattens an Iterable of ObservableSources into one ObservableSource, without any transformation, while limiting the + * number of concurrent subscriptions to these ObservableSources. + *

+ * + *

+ * You can combine the items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable, int, int)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of ObservableSources + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @param bufferSize + * the number of items to prefetch from each inner ObservableSource + * @return an Observable that emits items that are the result of flattening the items emitted by the + * ObservableSources in the Iterable + * @throws IllegalArgumentException + * if {@code maxConcurrent} is less than or equal to 0 + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Iterable, int, int) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable merge(Iterable> sources, int maxConcurrency, int bufferSize) { + return fromIterable(sources).flatMap((Function)Functions.identity(), false, maxConcurrency, bufferSize); + } + + /** + * Flattens an Iterable of ObservableSources into one ObservableSource, without any transformation, while limiting the + * number of concurrent subscriptions to these ObservableSources. + *

+ * + *

+ * You can combine the items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code mergeArray} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(int, int, ObservableSource...)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the array of ObservableSources + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @param bufferSize + * the number of items to prefetch from each inner ObservableSource + * @return an Observable that emits items that are the result of flattening the items emitted by the + * ObservableSources in the Iterable + * @throws IllegalArgumentException + * if {@code maxConcurrent} is less than or equal to 0 + * @see ReactiveX operators documentation: Merge + * @see #mergeArrayDelayError(int, int, ObservableSource...) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeArray(int maxConcurrency, int bufferSize, ObservableSource... sources) { + return fromArray(sources).flatMap((Function)Functions.identity(), false, maxConcurrency, bufferSize); + } + + /** + * Flattens an Iterable of ObservableSources into one ObservableSource, without any transformation. + *

+ * + *

+ * You can combine the items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of ObservableSources + * @return an Observable that emits items that are the result of flattening the items emitted by the + * ObservableSources in the Iterable + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Iterable) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable merge(Iterable> sources) { + return fromIterable(sources).flatMap((Function)Functions.identity()); + } + + /** + * Flattens an Iterable of ObservableSources into one ObservableSource, without any transformation, while limiting the + * number of concurrent subscriptions to these ObservableSources. + *

+ * + *

+ * You can combine the items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable, int)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of ObservableSources + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @return an Observable that emits items that are the result of flattening the items emitted by the + * ObservableSources in the Iterable + * @throws IllegalArgumentException + * if {@code maxConcurrent} is less than or equal to 0 + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(Iterable, int) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable merge(Iterable> sources, int maxConcurrency) { + return fromIterable(sources).flatMap((Function)Functions.identity(), maxConcurrency); + } + + /** + * Flattens an ObservableSource that emits ObservableSources into a single ObservableSource that emits the items emitted by + * those ObservableSources, without any transformation. + *

+ * + *

+ * You can combine the items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * an ObservableSource that emits ObservableSources + * @return an Observable that emits items that are the result of flattening the ObservableSources emitted by the + * {@code source} ObservableSource + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(ObservableSource) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Observable merge(ObservableSource> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new ObservableFlatMap(sources, Functions.identity(), false, Integer.MAX_VALUE, bufferSize())); + } + + /** + * Flattens an ObservableSource that emits ObservableSources into a single ObservableSource that emits the items emitted by + * those ObservableSources, without any transformation, while limiting the maximum number of concurrent + * subscriptions to these ObservableSources. + *

+ * + *

+ * You can combine the items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource, int)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * an ObservableSource that emits ObservableSources + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @return an Observable that emits items that are the result of flattening the ObservableSources emitted by the + * {@code source} ObservableSource + * @throws IllegalArgumentException + * if {@code maxConcurrent} is less than or equal to 0 + * @see ReactiveX operators documentation: Merge + * @since 1.1.0 + * @see #mergeDelayError(ObservableSource, int) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable merge(ObservableSource> sources, int maxConcurrency) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + return RxJavaPlugins.onAssembly(new ObservableFlatMap(sources, Functions.identity(), false, maxConcurrency, bufferSize())); + } + + /** + * Flattens two ObservableSources into a single ObservableSource, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource, ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param source1 + * an ObservableSource to be merged + * @param source2 + * an ObservableSource to be merged + * @return an Observable that emits all of the items emitted by the source ObservableSources + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(ObservableSource, ObservableSource) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable merge(ObservableSource source1, ObservableSource source2) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return fromArray(source1, source2).flatMap((Function)Functions.identity(), false, 2); + } + + /** + * Flattens three ObservableSources into a single ObservableSource, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource, ObservableSource, ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param source1 + * an ObservableSource to be merged + * @param source2 + * an ObservableSource to be merged + * @param source3 + * an ObservableSource to be merged + * @return an Observable that emits all of the items emitted by the source ObservableSources + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(ObservableSource, ObservableSource, ObservableSource) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable merge(ObservableSource source1, ObservableSource source2, ObservableSource source3) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return fromArray(source1, source2, source3).flatMap((Function)Functions.identity(), false, 3); + } + + /** + * Flattens four ObservableSources into a single ObservableSource, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(ObservableSource, ObservableSource, ObservableSource, ObservableSource)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param source1 + * an ObservableSource to be merged + * @param source2 + * an ObservableSource to be merged + * @param source3 + * an ObservableSource to be merged + * @param source4 + * an ObservableSource to be merged + * @return an Observable that emits all of the items emitted by the source ObservableSources + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(ObservableSource, ObservableSource, ObservableSource, ObservableSource) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable merge( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, ObservableSource source4) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return fromArray(source1, source2, source3, source4).flatMap((Function)Functions.identity(), false, 4); + } + + /** + * Flattens an Array of ObservableSources into one ObservableSource, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code merge} method. + *

+ *
Scheduler:
+ *
{@code mergeArray} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code ObservableSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Observable} terminates with that {@code Throwable} and all other source {@code ObservableSource}s are disposed. + * If more than one {@code ObservableSource} signals an error, the resulting {@code Observable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Observable} has been disposed or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeArrayDelayError(ObservableSource...)} to merge sources and terminate only when all source {@code ObservableSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common element base type + * @param sources + * the array of ObservableSources + * @return an Observable that emits all of the items emitted by the ObservableSources in the Array + * @see ReactiveX operators documentation: Merge + * @see #mergeArrayDelayError(ObservableSource...) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeArray(ObservableSource... sources) { + return fromArray(sources).flatMap((Function)Functions.identity(), sources.length); + } + + /** + * Flattens an Iterable of ObservableSources into one ObservableSource, in a way that allows an Observer to receive all + * successfully emitted items from each of the source ObservableSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(ObservableSource)} except that if any of the merged ObservableSources notify of an + * error via {@link Observer#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged ObservableSources have finished emitting items. + *

+ * + *

+ * Even if multiple merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of ObservableSources + * @return an Observable that emits items that are the result of flattening the items emitted by the + * ObservableSources in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeDelayError(Iterable> sources) { + return fromIterable(sources).flatMap((Function)Functions.identity(), true); + } + + /** + * Flattens an Iterable of ObservableSources into one ObservableSource, in a way that allows an Observer to receive all + * successfully emitted items from each of the source ObservableSources without being interrupted by an error + * notification from one of them, while limiting the number of concurrent subscriptions to these ObservableSources. + *

+ * This behaves like {@link #merge(ObservableSource)} except that if any of the merged ObservableSources notify of an + * error via {@link Observer#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged ObservableSources have finished emitting items. + *

+ * + *

+ * Even if multiple merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of ObservableSources + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @param bufferSize + * the number of items to prefetch from each inner ObservableSource + * @return an Observable that emits items that are the result of flattening the items emitted by the + * ObservableSources in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeDelayError(Iterable> sources, int maxConcurrency, int bufferSize) { + return fromIterable(sources).flatMap((Function)Functions.identity(), true, maxConcurrency, bufferSize); + } + + /** + * Flattens an array of ObservableSources into one ObservableSource, in a way that allows an Observer to receive all + * successfully emitted items from each of the source ObservableSources without being interrupted by an error + * notification from one of them, while limiting the number of concurrent subscriptions to these ObservableSources. + *

+ * This behaves like {@link #merge(ObservableSource)} except that if any of the merged ObservableSources notify of an + * error via {@link Observer#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged ObservableSources have finished emitting items. + *

+ * + *

+ * Even if multiple merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeArrayDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the array of ObservableSources + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @param bufferSize + * the number of items to prefetch from each inner ObservableSource + * @return an Observable that emits items that are the result of flattening the items emitted by the + * ObservableSources in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeArrayDelayError(int maxConcurrency, int bufferSize, ObservableSource... sources) { + return fromArray(sources).flatMap((Function)Functions.identity(), true, maxConcurrency, bufferSize); + } + + /** + * Flattens an Iterable of ObservableSources into one ObservableSource, in a way that allows an Observer to receive all + * successfully emitted items from each of the source ObservableSources without being interrupted by an error + * notification from one of them, while limiting the number of concurrent subscriptions to these ObservableSources. + *

+ * This behaves like {@link #merge(ObservableSource)} except that if any of the merged ObservableSources notify of an + * error via {@link Observer#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged ObservableSources have finished emitting items. + *

+ * + *

+ * Even if multiple merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of ObservableSources + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @return an Observable that emits items that are the result of flattening the items emitted by the + * ObservableSources in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeDelayError(Iterable> sources, int maxConcurrency) { + return fromIterable(sources).flatMap((Function)Functions.identity(), true, maxConcurrency); + } + + /** + * Flattens an ObservableSource that emits ObservableSources into one ObservableSource, in a way that allows an Observer to + * receive all successfully emitted items from all of the source ObservableSources without being interrupted by + * an error notification from one of them. + *

+ * This behaves like {@link #merge(ObservableSource)} except that if any of the merged ObservableSources notify of an + * error via {@link Observer#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged ObservableSources have finished emitting items. + *

+ * + *

+ * Even if multiple merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * an ObservableSource that emits ObservableSources + * @return an Observable that emits all of the items emitted by the ObservableSources emitted by the + * {@code source} ObservableSource + * @see ReactiveX operators documentation: Merge + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Observable mergeDelayError(ObservableSource> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new ObservableFlatMap(sources, Functions.identity(), true, Integer.MAX_VALUE, bufferSize())); + } + + /** + * Flattens an ObservableSource that emits ObservableSources into one ObservableSource, in a way that allows an Observer to + * receive all successfully emitted items from all of the source ObservableSources without being interrupted by + * an error notification from one of them, while limiting the + * number of concurrent subscriptions to these ObservableSources. + *

+ * This behaves like {@link #merge(ObservableSource)} except that if any of the merged ObservableSources notify of an + * error via {@link Observer#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged ObservableSources have finished emitting items. + *

+ * + *

+ * Even if multiple merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * an ObservableSource that emits ObservableSources + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @return an Observable that emits all of the items emitted by the ObservableSources emitted by the + * {@code source} ObservableSource + * @see ReactiveX operators documentation: Merge + * @since 2.0 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeDelayError(ObservableSource> sources, int maxConcurrency) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + return RxJavaPlugins.onAssembly(new ObservableFlatMap(sources, Functions.identity(), true, maxConcurrency, bufferSize())); + } + + /** + * Flattens two ObservableSources into one ObservableSource, in a way that allows an Observer to receive all + * successfully emitted items from each of the source ObservableSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(ObservableSource, ObservableSource)} except that if any of the merged ObservableSources + * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will refrain from + * propagating that error notification until all of the merged ObservableSources have finished emitting items. + *

+ * + *

+ * Even if both merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * an ObservableSource to be merged + * @param source2 + * an ObservableSource to be merged + * @return an Observable that emits all of the items that are emitted by the two source ObservableSources + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeDelayError(ObservableSource source1, ObservableSource source2) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return fromArray(source1, source2).flatMap((Function)Functions.identity(), true, 2); + } + + /** + * Flattens three ObservableSources into one ObservableSource, in a way that allows an Observer to receive all + * successfully emitted items from all of the source ObservableSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(ObservableSource, ObservableSource, ObservableSource)} except that if any of the merged + * ObservableSources notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged ObservableSources have finished emitting + * items. + *

+ * + *

+ * Even if multiple merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * an ObservableSource to be merged + * @param source2 + * an ObservableSource to be merged + * @param source3 + * an ObservableSource to be merged + * @return an Observable that emits all of the items that are emitted by the source ObservableSources + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeDelayError(ObservableSource source1, ObservableSource source2, ObservableSource source3) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return fromArray(source1, source2, source3).flatMap((Function)Functions.identity(), true, 3); + } + + /** + * Flattens four ObservableSources into one ObservableSource, in a way that allows an Observer to receive all + * successfully emitted items from all of the source ObservableSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(ObservableSource, ObservableSource, ObservableSource, ObservableSource)} except that if any of + * the merged ObservableSources notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} + * will refrain from propagating that error notification until all of the merged ObservableSources have finished + * emitting items. + *

+ * + *

+ * Even if multiple merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param source1 + * an ObservableSource to be merged + * @param source2 + * an ObservableSource to be merged + * @param source3 + * an ObservableSource to be merged + * @param source4 + * an ObservableSource to be merged + * @return an Observable that emits all of the items that are emitted by the source ObservableSources + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeDelayError( + ObservableSource source1, ObservableSource source2, + ObservableSource source3, ObservableSource source4) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return fromArray(source1, source2, source3, source4).flatMap((Function)Functions.identity(), true, 4); + } + + /** + * Flattens an Iterable of ObservableSources into one ObservableSource, in a way that allows an Observer to receive all + * successfully emitted items from each of the source ObservableSources without being interrupted by an error + * notification from one of them. + *

+ * This behaves like {@link #merge(ObservableSource)} except that if any of the merged ObservableSources notify of an + * error via {@link Observer#onError onError}, {@code mergeDelayError} will refrain from propagating that + * error notification until all of the merged ObservableSources have finished emitting items. + *

+ * + *

+ * Even if multiple merged ObservableSources send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + *

+ *
Scheduler:
+ *
{@code mergeArrayDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element base type + * @param sources + * the Iterable of ObservableSources + * @return an Observable that emits items that are the result of flattening the items emitted by the + * ObservableSources in the Iterable + * @see ReactiveX operators documentation: Merge + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable mergeArrayDelayError(ObservableSource... sources) { + return fromArray(sources).flatMap((Function)Functions.identity(), true, sources.length); + } + + /** + * Returns an Observable that never sends any items or notifications to an {@link Observer}. + *

+ * + *

+ * This ObservableSource is useful primarily for testing purposes. + *

+ *
Scheduler:
+ *
{@code never} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items (not) emitted by the ObservableSource + * @return an Observable that never emits any items or sends any notifications to an {@link Observer} + * @see ReactiveX operators documentation: Never + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Observable never() { + return RxJavaPlugins.onAssembly((Observable) ObservableNever.INSTANCE); + } + + /** + * Returns an Observable that emits a sequence of Integers within a specified range. + *

+ * + *

+ *
Scheduler:
+ *
{@code range} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param start + * the value of the first Integer in the sequence + * @param count + * the number of sequential Integers to generate + * @return an Observable that emits a range of sequential Integers + * @throws IllegalArgumentException + * if {@code count} is less than zero, or if {@code start} + {@code count} − 1 exceeds + * {@code Integer.MAX_VALUE} + * @see ReactiveX operators documentation: Range + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable range(final int start, final int count) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } + if (count == 0) { + return empty(); + } + if (count == 1) { + return just(start); + } + if ((long)start + (count - 1) > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Integer overflow"); + } + return RxJavaPlugins.onAssembly(new ObservableRange(start, count)); + } + + /** + * Returns an Observable that emits a sequence of Longs within a specified range. + *

+ * + *

+ *
Scheduler:
+ *
{@code rangeLong} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param start + * the value of the first Long in the sequence + * @param count + * the number of sequential Longs to generate + * @return an Observable that emits a range of sequential Longs + * @throws IllegalArgumentException + * if {@code count} is less than zero, or if {@code start} + {@code count} − 1 exceeds + * {@code Long.MAX_VALUE} + * @see ReactiveX operators documentation: Range + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable rangeLong(long start, long count) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } + + if (count == 0) { + return empty(); + } + + if (count == 1) { + return just(start); + } + + long end = start + (count - 1); + if (start > 0 && end < 0) { + throw new IllegalArgumentException("Overflow! start + count is bigger than Long.MAX_VALUE"); + } + + return RxJavaPlugins.onAssembly(new ObservableRangeLong(start, count)); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two ObservableSource sequences are the + * same by comparing the items emitted by each ObservableSource pairwise. + *

+ * + *

+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first ObservableSource to compare + * @param source2 + * the second ObservableSource to compare + * @param + * the type of items emitted by each ObservableSource + * @return a Single that emits a Boolean value that indicates whether the two sequences are the same + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(ObservableSource source1, ObservableSource source2) { + return sequenceEqual(source1, source2, ObjectHelper.equalsPredicate(), bufferSize()); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two ObservableSource sequences are the + * same by comparing the items emitted by each ObservableSource pairwise based on the results of a specified + * equality function. + *

+ * + *

+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first ObservableSource to compare + * @param source2 + * the second ObservableSource to compare + * @param isEqual + * a function used to compare items emitted by each ObservableSource + * @param + * the type of items emitted by each ObservableSource + * @return a Single that emits a Boolean value that indicates whether the two ObservableSource two sequences + * are the same according to the specified function + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(ObservableSource source1, ObservableSource source2, + BiPredicate isEqual) { + return sequenceEqual(source1, source2, isEqual, bufferSize()); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two ObservableSource sequences are the + * same by comparing the items emitted by each ObservableSource pairwise based on the results of a specified + * equality function. + *

+ * + *

+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first ObservableSource to compare + * @param source2 + * the second ObservableSource to compare + * @param isEqual + * a function used to compare items emitted by each ObservableSource + * @param bufferSize + * the number of items to prefetch from the first and second source ObservableSource + * @param + * the type of items emitted by each ObservableSource + * @return an Observable that emits a Boolean value that indicates whether the two ObservableSource two sequences + * are the same according to the specified function + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(ObservableSource source1, ObservableSource source2, + BiPredicate isEqual, int bufferSize) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(isEqual, "isEqual is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new ObservableSequenceEqualSingle(source1, source2, isEqual, bufferSize)); + } + + /** + * Returns a Single that emits a Boolean value that indicates whether two ObservableSource sequences are the + * same by comparing the items emitted by each ObservableSource pairwise. + *

+ * + *

+ *
Scheduler:
+ *
{@code sequenceEqual} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param source1 + * the first ObservableSource to compare + * @param source2 + * the second ObservableSource to compare + * @param bufferSize + * the number of items to prefetch from the first and second source ObservableSource + * @param + * the type of items emitted by each ObservableSource + * @return a Single that emits a Boolean value that indicates whether the two sequences are the same + * @see ReactiveX operators documentation: SequenceEqual + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Single sequenceEqual(ObservableSource source1, ObservableSource source2, + int bufferSize) { + return sequenceEqual(source1, source2, ObjectHelper.equalsPredicate(), bufferSize); + } + + /** + * Converts an ObservableSource that emits ObservableSources into an ObservableSource that emits the items emitted by the + * most recently emitted of those ObservableSources. + *

+ * + *

+ * {@code switchOnNext} subscribes to an ObservableSource that emits ObservableSources. Each time it observes one of + * these emitted ObservableSources, the ObservableSource returned by {@code switchOnNext} begins emitting the items + * emitted by that ObservableSource. When a new ObservableSource is emitted, {@code switchOnNext} stops emitting items + * from the earlier-emitted ObservableSource and begins emitting items from the new one. + *

+ * The resulting ObservableSource completes if both the outer ObservableSource and the last inner ObservableSource, if any, complete. + * If the outer ObservableSource signals an onError, the inner ObservableSource is disposed and the error delivered in-sequence. + *

+ *
Scheduler:
+ *
{@code switchOnNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the item type + * @param sources + * the source ObservableSource that emits ObservableSources + * @param bufferSize + * the number of items to prefetch from the inner ObservableSources + * @return an Observable that emits the items emitted by the ObservableSource most recently emitted by the source + * ObservableSource + * @see ReactiveX operators documentation: Switch + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable switchOnNext(ObservableSource> sources, int bufferSize) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMap(sources, Functions.identity(), bufferSize, false)); + } + + /** + * Converts an ObservableSource that emits ObservableSources into an ObservableSource that emits the items emitted by the + * most recently emitted of those ObservableSources. + *

+ * + *

+ * {@code switchOnNext} subscribes to an ObservableSource that emits ObservableSources. Each time it observes one of + * these emitted ObservableSources, the ObservableSource returned by {@code switchOnNext} begins emitting the items + * emitted by that ObservableSource. When a new ObservableSource is emitted, {@code switchOnNext} stops emitting items + * from the earlier-emitted ObservableSource and begins emitting items from the new one. + *

+ * The resulting ObservableSource completes if both the outer ObservableSource and the last inner ObservableSource, if any, complete. + * If the outer ObservableSource signals an onError, the inner ObservableSource is disposed and the error delivered in-sequence. + *

+ *
Scheduler:
+ *
{@code switchOnNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the item type + * @param sources + * the source ObservableSource that emits ObservableSources + * @return an Observable that emits the items emitted by the ObservableSource most recently emitted by the source + * ObservableSource + * @see ReactiveX operators documentation: Switch + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable switchOnNext(ObservableSource> sources) { + return switchOnNext(sources, bufferSize()); + } + + /** + * Converts an ObservableSource that emits ObservableSources into an ObservableSource that emits the items emitted by the + * most recently emitted of those ObservableSources and delays any exception until all ObservableSources terminate. + *

+ * + *

+ * {@code switchOnNext} subscribes to an ObservableSource that emits ObservableSources. Each time it observes one of + * these emitted ObservableSources, the ObservableSource returned by {@code switchOnNext} begins emitting the items + * emitted by that ObservableSource. When a new ObservableSource is emitted, {@code switchOnNext} stops emitting items + * from the earlier-emitted ObservableSource and begins emitting items from the new one. + *

+ * The resulting ObservableSource completes if both the main ObservableSource and the last inner ObservableSource, if any, complete. + * If the main ObservableSource signals an onError, the termination of the last inner ObservableSource will emit that error as is + * or wrapped into a CompositeException along with the other possible errors the former inner ObservableSources signalled. + *

+ *
Scheduler:
+ *
{@code switchOnNextDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the item type + * @param sources + * the source ObservableSource that emits ObservableSources + * @return an Observable that emits the items emitted by the ObservableSource most recently emitted by the source + * ObservableSource + * @see ReactiveX operators documentation: Switch + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable switchOnNextDelayError(ObservableSource> sources) { + return switchOnNextDelayError(sources, bufferSize()); + } + + /** + * Converts an ObservableSource that emits ObservableSources into an ObservableSource that emits the items emitted by the + * most recently emitted of those ObservableSources and delays any exception until all ObservableSources terminate. + *

+ * + *

+ * {@code switchOnNext} subscribes to an ObservableSource that emits ObservableSources. Each time it observes one of + * these emitted ObservableSources, the ObservableSource returned by {@code switchOnNext} begins emitting the items + * emitted by that ObservableSource. When a new ObservableSource is emitted, {@code switchOnNext} stops emitting items + * from the earlier-emitted ObservableSource and begins emitting items from the new one. + *

+ * The resulting ObservableSource completes if both the main ObservableSource and the last inner ObservableSource, if any, complete. + * If the main ObservableSource signals an onError, the termination of the last inner ObservableSource will emit that error as is + * or wrapped into a CompositeException along with the other possible errors the former inner ObservableSources signalled. + *

+ *
Scheduler:
+ *
{@code switchOnNextDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the item type + * @param sources + * the source ObservableSource that emits ObservableSources + * @param prefetch + * the number of items to prefetch from the inner ObservableSources + * @return an Observable that emits the items emitted by the ObservableSource most recently emitted by the source + * ObservableSource + * @see ReactiveX operators documentation: Switch + * @since 2.0 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable switchOnNextDelayError(ObservableSource> sources, int prefetch) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMap(sources, Functions.identity(), prefetch, true)); + } + + /** + * Returns an Observable that emits {@code 0L} after a specified delay, and then completes. + *

+ * + *

+ *
Scheduler:
+ *
{@code timer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the initial delay before emitting a single {@code 0L} + * @param unit + * time units to use for {@code delay} + * @return an Observable that {@code 0L} after a specified delay, and then completes + * @see ReactiveX operators documentation: Timer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Observable timer(long delay, TimeUnit unit) { + return timer(delay, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits {@code 0L} after a specified delay, on a specified Scheduler, and then + * completes. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the initial delay before emitting a single 0L + * @param unit + * time units to use for {@code delay} + * @param scheduler + * the {@link Scheduler} to use for scheduling the item + * @throws NullPointerException + * if {@code unit} is null, or + * if {@code scheduler} is null + * @return an Observable that emits {@code 0L} after a specified delay, on a specified Scheduler, and then + * completes + * @see ReactiveX operators documentation: Timer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Observable timer(long delay, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + + return RxJavaPlugins.onAssembly(new ObservableTimer(Math.max(delay, 0L), unit, scheduler)); + } + + /** + * Create an Observable by wrapping an ObservableSource which has to be implemented according + * to the Reactive Streams based Observable specification by handling + * disposal correctly; no safeguards are provided by the Observable itself. + *
+ *
Scheduler:
+ *
{@code unsafeCreate} by default doesn't operate on any particular {@link Scheduler}.
+ *
+ * @param the value type emitted + * @param onSubscribe the ObservableSource instance to wrap + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable unsafeCreate(ObservableSource onSubscribe) { + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + if (onSubscribe instanceof Observable) { + throw new IllegalArgumentException("unsafeCreate(Observable) should be upgraded"); + } + return RxJavaPlugins.onAssembly(new ObservableFromUnsafeSource(onSubscribe)); + } + + /** + * Constructs an ObservableSource that creates a dependent resource object which is disposed of when the downstream + * calls dispose(). + *

+ * + *

+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the generated ObservableSource + * @param the type of the resource associated with the output sequence + * @param resourceSupplier + * the factory function to create a resource object that depends on the ObservableSource + * @param sourceSupplier + * the factory function to create an ObservableSource + * @param disposer + * the function that will dispose of the resource + * @return the ObservableSource whose lifetime controls the lifetime of the dependent resource object + * @see ReactiveX operators documentation: Using + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable using(Callable resourceSupplier, Function> sourceSupplier, Consumer disposer) { + return using(resourceSupplier, sourceSupplier, disposer, true); + } + + /** + * Constructs an ObservableSource that creates a dependent resource object which is disposed of just before + * termination if you have set {@code disposeEagerly} to {@code true} and a dispose() call does not occur + * before termination. Otherwise resource disposal will occur on a dispose() call. Eager disposal is + * particularly appropriate for a synchronous ObservableSource that reuses resources. {@code disposeAction} will + * only be called once per subscription. + *

+ * + *

+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the generated ObservableSource + * @param the type of the resource associated with the output sequence + * @param resourceSupplier + * the factory function to create a resource object that depends on the ObservableSource + * @param sourceSupplier + * the factory function to create an ObservableSource + * @param disposer + * the function that will dispose of the resource + * @param eager + * if {@code true} then disposal will happen either on a dispose() call or just before emission of + * a terminal event ({@code onComplete} or {@code onError}). + * @return the ObservableSource whose lifetime controls the lifetime of the dependent resource object + * @see ReactiveX operators documentation: Using + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable using(Callable resourceSupplier, Function> sourceSupplier, Consumer disposer, boolean eager) { + ObjectHelper.requireNonNull(resourceSupplier, "resourceSupplier is null"); + ObjectHelper.requireNonNull(sourceSupplier, "sourceSupplier is null"); + ObjectHelper.requireNonNull(disposer, "disposer is null"); + return RxJavaPlugins.onAssembly(new ObservableUsing(resourceSupplier, sourceSupplier, disposer, eager)); + } + + /** + * Wraps an ObservableSource into an Observable if not already an Observable. + * + *
+ *
Scheduler:
+ *
{@code wrap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type + * @param source the source ObservableSource instance + * @return the new Observable instance or the same as the source + * @throws NullPointerException if source is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable wrap(ObservableSource source) { + ObjectHelper.requireNonNull(source, "source is null"); + if (source instanceof Observable) { + return RxJavaPlugins.onAssembly((Observable)source); + } + return RxJavaPlugins.onAssembly(new ObservableFromUnsafeSource(source)); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * items emitted, in sequence, by an Iterable of other ObservableSources. + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by each of the source ObservableSources; + * the second item emitted by the new ObservableSource will be the result of the function applied to the second + * item emitted by each of those ObservableSources; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@code onNext} as many times as + * the number of {@code onNext} invocations of the source ObservableSource that emits the fewest items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(Arrays.asList(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2)), (a) -> a)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common value type + * @param the zipped result type + * @param sources + * an Iterable of source ObservableSources + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip(Iterable> sources, Function zipper) { + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new ObservableZip(null, sources, zipper, bufferSize(), false)); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * n items emitted, in sequence, by the n ObservableSources emitted by a specified ObservableSource. + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by each of the ObservableSources emitted + * by the source ObservableSource; the second item emitted by the new ObservableSource will be the result of the + * function applied to the second item emitted by each of those ObservableSources; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@code onNext} as many times as + * the number of {@code onNext} invocations of the source ObservableSource that emits the fewest items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(just(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2)), (a) -> a)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner ObservableSources + * @param the zipped result type + * @param sources + * an ObservableSource of source ObservableSources + * @param zipper + * a function that, when applied to an item emitted by each of the ObservableSources emitted by + * {@code ws}, results in an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip(ObservableSource> sources, final Function zipper) { + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new ObservableToList(sources, 16) + .flatMap(ObservableInternalHelper.zipIterable(zipper))); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * two items emitted, in sequence, by two other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by {@code o1} and the first item + * emitted by {@code o2}; the second item emitted by the new ObservableSource will be the result of the function + * applied to the second item emitted by {@code o1} and the second item emitted by {@code o2}; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results + * in an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, + BiFunction zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * two items emitted, in sequence, by two other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by {@code o1} and the first item + * emitted by {@code o2}; the second item emitted by the new ObservableSource will be the result of the function + * applied to the second item emitted by {@code o1} and the second item emitted by {@code o2}; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results + * in an item that will be emitted by the resulting ObservableSource + * @param delayError delay errors from any of the source ObservableSources till the other terminates + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, + BiFunction zipper, boolean delayError) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return zipArray(Functions.toFunction(zipper), delayError, bufferSize(), source1, source2); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * two items emitted, in sequence, by two other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by {@code o1} and the first item + * emitted by {@code o2}; the second item emitted by the new ObservableSource will be the result of the function + * applied to the second item emitted by {@code o1} and the second item emitted by {@code o2}; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results + * in an item that will be emitted by the resulting ObservableSource + * @param delayError delay errors from any of the source ObservableSources till the other terminates + * @param bufferSize the number of elements to prefetch from each source ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, + BiFunction zipper, boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return zipArray(Functions.toFunction(zipper), delayError, bufferSize, source1, source2); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * three items emitted, in sequence, by three other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by {@code o1}, the first item + * emitted by {@code o2}, and the first item emitted by {@code o3}; the second item emitted by the new + * ObservableSource will be the result of the function applied to the second item emitted by {@code o1}, the + * second item emitted by {@code o2}, and the second item emitted by {@code o3}; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param source3 + * a third source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, ObservableSource source3, + Function3 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * four items emitted, in sequence, by four other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by {@code o1}, the first item + * emitted by {@code o2}, the first item emitted by {@code o3}, and the first item emitted by {@code 04}; + * the second item emitted by the new ObservableSource will be the result of the function applied to the second + * item emitted by each of those ObservableSources; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param source3 + * a third source ObservableSource + * @param source4 + * a fourth source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, ObservableSource source3, + ObservableSource source4, + Function4 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * five items emitted, in sequence, by five other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by {@code o1}, the first item + * emitted by {@code o2}, the first item emitted by {@code o3}, the first item emitted by {@code o4}, and + * the first item emitted by {@code o5}; the second item emitted by the new ObservableSource will be the result of + * the function applied to the second item emitted by each of those ObservableSources; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param source3 + * a third source ObservableSource + * @param source4 + * a fourth source ObservableSource + * @param source5 + * a fifth source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, ObservableSource source3, + ObservableSource source4, ObservableSource source5, + Function5 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * six items emitted, in sequence, by six other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by each source ObservableSource, the + * second item emitted by the new ObservableSource will be the result of the function applied to the second item + * emitted by each of those ObservableSources, and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e, f) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param source3 + * a third source ObservableSource + * @param source4 + * a fourth source ObservableSource + * @param source5 + * a fifth source ObservableSource + * @param source6 + * a sixth source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, ObservableSource source3, + ObservableSource source4, ObservableSource source5, ObservableSource source6, + Function6 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5, source6); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * seven items emitted, in sequence, by seven other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by each source ObservableSource, the + * second item emitted by the new ObservableSource will be the result of the function applied to the second item + * emitted by each of those ObservableSources, and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e, f, g) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the value type of the seventh source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param source3 + * a third source ObservableSource + * @param source4 + * a fourth source ObservableSource + * @param source5 + * a fifth source ObservableSource + * @param source6 + * a sixth source ObservableSource + * @param source7 + * a seventh source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, ObservableSource source3, + ObservableSource source4, ObservableSource source5, ObservableSource source6, + ObservableSource source7, + Function7 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5, source6, source7); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * eight items emitted, in sequence, by eight other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by each source ObservableSource, the + * second item emitted by the new ObservableSource will be the result of the function applied to the second item + * emitted by each of those ObservableSources, and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e, f, g, h) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the value type of the seventh source + * @param the value type of the eighth source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param source3 + * a third source ObservableSource + * @param source4 + * a fourth source ObservableSource + * @param source5 + * a fifth source ObservableSource + * @param source6 + * a sixth source ObservableSource + * @param source7 + * a seventh source ObservableSource + * @param source8 + * an eighth source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, ObservableSource source3, + ObservableSource source4, ObservableSource source5, ObservableSource source6, + ObservableSource source7, ObservableSource source8, + Function8 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5, source6, source7, source8); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * nine items emitted, in sequence, by nine other ObservableSources. + *

+ * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by each source ObservableSource, the + * second item emitted by the new ObservableSource will be the result of the function applied to the second item + * emitted by each of those ObservableSources, and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@link Observer#onNext onNext} + * as many times as the number of {@code onNext} invocations of the source ObservableSource that emits the fewest + * items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2), ..., (a, b, c, d, e, f, g, h, i) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the first source + * @param the value type of the second source + * @param the value type of the third source + * @param the value type of the fourth source + * @param the value type of the fifth source + * @param the value type of the sixth source + * @param the value type of the seventh source + * @param the value type of the eighth source + * @param the value type of the ninth source + * @param the zipped result type + * @param source1 + * the first source ObservableSource + * @param source2 + * a second source ObservableSource + * @param source3 + * a third source ObservableSource + * @param source4 + * a fourth source ObservableSource + * @param source5 + * a fifth source ObservableSource + * @param source6 + * a sixth source ObservableSource + * @param source7 + * a seventh source ObservableSource + * @param source8 + * an eighth source ObservableSource + * @param source9 + * a ninth source ObservableSource + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zip( + ObservableSource source1, ObservableSource source2, ObservableSource source3, + ObservableSource source4, ObservableSource source5, ObservableSource source6, + ObservableSource source7, ObservableSource source8, ObservableSource source9, + Function9 zipper) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + ObjectHelper.requireNonNull(source9, "source9 is null"); + return zipArray(Functions.toFunction(zipper), false, bufferSize(), source1, source2, source3, source4, source5, source6, source7, source8, source9); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * items emitted, in sequence, by an array of other ObservableSources. + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by each of the source ObservableSources; + * the second item emitted by the new ObservableSource will be the result of the function applied to the second + * item emitted by each of those ObservableSources; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@code onNext} as many times as + * the number of {@code onNext} invocations of the source ObservableSource that emits the fewest items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(new ObservableSource[]{range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2)}, (a) ->
+     * a)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code zipArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common element type + * @param the result type + * @param sources + * an array of source ObservableSources + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @param delayError + * delay errors signalled by any of the source ObservableSource until all ObservableSources terminate + * @param bufferSize + * the number of elements to prefetch from each source ObservableSource + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zipArray(Function zipper, + boolean delayError, int bufferSize, ObservableSource... sources) { + if (sources.length == 0) { + return empty(); + } + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new ObservableZip(sources, null, zipper, bufferSize, delayError)); + } + + /** + * Returns an Observable that emits the results of a specified combiner function applied to combinations of + * items emitted, in sequence, by an Iterable of other ObservableSources. + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the new ObservableSource + * will be the result of the function applied to the first item emitted by each of the source ObservableSources; + * the second item emitted by the new ObservableSource will be the result of the function applied to the second + * item emitted by each of those ObservableSources; and so forth. + *

+ * The resulting {@code ObservableSource} returned from {@code zip} will invoke {@code onNext} as many times as + * the number of {@code onNext} invocations of the source ObservableSource that emits the fewest items. + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

zip(Arrays.asList(range(1, 5).doOnComplete(action1), range(6, 5).doOnComplete(action2)), (a) -> a)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + * + *

+ * + *

+ *
Scheduler:
+ *
{@code zipIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * + * @param sources + * an Iterable of source ObservableSources + * @param zipper + * a function that, when applied to an item emitted by each of the source ObservableSources, results in + * an item that will be emitted by the resulting ObservableSource + * @param delayError + * delay errors signalled by any of the source ObservableSource until all ObservableSources terminate + * @param bufferSize + * the number of elements to prefetch from each source ObservableSource + * @param the common source value type + * @param the zipped result type + * @return an Observable that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Observable zipIterable(Iterable> sources, + Function zipper, boolean delayError, + int bufferSize) { + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new ObservableZip(null, sources, zipper, bufferSize, delayError)); + } + + // *************************************************************************************************** + // Instance operators + // *************************************************************************************************** + + /** + * Returns a Single that emits a Boolean that indicates whether all of the items emitted by the source + * ObservableSource satisfy a condition. + *

+ * + *

+ *
Scheduler:
+ *
{@code all} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function that evaluates an item and returns a Boolean + * @return a Single that emits {@code true} if all items emitted by the source ObservableSource satisfy the + * predicate; otherwise, {@code false} + * @see ReactiveX operators documentation: All + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single all(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new ObservableAllSingle(this, predicate)); + } + + /** + * Mirrors the ObservableSource (current or provided) that first either emits an item or sends a termination + * notification. + *

+ * + *

+ *
Scheduler:
+ *
{@code ambWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * an ObservableSource competing to react first. A subscription to this provided source will occur after + * subscribing to the current source. + * @return an Observable that emits the same sequence as whichever of the source ObservableSources first + * emitted an item or sent a termination notification + * @see ReactiveX operators documentation: Amb + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable ambWith(ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return ambArray(this, other); + } + + /** + * Returns a Single that emits {@code true} if any item emitted by the source ObservableSource satisfies a + * specified condition, otherwise {@code false}. Note: this always emits {@code false} if the + * source ObservableSource is empty. + *

+ * + *

+ * In Rx.Net this is the {@code any} Observer but we renamed it in RxJava to better match Java naming + * idioms. + *

+ *
Scheduler:
+ *
{@code any} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * the condition to test items emitted by the source ObservableSource + * @return a Single that emits a Boolean that indicates whether any item emitted by the source + * ObservableSource satisfies the {@code predicate} + * @see ReactiveX operators documentation: Contains + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single any(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new ObservableAnySingle(this, predicate)); + } + + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * This allows fluent conversion to any other type. + *

+ *
Scheduler:
+ *
{@code as} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.7 - experimental + * @param the resulting object type + * @param converter the function that receives the current Observable instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final R as(@NonNull ObservableConverter converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + + /** + * Returns the first item emitted by this {@code Observable}, or throws + * {@code NoSuchElementException} if it emits no items. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingFirst} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the first item emitted by this {@code Observable} + * @throws NoSuchElementException + * if this {@code Observable} emits no items + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingFirst() { + BlockingFirstObserver observer = new BlockingFirstObserver(); + subscribe(observer); + T v = observer.blockingGet(); + if (v != null) { + return v; + } + throw new NoSuchElementException(); + } + + /** + * Returns the first item emitted by this {@code Observable}, or a default value if it emits no + * items. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingFirst} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * a default value to return if this {@code Observable} emits no items + * @return the first item emitted by this {@code Observable}, or the default value if it emits no + * items + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingFirst(T defaultItem) { + BlockingFirstObserver observer = new BlockingFirstObserver(); + subscribe(observer); + T v = observer.blockingGet(); + return v != null ? v : defaultItem; + } + + /** + * Consumes the upstream {@code Observable} in a blocking fashion and invokes the given + * {@code Consumer} with each upstream item on the current thread until the + * upstream terminates. + *

+ * + *

+ * Note: the method will only return if the upstream terminates or the current + * thread is interrupted. + *

+ * This method executes the {@code Consumer} on the current thread while + * {@link #subscribe(Consumer)} executes the consumer on the original caller thread of the + * sequence. + *

+ *
Scheduler:
+ *
{@code blockingForEach} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @param onNext + * the {@link Consumer} to invoke for each item emitted by the {@code Observable} + * @throws RuntimeException + * if an error occurs + * @see ReactiveX documentation: Subscribe + * @see #subscribe(Consumer) + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingForEach(Consumer onNext) { + Iterator it = blockingIterable().iterator(); + while (it.hasNext()) { + try { + onNext.accept(it.next()); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + ((Disposable)it).dispose(); + throw ExceptionHelper.wrapOrThrow(e); + } + } + } + + /** + * Converts this {@code Observable} into an {@link Iterable}. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an {@link Iterable} version of this {@code Observable} + * @see ReactiveX documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingIterable() { + return blockingIterable(bufferSize()); + } + + /** + * Converts this {@code Observable} into an {@link Iterable}. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param bufferSize the number of items to prefetch from the current Observable + * @return an {@link Iterable} version of this {@code Observable} + * @see ReactiveX documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingIterable(int bufferSize) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return new BlockingObservableIterable(this, bufferSize); + } + + /** + * Returns the last item emitted by this {@code Observable}, or throws + * {@code NoSuchElementException} if this {@code Observable} emits no items. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingLast} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @return the last item emitted by this {@code Observable} + * @throws NoSuchElementException + * if this {@code Observable} emits no items + * @see ReactiveX documentation: Last + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingLast() { + BlockingLastObserver observer = new BlockingLastObserver(); + subscribe(observer); + T v = observer.blockingGet(); + if (v != null) { + return v; + } + throw new NoSuchElementException(); + } + + /** + * Returns the last item emitted by this {@code Observable}, or a default value if it emits no + * items. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingLast} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @param defaultItem + * a default value to return if this {@code Observable} emits no items + * @return the last item emitted by the {@code Observable}, or the default value if it emits no + * items + * @see ReactiveX documentation: Last + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingLast(T defaultItem) { + BlockingLastObserver observer = new BlockingLastObserver(); + subscribe(observer); + T v = observer.blockingGet(); + return v != null ? v : defaultItem; + } + + /** + * Returns an {@link Iterable} that returns the latest item emitted by this {@code Observable}, + * waiting if necessary for one to become available. + *

+ * + *

+ * If this {@code Observable} produces items faster than {@code Iterator.next} takes them, + * {@code onNext} events might be skipped, but {@code onError} or {@code onComplete} events are not. + *

+ * Note also that an {@code onNext} directly followed by {@code onComplete} might hide the {@code onNext} + * event. + *

+ *
Scheduler:
+ *
{@code blockingLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an Iterable that always returns the latest item emitted by this {@code Observable} + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingLatest() { + return new BlockingObservableLatest(this); + } + + /** + * Returns an {@link Iterable} that always returns the item most recently emitted by this + * {@code Observable}. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingMostRecent} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param initialValue + * the initial value that the {@link Iterable} sequence will yield if this + * {@code Observable} has not yet emitted an item + * @return an {@link Iterable} that on each iteration returns the item that this {@code Observable} + * has most recently emitted + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingMostRecent(T initialValue) { + return new BlockingObservableMostRecent(this, initialValue); + } + + /** + * Returns an {@link Iterable} that blocks until this {@code Observable} emits another item, then + * returns that item. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an {@link Iterable} that blocks upon each iteration until this {@code Observable} emits + * a new item, whereupon the Iterable returns that item + * @see ReactiveX documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Iterable blockingNext() { + return new BlockingObservableNext(this); + } + + /** + * If this {@code Observable} completes after emitting a single item, return that item, otherwise + * throw a {@code NoSuchElementException}. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingSingle} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @return the single item emitted by this {@code Observable} + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingSingle() { + T v = singleElement().blockingGet(); + if (v == null) { + throw new NoSuchElementException(); + } + return v; + } + + /** + * If this {@code Observable} completes after emitting a single item, return that item; if it emits + * more than one item, throw an {@code IllegalArgumentException}; if it emits no items, return a default + * value. + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingSingle} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * + * @param defaultItem + * a default value to return if this {@code Observable} emits no items + * @return the single item emitted by this {@code Observable}, or the default value if it emits no + * items + * @see ReactiveX documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingSingle(T defaultItem) { + return single(defaultItem).blockingGet(); + } + + /** + * Returns a {@link Future} representing the only value emitted by this {@code Observable}. + *

+ * + *

+ * If the {@link Observable} emits more than one item, {@link Future} will receive an + * {@link IndexOutOfBoundsException}. If the {@link Observable} is empty, {@link Future} + * will receive an {@link NoSuchElementException}. The {@code Observable} source has to terminate in order + * for the returned {@code Future} to terminate as well. + *

+ * If the {@code Observable} may emit more than one item, use {@code Observable.toList().toFuture()}. + *

+ *
Scheduler:
+ *
{@code toFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Future} that expects a single item to be emitted by this {@code Observable} + * @see ReactiveX documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Future toFuture() { + return subscribeWith(new FutureObserver()); + } + + /** + * Runs the source observable to a terminal event, ignoring any values and rethrowing any exception. + *

+ * + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @since 2.0 + * @see #blockingSubscribe(Consumer) + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe() { + ObservableBlockingSubscribe.subscribe(this); + } + + /** + * Subscribes to the source and calls the given callbacks on the current thread. + *

+ * + *

+ * If the {@code Observable} emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + * Using the overloads {@link #blockingSubscribe(Consumer, Consumer)} + * or {@link #blockingSubscribe(Consumer, Consumer, Action)} instead is recommended. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onNext the callback action for each source value + * @since 2.0 + * @see #blockingSubscribe(Consumer, Consumer) + * @see #blockingSubscribe(Consumer, Consumer, Action) + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer onNext) { + ObservableBlockingSubscribe.subscribe(this, onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to the source and calls the given callbacks on the current thread. + *

+ * + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @since 2.0 + * @see #blockingSubscribe(Consumer, Consumer, Action) + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer onNext, Consumer onError) { + ObservableBlockingSubscribe.subscribe(this, onNext, onError, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to the source and calls the given callbacks on the current thread. + *

+ * + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally or with an error. Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param onComplete the callback action for the completion event. + * @since 2.0 + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Consumer onNext, Consumer onError, Action onComplete) { + ObservableBlockingSubscribe.subscribe(this, onNext, onError, onComplete); + } + + /** + * Subscribes to the source and calls the {@link Observer} methods on the current thread. + *

+ * Note that calling this method will block the caller thread until the upstream terminates + * normally, with an error or the {@code Observer} disposes the {@link Disposable} it receives via + * {@link Observer#onSubscribe(Disposable)}. + * Therefore, calling this method from special threads such as the + * Android Main Thread or the Swing Event Dispatch Thread is not recommended. + *

+ *
Scheduler:
+ *
{@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * The a dispose() call is composed through. + * @param observer the {@code Observer} instance to forward events and calls to in the current thread + * @since 2.0 + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final void blockingSubscribe(Observer observer) { + ObservableBlockingSubscribe.subscribe(this, observer); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping buffers, each containing {@code count} items. When the source + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum number of items in each buffer before it should be emitted + * @return an Observable that emits connected, non-overlapping buffers, each containing at most + * {@code count} items from the source ObservableSource + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> buffer(int count) { + return buffer(count, count); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits buffers every {@code skip} items, each containing {@code count} items. When the source + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum size of each buffer before it should be emitted + * @param skip + * how many items emitted by the source ObservableSource should be skipped before starting a new + * buffer. Note that when {@code skip} and {@code count} are equal, this is the same operation as + * {@link #buffer(int)}. + * @return an Observable that emits buffers for every {@code skip} item from the source ObservableSource and + * containing at most {@code count} items + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> buffer(int count, int skip) { + return buffer(count, skip, ArrayListSupplier.asCallable()); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits buffers every {@code skip} items, each containing {@code count} items. When the source + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param count + * the maximum size of each buffer before it should be emitted + * @param skip + * how many items emitted by the source ObservableSource should be skipped before starting a new + * buffer. Note that when {@code skip} and {@code count} are equal, this is the same operation as + * {@link #buffer(int)}. + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return an Observable that emits buffers for every {@code skip} item from the source ObservableSource and + * containing at most {@code count} items + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final > Observable buffer(int count, int skip, Callable bufferSupplier) { + ObjectHelper.verifyPositive(count, "count"); + ObjectHelper.verifyPositive(skip, "skip"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new ObservableBuffer(this, count, skip, bufferSupplier)); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping buffers, each containing {@code count} items. When the source + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param count + * the maximum number of items in each buffer before it should be emitted + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return an Observable that emits connected, non-overlapping buffers, each containing at most + * {@code count} items from the source ObservableSource + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final > Observable buffer(int count, Callable bufferSupplier) { + return buffer(count, count, bufferSupplier); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource starts a new buffer periodically, as determined by the {@code timeskip} argument. It emits + * each buffer after a fixed timespan, specified by the {@code timespan} argument. When the source + * ObservableSource completes, the resulting ObservableSource emits the current buffer and propagates the notification + * from the source ObservableSource. Note that if the source ObservableSource issues an onError notification + * the event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code buffer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted + * @param timeskip + * the period of time after which a new buffer will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @return an Observable that emits new buffers of items emitted by the source ObservableSource periodically after + * a fixed timespan has elapsed + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable> buffer(long timespan, long timeskip, TimeUnit unit) { + return buffer(timespan, timeskip, unit, Schedulers.computation(), ArrayListSupplier.asCallable()); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource starts a new buffer periodically, as determined by the {@code timeskip} argument, and on the + * specified {@code scheduler}. It emits each buffer after a fixed timespan, specified by the + * {@code timespan} argument. When the source ObservableSource completes, the resulting ObservableSource emits the + * current buffer and propagates the notification from the source ObservableSource. Note that if the source + * ObservableSource issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted + * @param timeskip + * the period of time after which a new buffer will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @return an Observable that emits new buffers of items emitted by the source ObservableSource periodically after + * a fixed timespan has elapsed + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable> buffer(long timespan, long timeskip, TimeUnit unit, Scheduler scheduler) { + return buffer(timespan, timeskip, unit, scheduler, ArrayListSupplier.asCallable()); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource starts a new buffer periodically, as determined by the {@code timeskip} argument, and on the + * specified {@code scheduler}. It emits each buffer after a fixed timespan, specified by the + * {@code timespan} argument. When the source ObservableSource completes, the resulting ObservableSource emits the + * current buffer and propagates the notification from the source ObservableSource. Note that if the source + * ObservableSource issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param the collection subclass type to buffer into + * @param timespan + * the period of time each buffer collects items before it is emitted + * @param timeskip + * the period of time after which a new buffer will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return an Observable that emits new buffers of items emitted by the source ObservableSource periodically after + * a fixed timespan has elapsed + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final > Observable buffer(long timespan, long timeskip, TimeUnit unit, Scheduler scheduler, Callable bufferSupplier) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new ObservableBufferTimed(this, timespan, timeskip, unit, scheduler, bufferSupplier, Integer.MAX_VALUE, false)); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument. When the source ObservableSource completes, the resulting ObservableSource emits the + * current buffer and propagates the notification from the source ObservableSource. Note that if the source + * ObservableSource issues an onError notification the event is passed on immediately without first emitting the + * buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code buffer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time that applies to the {@code timespan} argument + * @return an Observable that emits connected, non-overlapping buffers of items emitted by the source + * ObservableSource within a fixed duration + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable> buffer(long timespan, TimeUnit unit) { + return buffer(timespan, unit, Schedulers.computation(), Integer.MAX_VALUE); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source ObservableSource completes, the resulting ObservableSource emits the current buffer and + * propagates the notification from the source ObservableSource. Note that if the source ObservableSource issues an + * onError notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code buffer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each buffer before it is emitted + * @return an Observable that emits connected, non-overlapping buffers of items emitted by the source + * ObservableSource, after a fixed duration or when the buffer reaches maximum capacity (whichever occurs + * first) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable> buffer(long timespan, TimeUnit unit, int count) { + return buffer(timespan, unit, Schedulers.computation(), count); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument as measured on the specified {@code scheduler}, or a maximum size specified by + * the {@code count} argument (whichever is reached first). When the source ObservableSource completes, the resulting + * ObservableSource emits the current buffer and propagates the notification from the source ObservableSource. Note + * that if the source ObservableSource issues an onError notification the event is passed on immediately without + * first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @param count + * the maximum size of each buffer before it is emitted + * @return an Observable that emits connected, non-overlapping buffers of items emitted by the source + * ObservableSource after a fixed duration or when the buffer reaches maximum capacity (whichever occurs + * first) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable> buffer(long timespan, TimeUnit unit, Scheduler scheduler, int count) { + return buffer(timespan, unit, scheduler, count, ArrayListSupplier.asCallable(), false); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument as measured on the specified {@code scheduler}, or a maximum size specified by + * the {@code count} argument (whichever is reached first). When the source ObservableSource completes, the resulting + * ObservableSource emits the current buffer and propagates the notification from the source ObservableSource. Note + * that if the source ObservableSource issues an onError notification the event is passed on immediately without + * first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param the collection subclass type to buffer into + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @param count + * the maximum size of each buffer before it is emitted + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @param restartTimerOnMaxSize if true the time window is restarted when the max capacity of the current buffer + * is reached + * @return an Observable that emits connected, non-overlapping buffers of items emitted by the source + * ObservableSource after a fixed duration or when the buffer reaches maximum capacity (whichever occurs + * first) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final > Observable buffer( + long timespan, TimeUnit unit, + Scheduler scheduler, int count, + Callable bufferSupplier, + boolean restartTimerOnMaxSize) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + ObjectHelper.verifyPositive(count, "count"); + return RxJavaPlugins.onAssembly(new ObservableBufferTimed(this, timespan, timespan, unit, scheduler, bufferSupplier, count, restartTimerOnMaxSize)); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument and on the specified {@code scheduler}. When the source ObservableSource completes, + * the resulting ObservableSource emits the current buffer and propagates the notification from the source + * ObservableSource. Note that if the source ObservableSource issues an onError notification the event is passed on + * immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @return an Observable that emits connected, non-overlapping buffers of items emitted by the source + * ObservableSource within a fixed duration + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable> buffer(long timespan, TimeUnit unit, Scheduler scheduler) { + return buffer(timespan, unit, scheduler, Integer.MAX_VALUE, ArrayListSupplier.asCallable(), false); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits buffers that it creates when the specified {@code openingIndicator} ObservableSource emits an + * item, and closes when the ObservableSource returned from {@code closingIndicator} emits an item. If any of the + * source ObservableSource, {@code openingIndicator} or {@code closingIndicator} issues an onError notification the + * event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the buffer-opening ObservableSource + * @param the element type of the individual buffer-closing ObservableSources + * @param openingIndicator + * the ObservableSource that, when it emits an item, causes a new buffer to be created + * @param closingIndicator + * the {@link Function} that is used to produce an ObservableSource for every buffer created. When this + * ObservableSource emits an item, the associated buffer is emitted. + * @return an Observable that emits buffers, containing items from the source ObservableSource, that are created + * and closed when the specified ObservableSources emit items + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> buffer( + ObservableSource openingIndicator, + Function> closingIndicator) { + return buffer(openingIndicator, closingIndicator, ArrayListSupplier.asCallable()); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits buffers that it creates when the specified {@code openingIndicator} ObservableSource emits an + * item, and closes when the ObservableSource returned from {@code closingIndicator} emits an item. If any of the + * source ObservableSource, {@code openingIndicator} or {@code closingIndicator} issues an onError notification the + * event is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param the element type of the buffer-opening ObservableSource + * @param the element type of the individual buffer-closing ObservableSources + * @param openingIndicator + * the ObservableSource that, when it emits an item, causes a new buffer to be created + * @param closingIndicator + * the {@link Function} that is used to produce an ObservableSource for every buffer created. When this + * ObservableSource emits an item, the associated buffer is emitted. + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return an Observable that emits buffers, containing items from the source ObservableSource, that are created + * and closed when the specified ObservableSources emit items + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final > Observable buffer( + ObservableSource openingIndicator, + Function> closingIndicator, + Callable bufferSupplier) { + ObjectHelper.requireNonNull(openingIndicator, "openingIndicator is null"); + ObjectHelper.requireNonNull(closingIndicator, "closingIndicator is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new ObservableBufferBoundary(this, openingIndicator, closingIndicator, bufferSupplier)); + } + + /** + * Returns an Observable that emits non-overlapping buffered items from the source ObservableSource each time the + * specified boundary ObservableSource emits an item. + *

+ * + *

+ * Completion of either the source or the boundary ObservableSource causes the returned ObservableSource to emit the + * latest buffer and complete. If either the source ObservableSource or the boundary ObservableSource issues an + * onError notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the boundary value type (ignored) + * @param boundary + * the boundary ObservableSource + * @return an Observable that emits buffered items from the source ObservableSource when the boundary ObservableSource + * emits an item + * @see #buffer(ObservableSource, int) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> buffer(ObservableSource boundary) { + return buffer(boundary, ArrayListSupplier.asCallable()); + } + + /** + * Returns an Observable that emits non-overlapping buffered items from the source ObservableSource each time the + * specified boundary ObservableSource emits an item. + *

+ * + *

+ * Completion of either the source or the boundary ObservableSource causes the returned ObservableSource to emit the + * latest buffer and complete. If either the source ObservableSource or the boundary ObservableSource issues an + * onError notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the boundary value type (ignored) + * @param boundary + * the boundary ObservableSource + * @param initialCapacity + * the initial capacity of each buffer chunk + * @return an Observable that emits buffered items from the source ObservableSource when the boundary ObservableSource + * emits an item + * @see ReactiveX operators documentation: Buffer + * @see #buffer(ObservableSource) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> buffer(ObservableSource boundary, final int initialCapacity) { + ObjectHelper.verifyPositive(initialCapacity, "initialCapacity"); + return buffer(boundary, Functions.createArrayList(initialCapacity)); + } + + /** + * Returns an Observable that emits non-overlapping buffered items from the source ObservableSource each time the + * specified boundary ObservableSource emits an item. + *

+ * + *

+ * Completion of either the source or the boundary ObservableSource causes the returned ObservableSource to emit the + * latest buffer and complete. If either the source ObservableSource or the boundary ObservableSource issues an + * onError notification the event is passed on immediately without first emitting the buffer it is in the process of + * assembling. + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param + * the boundary value type (ignored) + * @param boundary + * the boundary ObservableSource + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return an Observable that emits buffered items from the source ObservableSource when the boundary ObservableSource + * emits an item + * @see #buffer(ObservableSource, int) + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final > Observable buffer(ObservableSource boundary, Callable bufferSupplier) { + ObjectHelper.requireNonNull(boundary, "boundary is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new ObservableBufferExactBoundary(this, boundary, bufferSupplier)); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a + * new buffer whenever the ObservableSource produced by the specified {@code boundarySupplier} emits an item. + *

+ * + *

+ * If either the source {@code ObservableSource} or the boundary {@code ObservableSource} issues an {@code onError} notification the event + * is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the boundary-providing ObservableSource + * @param boundarySupplier + * a {@link Callable} that produces an ObservableSource that governs the boundary between buffers. + * Whenever the supplied {@code ObservableSource} emits an item, {@code buffer} emits the current buffer and + * begins to fill a new one + * @return an Observable that emits a connected, non-overlapping buffer of items from the source ObservableSource + * each time the ObservableSource created with the {@code closingIndicator} argument emits an item + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> buffer(Callable> boundarySupplier) { + return buffer(boundarySupplier, ArrayListSupplier.asCallable()); + } + + /** + * Returns an Observable that emits buffers of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a + * new buffer whenever the ObservableSource produced by the specified {@code boundarySupplier} emits an item. + *

+ * + *

+ * If either the source {@code ObservableSource} or the boundary {@code ObservableSource} issues an {@code onError} notification the event + * is passed on immediately without first emitting the buffer it is in the process of assembling. + *

+ *
Scheduler:
+ *
This version of {@code buffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the collection subclass type to buffer into + * @param the value type of the boundary-providing ObservableSource + * @param boundarySupplier + * a {@link Callable} that produces an ObservableSource that governs the boundary between buffers. + * Whenever the supplied {@code ObservableSource} emits an item, {@code buffer} emits the current buffer and + * begins to fill a new one + * @param bufferSupplier + * a factory function that returns an instance of the collection subclass to be used and returned + * as the buffer + * @return an Observable that emits a connected, non-overlapping buffer of items from the source ObservableSource + * each time the ObservableSource created with the {@code closingIndicator} argument emits an item + * @see ReactiveX operators documentation: Buffer + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final > Observable buffer(Callable> boundarySupplier, Callable bufferSupplier) { + ObjectHelper.requireNonNull(boundarySupplier, "boundarySupplier is null"); + ObjectHelper.requireNonNull(bufferSupplier, "bufferSupplier is null"); + return RxJavaPlugins.onAssembly(new ObservableBufferBoundarySupplier(this, boundarySupplier, bufferSupplier)); + } + + /** + * Returns an Observable that subscribes to this ObservableSource lazily, caches all of its events + * and replays them, in the same order as received, to all the downstream subscribers. + *

+ * + *

+ * This is useful when you want an ObservableSource to cache responses and you can't control the + * subscribe/dispose behavior of all the {@link Observer}s. + *

+ * The operator subscribes only when the first downstream subscriber subscribes and maintains + * a single subscription towards this ObservableSource. In contrast, the operator family of {@link #replay()} + * that return a {@link ConnectableObservable} require an explicit call to {@link ConnectableObservable#connect()}. + *

+ * Note: You sacrifice the ability to dispose the origin when you use the {@code cache} + * Observer so be careful not to use this Observer on ObservableSources that emit an infinite or very large number + * of items that will use up memory. + * A possible workaround is to apply `takeUntil` with a predicate or + * another source before (and perhaps after) the application of cache(). + *


+     * AtomicBoolean shouldStop = new AtomicBoolean();
+     *
+     * source.takeUntil(v -> shouldStop.get())
+     *       .cache()
+     *       .takeUntil(v -> shouldStop.get())
+     *       .subscribe(...);
+     * 
+ * Since the operator doesn't allow clearing the cached values either, the possible workaround is + * to forget all references to it via {@link #onTerminateDetach()} applied along with the previous + * workaround: + *

+     * AtomicBoolean shouldStop = new AtomicBoolean();
+     *
+     * source.takeUntil(v -> shouldStop.get())
+     *       .onTerminateDetach()
+     *       .cache()
+     *       .takeUntil(v -> shouldStop.get())
+     *       .onTerminateDetach()
+     *       .subscribe(...);
+     * 
+ *
+ *
Scheduler:
+ *
{@code cache} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an Observable that, when first subscribed to, caches all of its items and notifications for the + * benefit of subsequent subscribers + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable cache() { + return cacheWithInitialCapacity(16); + } + + /** + * Returns an Observable that subscribes to this ObservableSource lazily, caches all of its events + * and replays them, in the same order as received, to all the downstream subscribers. + *

+ * + *

+ * This is useful when you want an ObservableSource to cache responses and you can't control the + * subscribe/dispose behavior of all the {@link Observer}s. + *

+ * The operator subscribes only when the first downstream subscriber subscribes and maintains + * a single subscription towards this ObservableSource. In contrast, the operator family of {@link #replay()} + * that return a {@link ConnectableObservable} require an explicit call to {@link ConnectableObservable#connect()}. + *

+ * Note: You sacrifice the ability to dispose the origin when you use the {@code cache} + * Observer so be careful not to use this Observer on ObservableSources that emit an infinite or very large number + * of items that will use up memory. + * A possible workaround is to apply `takeUntil` with a predicate or + * another source before (and perhaps after) the application of cache(). + *


+     * AtomicBoolean shouldStop = new AtomicBoolean();
+     *
+     * source.takeUntil(v -> shouldStop.get())
+     *       .cache()
+     *       .takeUntil(v -> shouldStop.get())
+     *       .subscribe(...);
+     * 
+ * Since the operator doesn't allow clearing the cached values either, the possible workaround is + * to forget all references to it via {@link #onTerminateDetach()} applied along with the previous + * workaround: + *

+     * AtomicBoolean shouldStop = new AtomicBoolean();
+     *
+     * source.takeUntil(v -> shouldStop.get())
+     *       .onTerminateDetach()
+     *       .cache()
+     *       .takeUntil(v -> shouldStop.get())
+     *       .onTerminateDetach()
+     *       .subscribe(...);
+     * 
+ *
+ *
Scheduler:
+ *
{@code cacheWithInitialCapacity} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

+ * Note: The capacity hint is not an upper bound on cache size. For that, consider + * {@link #replay(int)} in combination with {@link ConnectableObservable#autoConnect()} or similar. + * + * @param initialCapacity hint for number of items to cache (for optimizing underlying data structure) + * @return an Observable that, when first subscribed to, caches all of its items and notifications for the + * benefit of subsequent subscribers + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable cacheWithInitialCapacity(int initialCapacity) { + ObjectHelper.verifyPositive(initialCapacity, "initialCapacity"); + return RxJavaPlugins.onAssembly(new ObservableCache(this, initialCapacity)); + } + + /** + * Returns an Observable that emits the items emitted by the source ObservableSource, converted to the specified + * type. + *

+ * + *

+ *
Scheduler:
+ *
{@code cast} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output value type cast to + * @param clazz + * the target class type that {@code cast} will cast the items emitted by the source ObservableSource + * into before emitting them from the resulting ObservableSource + * @return an Observable that emits each item from the source ObservableSource after converting it to the + * specified type + * @see ReactiveX operators documentation: Map + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable cast(final Class clazz) { + ObjectHelper.requireNonNull(clazz, "clazz is null"); + return map(Functions.castFunction(clazz)); + } + + /** + * Collects items emitted by the finite source ObservableSource into a single mutable data structure and returns + * a Single that emits this structure. + *

+ * + *

+ * This is a simplified version of {@code reduce} that does not need to return the state on each pass. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code collect} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the accumulator and output type + * @param initialValueSupplier + * the mutable data structure that will collect the items + * @param collector + * a function that accepts the {@code state} and an emitted item, and modifies {@code state} + * accordingly + * @return a Single that emits the result of collecting the values emitted by the source ObservableSource + * into a single mutable data structure + * @see ReactiveX operators documentation: Reduce + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single collect(Callable initialValueSupplier, BiConsumer collector) { + ObjectHelper.requireNonNull(initialValueSupplier, "initialValueSupplier is null"); + ObjectHelper.requireNonNull(collector, "collector is null"); + return RxJavaPlugins.onAssembly(new ObservableCollectSingle(this, initialValueSupplier, collector)); + } + + /** + * Collects items emitted by the finite source ObservableSource into a single mutable data structure and returns + * a Single that emits this structure. + *

+ * + *

+ * This is a simplified version of {@code reduce} that does not need to return the state on each pass. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code collectInto} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the accumulator and output type + * @param initialValue + * the mutable data structure that will collect the items + * @param collector + * a function that accepts the {@code state} and an emitted item, and modifies {@code state} + * accordingly + * @return a Single that emits the result of collecting the values emitted by the source ObservableSource + * into a single mutable data structure + * @see ReactiveX operators documentation: Reduce + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single collectInto(final U initialValue, BiConsumer collector) { + ObjectHelper.requireNonNull(initialValue, "initialValue is null"); + return collect(Functions.justCallable(initialValue), collector); + } + + /** + * Transform an ObservableSource by applying a particular Transformer function to it. + *

+ * This method operates on the ObservableSource itself whereas {@link #lift} operates on the ObservableSource's + * Observers. + *

+ * If the operator you are creating is designed to act on the individual items emitted by a source + * ObservableSource, use {@link #lift}. If your operator is designed to transform the source ObservableSource as a whole + * (for instance, by applying a particular set of existing RxJava operators to it) use {@code compose}. + *

+ *
Scheduler:
+ *
{@code compose} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the output ObservableSource + * @param composer implements the function that transforms the source ObservableSource + * @return the source ObservableSource, transformed by the transformer function + * @see RxJava wiki: Implementing Your Own Operators + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable compose(ObservableTransformer composer) { + return wrap(((ObservableTransformer) ObjectHelper.requireNonNull(composer, "composer is null")).apply(this)); + } + + /** + * Returns a new Observable that emits items resulting from applying a function that you supply to each item + * emitted by the source ObservableSource, where that function returns an ObservableSource, and then emitting the items + * that result from concatenating those resulting ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the inner ObservableSource sources and thus the output type + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @return an Observable that emits the result of applying the transformation function to each item emitted + * by the source ObservableSource and concatenating the ObservableSources obtained from this transformation + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMap(Function> mapper) { + return concatMap(mapper, 2); + } + + /** + * Returns a new Observable that emits items resulting from applying a function that you supply to each item + * emitted by the source ObservableSource, where that function returns an ObservableSource, and then emitting the items + * that result from concatenating those resulting ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the type of the inner ObservableSource sources and thus the output type + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @param prefetch + * the number of elements to prefetch from the current Observable + * @return an Observable that emits the result of applying the transformation function to each item emitted + * by the source ObservableSource and concatenating the ObservableSources obtained from this transformation + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMap(Function> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + if (this instanceof ScalarCallable) { + @SuppressWarnings("unchecked") + T v = ((ScalarCallable)this).call(); + if (v == null) { + return empty(); + } + return ObservableScalarXMap.scalarXMap(v, mapper); + } + return RxJavaPlugins.onAssembly(new ObservableConcatMap(this, mapper, prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Maps each of the items into an ObservableSource, subscribes to them one after the other, + * one at a time and emits their values in order + * while delaying any error from either this or any of the inner ObservableSources + * till all of them terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper the function that maps the items of this ObservableSource into the inner ObservableSources. + * @return the new ObservableSource instance with the concatenation behavior + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapDelayError(Function> mapper) { + return concatMapDelayError(mapper, bufferSize(), true); + } + + /** + * Maps each of the items into an ObservableSource, subscribes to them one after the other, + * one at a time and emits their values in order + * while delaying any error from either this or any of the inner ObservableSources + * till all of them terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper the function that maps the items of this ObservableSource into the inner ObservableSources. + * @param prefetch + * the number of elements to prefetch from the current Observable + * @param tillTheEnd + * if true, all errors from the outer and inner ObservableSource sources are delayed until the end, + * if false, an error from the main source is signalled when the current ObservableSource source terminates + * @return the new ObservableSource instance with the concatenation behavior + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapDelayError(Function> mapper, + int prefetch, boolean tillTheEnd) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + if (this instanceof ScalarCallable) { + @SuppressWarnings("unchecked") + T v = ((ScalarCallable)this).call(); + if (v == null) { + return empty(); + } + return ObservableScalarXMap.scalarXMap(v, mapper); + } + return RxJavaPlugins.onAssembly(new ObservableConcatMap(this, mapper, prefetch, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY)); + } + + /** + * Maps a sequence of values into ObservableSources and concatenates these ObservableSources eagerly into a single + * ObservableSource. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them in + * order, each one after the previous one completes. + *

+ * + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param mapper the function that maps a sequence of values into a sequence of ObservableSources that will be + * eagerly concatenated + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapEager(Function> mapper) { + return concatMapEager(mapper, Integer.MAX_VALUE, bufferSize()); + } + + /** + * Maps a sequence of values into ObservableSources and concatenates these ObservableSources eagerly into a single + * ObservableSource. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them in + * order, each one after the previous one completes. + *

+ * + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param mapper the function that maps a sequence of values into a sequence of ObservableSources that will be + * eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscribed ObservableSources + * @param prefetch hints about the number of expected values from each inner ObservableSource, must be positive + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapEager(Function> mapper, + int maxConcurrency, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapEager(this, mapper, ErrorMode.IMMEDIATE, maxConcurrency, prefetch)); + } + + /** + * Maps a sequence of values into ObservableSources and concatenates these ObservableSources eagerly into a single + * ObservableSource. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them in + * order, each one after the previous one completes. + *

+ * + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param mapper the function that maps a sequence of values into a sequence of ObservableSources that will be + * eagerly concatenated + * @param tillTheEnd + * if true, all errors from the outer and inner ObservableSource sources are delayed until the end, + * if false, an error from the main source is signalled when the current ObservableSource source terminates + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapEagerDelayError(Function> mapper, + boolean tillTheEnd) { + return concatMapEagerDelayError(mapper, Integer.MAX_VALUE, bufferSize(), tillTheEnd); + } + + /** + * Maps a sequence of values into ObservableSources and concatenates these ObservableSources eagerly into a single + * ObservableSource. + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source ObservableSources. The operator buffers the values emitted by these ObservableSources and then drains them in + * order, each one after the previous one completes. + *

+ * + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param mapper the function that maps a sequence of values into a sequence of ObservableSources that will be + * eagerly concatenated + * @param maxConcurrency the maximum number of concurrent subscribed ObservableSources + * @param prefetch + * the number of elements to prefetch from each source ObservableSource + * @param tillTheEnd + * if true, exceptions from the current Observable and all the inner ObservableSources are delayed until + * all of them terminate, if false, exception from the current Observable is delayed until the + * currently running ObservableSource terminates + * @return the new ObservableSource instance with the specified concatenation behavior + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapEagerDelayError(Function> mapper, + int maxConcurrency, int prefetch, boolean tillTheEnd) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapEager(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, maxConcurrency, prefetch)); + } + + /** + * Maps each element of the upstream Observable into CompletableSources, subscribes to them one at a time in + * order and waits until the upstream and all CompletableSources complete. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.6 - experimental + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns a CompletableSource + * @return a Completable that signals {@code onComplete} when the upstream and all CompletableSources complete + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletable(Function mapper) { + return concatMapCompletable(mapper, 2); + } + + /** + * Maps each element of the upstream Observable into CompletableSources, subscribes to them one at a time in + * order and waits until the upstream and all CompletableSources complete. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.6 - experimental + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns a CompletableSource + * + * @param capacityHint + * the number of upstream items expected to be buffered until the current CompletableSource, mapped from + * the current item, completes. + * @return a Completable that signals {@code onComplete} when the upstream and all CompletableSources complete + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletable(Function mapper, int capacityHint) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(capacityHint, "capacityHint"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapCompletable(this, mapper, ErrorMode.IMMEDIATE, capacityHint)); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, delaying all errors till both this {@code Observable} and all + * inner {@code CompletableSource}s terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @return a new Completable instance + * @see #concatMapCompletable(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletableDelayError(Function mapper) { + return concatMapCompletableDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, optionally delaying all errors till both this {@code Observable} and all + * inner {@code CompletableSource}s terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code CompletableSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code CompletableSource} terminates and only then is + * it emitted to the downstream. + * @return a new Completable instance + * @see #concatMapCompletable(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletableDelayError(Function mapper, boolean tillTheEnd) { + return concatMapCompletableDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other terminates, optionally delaying all errors till both this {@code Observable} and all + * inner {@code CompletableSource}s terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with the upstream item and should return + * a {@code CompletableSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code CompletableSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code CompletableSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code CompletableSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code CompletableSource}s. + * @return a new Completable instance + * @see #concatMapCompletable(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable concatMapCompletableDelayError(Function mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapCompletable(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Returns an Observable that concatenate each item emitted by the source ObservableSource with the values in an + * Iterable corresponding to that item that is generated by a selector. + *

+ * + * + *

+ *
Scheduler:
+ *
{@code concatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting ObservableSource + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source ObservableSource + * @return an Observable that emits the results of concatenating the items emitted by the source ObservableSource with + * the values in the Iterables corresponding to those items, as generated by {@code collectionSelector} + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapIterable(final Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableFlattenIterable(this, mapper)); + } + + /** + * Returns an Observable that concatenate each item emitted by the source ObservableSource with the values in an + * Iterable corresponding to that item that is generated by a selector. + *

+ * + * + *

+ *
Scheduler:
+ *
{@code concatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting ObservableSource + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source ObservableSource + * @param prefetch + * the number of elements to prefetch from the current Observable + * @return an Observable that emits the results of concatenating the items emitted by the source ObservableSource with + * the values in the Iterables corresponding to those items, as generated by {@code collectionSelector} + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapIterable(final Function> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return concatMap(ObservableInternalHelper.flatMapIntoIterable(mapper), prefetch); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other succeeds or completes, emits their success value if available or terminates immediately if + * either this {@code Observable} or the current inner {@code MaybeSource} fail. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @return a new Observable instance + * @see #concatMapMaybeDelayError(Function) + * @see #concatMapMaybe(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapMaybe(Function> mapper) { + return concatMapMaybe(mapper, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other succeeds or completes, emits their success value if available or terminates immediately if + * either this {@code Observable} or the current inner {@code MaybeSource} fail. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code MaybeSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code MaybeSource}s. + * @return a new Observable instance + * @see #concatMapMaybe(Function) + * @see #concatMapMaybeDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapMaybe(Function> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapMaybe(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and delaying all errors + * till both this {@code Observable} and all inner {@code MaybeSource}s terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @return a new Observable instance + * @see #concatMapMaybe(Function) + * @see #concatMapMaybeDelayError(Function, boolean) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapMaybeDelayError(Function> mapper) { + return concatMapMaybeDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and optionally delaying all errors + * till both this {@code Observable} and all inner {@code MaybeSource}s terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code MaybeSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code MaybeSource} terminates and only then is + * it emitted to the downstream. + * @return a new Observable instance + * @see #concatMapMaybe(Function, int) + * @see #concatMapMaybeDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapMaybeDelayError(Function> mapper, boolean tillTheEnd) { + return concatMapMaybeDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and subscribes to them one after the + * other terminates, emits their success value if available and optionally delaying all errors + * till both this {@code Observable} and all inner {@code MaybeSource}s terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code MaybeSource}s + * @param mapper the function called with the upstream item and should return + * a {@code MaybeSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code MaybeSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code MaybeSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code MaybeSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code MaybeSource}s. + * @return a new Observable instance + * @see #concatMapMaybe(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapMaybeDelayError(Function> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapMaybe(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds, emits their success values or terminates immediately if + * either this {@code Observable} or the current inner {@code SingleSource} fail. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @return a new Observable instance + * @see #concatMapSingleDelayError(Function) + * @see #concatMapSingle(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapSingle(Function> mapper) { + return concatMapSingle(mapper, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds, emits their success values or terminates immediately if + * either this {@code Observable} or the current inner {@code SingleSource} fail. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code SingleSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code SingleSource}s. + * @return a new Observable instance + * @see #concatMapSingle(Function) + * @see #concatMapSingleDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapSingle(Function> mapper, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapSingle(this, mapper, ErrorMode.IMMEDIATE, prefetch)); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and delays all errors + * till both this {@code Observable} and all inner {@code SingleSource}s terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @return a new Observable instance + * @see #concatMapSingle(Function) + * @see #concatMapSingleDelayError(Function, boolean) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapSingleDelayError(Function> mapper) { + return concatMapSingleDelayError(mapper, true, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and optionally delays all errors + * till both this {@code Observable} and all inner {@code SingleSource}s terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code SingleSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code SingleSource} terminates and only then is + * it emitted to the downstream. + * @return a new Observable instance + * @see #concatMapSingle(Function, int) + * @see #concatMapSingleDelayError(Function, boolean, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapSingleDelayError(Function> mapper, boolean tillTheEnd) { + return concatMapSingleDelayError(mapper, tillTheEnd, 2); + } + + /** + * Maps the upstream items into {@link SingleSource}s and subscribes to them one after the + * other succeeds or fails, emits their success values and optionally delays errors + * till both this {@code Observable} and all inner {@code SingleSource}s terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the result type of the inner {@code SingleSource}s + * @param mapper the function called with the upstream item and should return + * a {@code SingleSource} to become the next source to + * be subscribed to + * @param tillTheEnd If {@code true}, errors from this {@code Observable} or any of the + * inner {@code SingleSource}s are delayed until all + * of them terminate. If {@code false}, an error from this + * {@code Observable} is delayed until the current inner + * {@code SingleSource} terminates and only then is + * it emitted to the downstream. + * @param prefetch The number of upstream items to prefetch so that fresh items are + * ready to be mapped when a previous {@code SingleSource} terminates. + * The operator replenishes after half of the prefetch amount has been consumed + * and turned into {@code SingleSource}s. + * @return a new Observable instance + * @see #concatMapSingle(Function, int) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatMapSingleDelayError(Function> mapper, boolean tillTheEnd, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ObservableConcatMapSingle(this, mapper, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY, prefetch)); + } + + /** + * Returns an Observable that emits the items emitted from the current ObservableSource, then the next, one after + * the other, without interleaving them. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * an ObservableSource to be concatenated after the current + * @return an Observable that emits items emitted by the two source ObservableSources, one after the other, + * without interleaving them + * @see ReactiveX operators documentation: Concat + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatWith(ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return concat(this, other); + } + + /** + * Returns an {@code Observable} that emits the items from this {@code Observable} followed by the success item or error event + * of the other {@link SingleSource}. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the SingleSource whose signal should be emitted after this {@code Observable} completes normally. + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatWith(@NonNull SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableConcatWithSingle(this, other)); + } + + /** + * Returns an {@code Observable} that emits the items from this {@code Observable} followed by the success item or terminal events + * of the other {@link MaybeSource}. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the MaybeSource whose signal should be emitted after this Observable completes normally. + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatWith(@NonNull MaybeSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableConcatWithMaybe(this, other)); + } + + /** + * Returns an {@code Observable} that emits items from this {@code Observable} and when it completes normally, the + * other {@link CompletableSource} is subscribed to and the returned {@code Observable} emits its terminal events. + *

+ * + *

+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the {@code CompletableSource} to subscribe to once the current {@code Observable} completes normally + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable concatWith(@NonNull CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableConcatWithCompletable(this, other)); + } + + /** + * Returns a Single that emits a Boolean that indicates whether the source ObservableSource emitted a + * specified item. + *

+ * + *

+ *
Scheduler:
+ *
{@code contains} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param element + * the item to search for in the emissions from the source ObservableSource + * @return a Single that emits {@code true} if the specified item is emitted by the source ObservableSource, + * or {@code false} if the source ObservableSource completes without emitting that item + * @see ReactiveX operators documentation: Contains + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single contains(final Object element) { + ObjectHelper.requireNonNull(element, "element is null"); + return any(Functions.equalsWith(element)); + } + + /** + * Returns a Single that counts the total number of items emitted by the source ObservableSource and emits + * this count as a 64-bit Long. + *

+ * + *

+ *
Scheduler:
+ *
{@code count} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Single that emits a single item: the number of items emitted by the source ObservableSource as a + * 64-bit Long item + * @see ReactiveX operators documentation: Count + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single count() { + return RxJavaPlugins.onAssembly(new ObservableCountSingle(this)); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, except that it drops items emitted by the + * source ObservableSource that are followed by another item within a computed debounce duration. + *

+ * + *

+ * The delivery of the item happens on the thread of the first {@code onNext} or {@code onComplete} + * signal of the generated {@code ObservableSource} sequence, + * which if takes too long, a newer item may arrive from the upstream, causing the + * generated sequence to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. + *

+ *
Scheduler:
+ *
This version of {@code debounce} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the debounce value type (ignored) + * @param debounceSelector + * function to retrieve a sequence that indicates the throttle duration for each item + * @return an Observable that omits items emitted by the source ObservableSource that are followed by another item + * within a computed debounce duration + * @see ReactiveX operators documentation: Debounce + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable debounce(Function> debounceSelector) { + ObjectHelper.requireNonNull(debounceSelector, "debounceSelector is null"); + return RxJavaPlugins.onAssembly(new ObservableDebounce(this, debounceSelector)); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, except that it drops items emitted by the + * source ObservableSource that are followed by newer items before a timeout value expires. The timer resets on + * each emission. + *

+ * Note: If items keep being emitted by the source ObservableSource faster than the timeout then no items + * will be emitted by the resulting ObservableSource. + *

+ * + *

+ * Delivery of the item after the grace period happens on the {@code computation} {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. + *

+ *
Scheduler:
+ *
{@code debounce} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * the length of the window of time that must pass after the emission of an item from the source + * ObservableSource in which that ObservableSource emits no items in order for the item to be emitted by the + * resulting ObservableSource + * @param unit + * the unit of time for the specified {@code timeout} + * @return an Observable that filters out items from the source ObservableSource that are too quickly followed by + * newer items + * @see ReactiveX operators documentation: Debounce + * @see #throttleWithTimeout(long, TimeUnit) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable debounce(long timeout, TimeUnit unit) { + return debounce(timeout, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, except that it drops items emitted by the + * source ObservableSource that are followed by newer items before a timeout value expires on a specified + * Scheduler. The timer resets on each emission. + *

+ * Note: If items keep being emitted by the source ObservableSource faster than the timeout then no items + * will be emitted by the resulting ObservableSource. + *

+ * + *

+ * Delivery of the item after the grace period happens on the given {@code Scheduler}'s + * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the + * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation + * (yielding an {@code InterruptedException}). It is recommended processing items + * that may take long time to be moved to another thread via {@link #observeOn} applied after + * {@code debounce} itself. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * the time each item has to be "the most recent" of those emitted by the source ObservableSource to + * ensure that it's not dropped + * @param unit + * the unit of time for the specified {@code timeout} + * @param scheduler + * the {@link Scheduler} to use internally to manage the timers that handle the timeout for each + * item + * @return an Observable that filters out items from the source ObservableSource that are too quickly followed by + * newer items + * @see ReactiveX operators documentation: Debounce + * @see #throttleWithTimeout(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable debounce(long timeout, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableDebounceTimed(this, timeout, unit, scheduler)); + } + + /** + * Returns an Observable that emits the items emitted by the source ObservableSource or a specified default item + * if the source ObservableSource is empty. + *

+ * + *

+ *
Scheduler:
+ *
{@code defaultIfEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * the item to emit if the source ObservableSource emits no items + * @return an Observable that emits either the specified default item if the source ObservableSource emits no + * items, or the items emitted by the source ObservableSource + * @see ReactiveX operators documentation: DefaultIfEmpty + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable defaultIfEmpty(T defaultItem) { + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); + return switchIfEmpty(just(defaultItem)); + } + + /** + * Returns an Observable that delays the emissions of the source ObservableSource via another ObservableSource on a + * per-item basis. + *

+ * + *

+ * Note: the resulting ObservableSource will immediately propagate any {@code onError} notification + * from the source ObservableSource. + *

+ *
Scheduler:
+ *
This version of {@code delay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the item delay value type (ignored) + * @param itemDelay + * a function that returns an ObservableSource for each item emitted by the source ObservableSource, which is + * then used to delay the emission of that item by the resulting ObservableSource until the ObservableSource + * returned from {@code itemDelay} emits an item + * @return an Observable that delays the emissions of the source ObservableSource via another ObservableSource on a + * per-item basis + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable delay(final Function> itemDelay) { + ObjectHelper.requireNonNull(itemDelay, "itemDelay is null"); + return flatMap(ObservableInternalHelper.itemDelay(itemDelay)); + } + + /** + * Returns an Observable that emits the items emitted by the source ObservableSource shifted forward in time by a + * specified delay. Error notifications from the source ObservableSource are not delayed. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code delay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @return the source ObservableSource shifted in time by the specified delay + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable delay(long delay, TimeUnit unit) { + return delay(delay, unit, Schedulers.computation(), false); + } + + /** + * Returns an Observable that emits the items emitted by the source ObservableSource shifted forward in time by a + * specified delay. If {@code delayError} is true, error notifications will also be delayed. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code delay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @param delayError + * if true, the upstream exception is signalled with the given delay, after all preceding normal elements, + * if false, the upstream exception is signalled immediately + * @return the source ObservableSource shifted in time by the specified delay + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable delay(long delay, TimeUnit unit, boolean delayError) { + return delay(delay, unit, Schedulers.computation(), delayError); + } + + /** + * Returns an Observable that emits the items emitted by the source ObservableSource shifted forward in time by a + * specified delay. Error notifications from the source ObservableSource are not delayed. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the {@link Scheduler} to use for delaying + * @return the source ObservableSource shifted in time by the specified delay + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable delay(long delay, TimeUnit unit, Scheduler scheduler) { + return delay(delay, unit, scheduler, false); + } + + /** + * Returns an Observable that emits the items emitted by the source ObservableSource shifted forward in time by a + * specified delay. If {@code delayError} is true, error notifications will also be delayed. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the delay to shift the source by + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the {@link Scheduler} to use for delaying + * @param delayError + * if true, the upstream exception is signalled with the given delay, after all preceding normal elements, + * if false, the upstream exception is signalled immediately + * @return the source ObservableSource shifted in time by the specified delay + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable delay(long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + + return RxJavaPlugins.onAssembly(new ObservableDelay(this, delay, unit, scheduler, delayError)); + } + + /** + * Returns an Observable that delays the subscription to and emissions from the source ObservableSource via another + * ObservableSource on a per-item basis. + *

+ * + *

+ * Note: the resulting ObservableSource will immediately propagate any {@code onError} notification + * from the source ObservableSource. + *

+ *
Scheduler:
+ *
This version of {@code delay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the subscription delay value type (ignored) + * @param + * the item delay value type (ignored) + * @param subscriptionDelay + * a function that returns an ObservableSource that triggers the subscription to the source ObservableSource + * once it emits any item + * @param itemDelay + * a function that returns an ObservableSource for each item emitted by the source ObservableSource, which is + * then used to delay the emission of that item by the resulting ObservableSource until the ObservableSource + * returned from {@code itemDelay} emits an item + * @return an Observable that delays the subscription and emissions of the source ObservableSource via another + * ObservableSource on a per-item basis + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable delay(ObservableSource subscriptionDelay, + Function> itemDelay) { + return delaySubscription(subscriptionDelay).delay(itemDelay); + } + + /** + * Returns an Observable that delays the subscription to this Observable + * until the other Observable emits an element or completes normally. + *

+ * + *

+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the other Observable, irrelevant + * @param other the other Observable that should trigger the subscription + * to this Observable. + * @return an Observable that delays the subscription to this Observable + * until the other Observable emits an element or completes normally. + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable delaySubscription(ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableDelaySubscriptionOther(this, other)); + } + + /** + * Returns an Observable that delays the subscription to the source ObservableSource by a given amount of time. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code delaySubscription} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param delay + * the time to delay the subscription + * @param unit + * the time unit of {@code delay} + * @return an Observable that delays the subscription to the source ObservableSource by the given amount + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable delaySubscription(long delay, TimeUnit unit) { + return delaySubscription(delay, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that delays the subscription to the source ObservableSource by a given amount of time, + * both waiting and subscribing on a given Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param delay + * the time to delay the subscription + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the Scheduler on which the waiting and subscription will happen + * @return an Observable that delays the subscription to the source ObservableSource by a given + * amount, waiting and subscribing on the given Scheduler + * @see ReactiveX operators documentation: Delay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable delaySubscription(long delay, TimeUnit unit, Scheduler scheduler) { + return delaySubscription(timer(delay, unit, scheduler)); + } + + /** + * Returns an Observable that reverses the effect of {@link #materialize materialize} by transforming the + * {@link Notification} objects emitted by the source ObservableSource into the items or notifications they + * represent. + *

+ * + *

+ * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Observable disposes of the flow and terminates with that type of terminal event: + *


+     * Observable.just(createOnNext(1), createOnComplete(), createOnNext(2))
+     * .doOnDispose(() -> System.out.println("Disposed!"));
+     * .dematerialize()
+     * .test()
+     * .assertResult(1);
+     * 
+ * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + *

+     * Observable.just(createOnNext(1), createOnNext(2))
+     * .dematerialize()
+     * .test()
+     * .assertResult(1, 2);
+     * 
+ * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(ObservableSource)} + * with a {@link #never()} source. + *
+ *
Scheduler:
+ *
{@code dematerialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output value type + * @return an Observable that emits the items and notifications embedded in the {@link Notification} objects + * emitted by the source ObservableSource + * @see ReactiveX operators documentation: Dematerialize + * @see #dematerialize(Function) + * @deprecated in 2.2.4; inherently type-unsafe as it overrides the output generic type. Use {@link #dematerialize(Function)} instead. + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @Deprecated + @SuppressWarnings({ "unchecked", "rawtypes" }) + public final Observable dematerialize() { + return RxJavaPlugins.onAssembly(new ObservableDematerialize(this, Functions.identity())); + } + + /** + * Returns an Observable that reverses the effect of {@link #materialize materialize} by transforming the + * {@link Notification} objects extracted from the source items via a selector function + * into their respective {@code Observer} signal types. + *

+ * + *

+ * The intended use of the {@code selector} function is to perform a + * type-safe identity mapping (see example) on a source that is already of type + * {@code Notification}. The Java language doesn't allow + * limiting instance methods to a certain generic argument shape, therefore, + * a function is used to ensure the conversion remains type safe. + *

+ * When the upstream signals an {@link Notification#createOnError(Throwable) onError} or + * {@link Notification#createOnComplete() onComplete} item, the + * returned Observable disposes of the flow and terminates with that type of terminal event: + *


+     * Observable.just(createOnNext(1), createOnComplete(), createOnNext(2))
+     * .doOnDispose(() -> System.out.println("Disposed!"));
+     * .dematerialize(notification -> notification)
+     * .test()
+     * .assertResult(1);
+     * 
+ * If the upstream signals {@code onError} or {@code onComplete} directly, the flow is terminated + * with the same event. + *

+     * Observable.just(createOnNext(1), createOnNext(2))
+     * .dematerialize(notification -> notification)
+     * .test()
+     * .assertResult(1, 2);
+     * 
+ * If this behavior is not desired, the completion can be suppressed by applying {@link #concatWith(ObservableSource)} + * with a {@link #never()} source. + *
+ *
Scheduler:
+ *
{@code dematerialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output value type + * @param selector function that returns the upstream item and should return a Notification to signal + * the corresponding {@code Observer} event to the downstream. + * @return an Observable that emits the items and notifications embedded in the {@link Notification} objects + * selected from the items emitted by the source ObservableSource + * @see ReactiveX operators documentation: Dematerialize + * @since 2.2.4 - experimental + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable dematerialize(Function> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new ObservableDematerialize(this, selector)); + } + + /** + * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct + * based on {@link Object#equals(Object)} comparison. + *

+ * + *

+ * It is recommended the elements' class {@code T} in the flow overrides the default {@code Object.equals()} + * and {@link Object#hashCode()} to provide meaningful comparison between items as the default Java + * implementation only considers reference equivalence. + *

+ * By default, {@code distinct()} uses an internal {@link HashSet} per Observer to remember + * previously seen items and uses {@link Set#add(Object)} returning {@code false} as the + * indicator for duplicates. + *

+ * Note that this internal {@code HashSet} may grow unbounded as items won't be removed from it by + * the operator. Therefore, using very long or infinite upstream (with very distinct elements) may lead + * to {@code OutOfMemoryError}. + *

+ * Customizing the retention policy can happen only by providing a custom {@link Collection} implementation + * to the {@link #distinct(Function, Callable)} overload. + *

+ *
Scheduler:
+ *
{@code distinct} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an Observable that emits only those items emitted by the source ObservableSource that are distinct from + * each other + * @see ReactiveX operators documentation: Distinct + * @see #distinct(Function) + * @see #distinct(Function, Callable) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable distinct() { + return distinct(Functions.identity(), Functions.createHashSet()); + } + + /** + * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct according + * to a key selector function and based on {@link Object#equals(Object)} comparison of the objects + * returned by the key selector function. + *

+ * + *

+ * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} + * and {@link Object#hashCode()} to provide meaningful comparison between the key objects as the default + * Java implementation only considers reference equivalence. + *

+ * By default, {@code distinct()} uses an internal {@link HashSet} per Observer to remember + * previously seen keys and uses {@link Set#add(Object)} returning {@code false} as the + * indicator for duplicates. + *

+ * Note that this internal {@code HashSet} may grow unbounded as keys won't be removed from it by + * the operator. Therefore, using very long or infinite upstream (with very distinct keys) may lead + * to {@code OutOfMemoryError}. + *

+ * Customizing the retention policy can happen only by providing a custom {@link Collection} implementation + * to the {@link #distinct(Function, Callable)} overload. + *

+ *
Scheduler:
+ *
{@code distinct} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type + * @param keySelector + * a function that projects an emitted item to a key value that is used to decide whether an item + * is distinct from another one or not + * @return an Observable that emits those items emitted by the source ObservableSource that have distinct keys + * @see ReactiveX operators documentation: Distinct + * @see #distinct(Function, Callable) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable distinct(Function keySelector) { + return distinct(keySelector, Functions.createHashSet()); + } + + /** + * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct according + * to a key selector function and based on {@link Object#equals(Object)} comparison of the objects + * returned by the key selector function. + *

+ * + *

+ * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} + * and {@link Object#hashCode()} to provide meaningful comparison between the key objects as + * the default Java implementation only considers reference equivalence. + *

+ *
Scheduler:
+ *
{@code distinct} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type + * @param keySelector + * a function that projects an emitted item to a key value that is used to decide whether an item + * is distinct from another one or not + * @param collectionSupplier + * function called for each individual Observer to return a Collection subtype for holding the extracted + * keys and whose add() method's return indicates uniqueness. + * @return an Observable that emits those items emitted by the source ObservableSource that have distinct keys + * @see ReactiveX operators documentation: Distinct + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable distinct(Function keySelector, Callable> collectionSupplier) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(collectionSupplier, "collectionSupplier is null"); + return RxJavaPlugins.onAssembly(new ObservableDistinct(this, keySelector, collectionSupplier)); + } + + /** + * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct from their + * immediate predecessors based on {@link Object#equals(Object)} comparison. + *

+ * + *

+ * It is recommended the elements' class {@code T} in the flow overrides the default {@code Object.equals()} to provide + * meaningful comparison between items as the default Java implementation only considers reference equivalence. + * Alternatively, use the {@link #distinctUntilChanged(BiPredicate)} overload and provide a comparison function + * in case the class {@code T} can't be overridden with custom {@code equals()} or the comparison itself + * should happen on different terms or properties of the class {@code T}. + *

+ * Note that the operator always retains the latest item from upstream regardless of the comparison result + * and uses it in the next comparison with the next upstream item. + *

+ * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. + *

+ *
Scheduler:
+ *
{@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an Observable that emits those items from the source ObservableSource that are distinct from their + * immediate predecessors + * @see ReactiveX operators documentation: Distinct + * @see #distinctUntilChanged(BiPredicate) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable distinctUntilChanged() { + return distinctUntilChanged(Functions.identity()); + } + + /** + * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct from their + * immediate predecessors, according to a key selector function and based on {@link Object#equals(Object)} comparison + * of those objects returned by the key selector function. + *

+ * + *

+ * It is recommended the keys' class {@code K} overrides the default {@code Object.equals()} to provide + * meaningful comparison between the key objects as the default Java implementation only considers reference equivalence. + * Alternatively, use the {@link #distinctUntilChanged(BiPredicate)} overload and provide a comparison function + * in case the class {@code K} can't be overridden with custom {@code equals()} or the comparison itself + * should happen on different terms or properties of the item class {@code T} (for which the keys can be + * derived via a similar selector). + *

+ * Note that the operator always retains the latest key from upstream regardless of the comparison result + * and uses it in the next comparison with the next key derived from the next upstream item. + *

+ * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. + *

+ *
Scheduler:
+ *
{@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type + * @param keySelector + * a function that projects an emitted item to a key value that is used to decide whether an item + * is distinct from another one or not + * @return an Observable that emits those items from the source ObservableSource whose keys are distinct from + * those of their immediate predecessors + * @see ReactiveX operators documentation: Distinct + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable distinctUntilChanged(Function keySelector) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + return RxJavaPlugins.onAssembly(new ObservableDistinctUntilChanged(this, keySelector, ObjectHelper.equalsPredicate())); + } + + /** + * Returns an Observable that emits all items emitted by the source ObservableSource that are distinct from their + * immediate predecessors when compared with each other via the provided comparator function. + *

+ * + *

+ * Note that the operator always retains the latest item from upstream regardless of the comparison result + * and uses it in the next comparison with the next upstream item. + *

+ * Note that if element type {@code T} in the flow is mutable, the comparison of the previous and current + * item may yield unexpected results if the items are mutated externally. Common cases are mutable + * {@code CharSequence}s or {@code List}s where the objects will actually have the same + * references when they are modified and {@code distinctUntilChanged} will evaluate subsequent items as same. + * To avoid such situation, it is recommended that mutable data is converted to an immutable one, + * for example using {@code map(CharSequence::toString)} or {@code map(list -> Collections.unmodifiableList(new ArrayList<>(list)))}. + *

+ *
Scheduler:
+ *
{@code distinctUntilChanged} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param comparer the function that receives the previous item and the current item and is + * expected to return true if the two are equal, thus skipping the current value. + * @return an Observable that emits those items from the source ObservableSource that are distinct from their + * immediate predecessors + * @see ReactiveX operators documentation: Distinct + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable distinctUntilChanged(BiPredicate comparer) { + ObjectHelper.requireNonNull(comparer, "comparer is null"); + return RxJavaPlugins.onAssembly(new ObservableDistinctUntilChanged(this, Functions.identity(), comparer)); + } + + /** + * Calls the specified consumer with the current item after this item has been emitted to the downstream. + *

Note that the {@code onAfterNext} action is shared between subscriptions and as such + * should be thread-safe. + *

+ * + *

+ *
Scheduler:
+ *
{@code doAfterNext} does not operate by default on a particular {@link Scheduler}.
+ *
Operator-fusion:
+ *
This operator supports boundary-limited synchronous or asynchronous queue-fusion.
+ *
+ *

History: 2.0.1 - experimental + * @param onAfterNext the Consumer that will be called after emitting an item from upstream to the downstream + * @return the new Observable instance + * @since 2.1 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doAfterNext(Consumer onAfterNext) { + ObjectHelper.requireNonNull(onAfterNext, "onAfterNext is null"); + return RxJavaPlugins.onAssembly(new ObservableDoAfterNext(this, onAfterNext)); + } + + /** + * Registers an {@link Action} to be called when this ObservableSource invokes either + * {@link Observer#onComplete onComplete} or {@link Observer#onError onError}. + *

+ * + *

+ *
Scheduler:
+ *
{@code doAfterTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onFinally + * an {@link Action} to be invoked when the source ObservableSource finishes + * @return an Observable that emits the same items as the source ObservableSource, then invokes the + * {@link Action} + * @see ReactiveX operators documentation: Do + * @see #doOnTerminate(Action) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doAfterTerminate(Action onFinally) { + ObjectHelper.requireNonNull(onFinally, "onFinally is null"); + return doOnEach(Functions.emptyConsumer(), Functions.emptyConsumer(), Functions.EMPTY_ACTION, onFinally); + } + + /** + * Calls the specified action after this Observable signals onError or onCompleted or gets disposed by + * the downstream. + *

In case of a race between a terminal event and a dispose call, the provided {@code onFinally} action + * is executed once per subscription. + *

Note that the {@code onFinally} action is shared between subscriptions and as such + * should be thread-safe. + *

+ * + *

+ *
Scheduler:
+ *
{@code doFinally} does not operate by default on a particular {@link Scheduler}.
+ *
Operator-fusion:
+ *
This operator supports boundary-limited synchronous or asynchronous queue-fusion.
+ *
+ *

History: 2.0.1 - experimental + * @param onFinally the action called when this Observable terminates or gets disposed + * @return the new Observable instance + * @since 2.1 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doFinally(Action onFinally) { + ObjectHelper.requireNonNull(onFinally, "onFinally is null"); + return RxJavaPlugins.onAssembly(new ObservableDoFinally(this, onFinally)); + } + + /** + * Calls the dispose {@code Action} if the downstream disposes the sequence. + *

+ * The action is shared between subscriptions and thus may be called concurrently from multiple + * threads; the action must be thread safe. + *

+ * If the action throws a runtime exception, that exception is rethrown by the {@code dispose()} call, + * sometimes as a {@code CompositeException} if there were multiple exceptions along the way. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnDispose} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onDispose + * the action that gets called when the source {@code ObservableSource}'s Disposable is disposed + * @return the source {@code ObservableSource} modified so as to call this Action when appropriate + * @throws NullPointerException if onDispose is null + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doOnDispose(Action onDispose) { + return doOnLifecycle(Functions.emptyConsumer(), onDispose); + } + + /** + * Modifies the source ObservableSource so that it invokes an action when it calls {@code onComplete}. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnComplete} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onComplete + * the action to invoke when the source ObservableSource calls {@code onComplete} + * @return the source ObservableSource with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doOnComplete(Action onComplete) { + return doOnEach(Functions.emptyConsumer(), Functions.emptyConsumer(), onComplete, Functions.EMPTY_ACTION); + } + + /** + * Calls the appropriate onXXX consumer (shared between all subscribers) whenever a signal with the same type + * passes through, before forwarding them to downstream. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnEach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the source ObservableSource with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + private Observable doOnEach(Consumer onNext, Consumer onError, Action onComplete, Action onAfterTerminate) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"); + return RxJavaPlugins.onAssembly(new ObservableDoOnEach(this, onNext, onError, onComplete, onAfterTerminate)); + } + + /** + * Modifies the source ObservableSource so that it invokes an action for each item it emits. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnEach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNotification + * the action to invoke for each item emitted by the source ObservableSource + * @return the source ObservableSource with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doOnEach(final Consumer> onNotification) { + ObjectHelper.requireNonNull(onNotification, "onNotification is null"); + return doOnEach( + Functions.notificationOnNext(onNotification), + Functions.notificationOnError(onNotification), + Functions.notificationOnComplete(onNotification), + Functions.EMPTY_ACTION + ); + } + + /** + * Modifies the source ObservableSource so that it notifies an Observer for each item and terminal event it emits. + *

+ * In case the {@code onError} of the supplied observer throws, the downstream will receive a composite + * exception containing the original exception and the exception thrown by {@code onError}. If either the + * {@code onNext} or the {@code onComplete} method of the supplied observer throws, the downstream will be + * terminated and will receive this thrown exception. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnEach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param observer + * the observer to be notified about onNext, onError and onComplete events on its + * respective methods before the actual downstream Observer gets notified. + * @return the source ObservableSource with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doOnEach(final Observer observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); + return doOnEach( + ObservableInternalHelper.observerOnNext(observer), + ObservableInternalHelper.observerOnError(observer), + ObservableInternalHelper.observerOnComplete(observer), + Functions.EMPTY_ACTION); + } + + /** + * Modifies the source ObservableSource so that it invokes an action if it calls {@code onError}. + *

+ * In case the {@code onError} action throws, the downstream will receive a composite exception containing + * the original exception and the exception thrown by {@code onError}. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onError + * the action to invoke if the source ObservableSource calls {@code onError} + * @return the source ObservableSource with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doOnError(Consumer onError) { + return doOnEach(Functions.emptyConsumer(), onError, Functions.EMPTY_ACTION, Functions.EMPTY_ACTION); + } + + /** + * Calls the appropriate onXXX method (shared between all Observer) for the lifecycle events of + * the sequence (subscription, disposal). + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnLifecycle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onSubscribe + * a Consumer called with the Disposable sent via Observer.onSubscribe() + * @param onDispose + * called when the downstream disposes the Disposable via dispose() + * @return the source ObservableSource with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doOnLifecycle(final Consumer onSubscribe, final Action onDispose) { + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + ObjectHelper.requireNonNull(onDispose, "onDispose is null"); + return RxJavaPlugins.onAssembly(new ObservableDoOnLifecycle(this, onSubscribe, onDispose)); + } + + /** + * Modifies the source ObservableSource so that it invokes an action when it calls {@code onNext}. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the action to invoke when the source ObservableSource calls {@code onNext} + * @return the source ObservableSource with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doOnNext(Consumer onNext) { + return doOnEach(onNext, Functions.emptyConsumer(), Functions.EMPTY_ACTION, Functions.EMPTY_ACTION); + } + + /** + * Modifies the source {@code ObservableSource} so that it invokes the given action when it is subscribed from + * its subscribers. Each subscription will result in an invocation of the given action except when the + * source {@code ObservableSource} is reference counted, in which case the source {@code ObservableSource} will invoke + * the given action for the first subscription. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onSubscribe + * the Consumer that gets called when an Observer subscribes to the current {@code Observable} + * @return the source {@code ObservableSource} modified so as to call this Consumer when appropriate + * @see ReactiveX operators documentation: Do + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doOnSubscribe(Consumer onSubscribe) { + return doOnLifecycle(onSubscribe, Functions.EMPTY_ACTION); + } + + /** + * Modifies the source ObservableSource so that it invokes an action when it calls {@code onComplete} or + * {@code onError}. + *

+ * + *

+ * This differs from {@code doAfterTerminate} in that this happens before the {@code onComplete} or + * {@code onError} notification. + *

+ *
Scheduler:
+ *
{@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onTerminate + * the action to invoke when the source ObservableSource calls {@code onComplete} or {@code onError} + * @return the source ObservableSource with the side-effecting behavior applied + * @see ReactiveX operators documentation: Do + * @see #doAfterTerminate(Action) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable doOnTerminate(final Action onTerminate) { + ObjectHelper.requireNonNull(onTerminate, "onTerminate is null"); + return doOnEach(Functions.emptyConsumer(), + Functions.actionConsumer(onTerminate), onTerminate, + Functions.EMPTY_ACTION); + } + + /** + * Returns a Maybe that emits the single item at a specified index in a sequence of emissions from + * this Observable or completes if this Observable signals fewer elements than index. + *

+ * + *

+ *
Scheduler:
+ *
{@code elementAt} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param index + * the zero-based index of the item to retrieve + * @return a Maybe that emits a single item: the item at the specified position in the sequence of + * those emitted by the source ObservableSource + * @throws IndexOutOfBoundsException + * if {@code index} is less than 0 + * @see ReactiveX operators documentation: ElementAt + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe elementAt(long index) { + if (index < 0) { + throw new IndexOutOfBoundsException("index >= 0 required but it was " + index); + } + return RxJavaPlugins.onAssembly(new ObservableElementAtMaybe(this, index)); + } + + /** + * Returns a Single that emits the item found at a specified index in a sequence of emissions from + * this Observable, or a default item if that index is out of range. + *

+ * + *

+ *
Scheduler:
+ *
{@code elementAt} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param index + * the zero-based index of the item to retrieve + * @param defaultItem + * the default item + * @return a Single that emits the item at the specified position in the sequence emitted by the source + * ObservableSource, or the default item if that index is outside the bounds of the source sequence + * @throws IndexOutOfBoundsException + * if {@code index} is less than 0 + * @see ReactiveX operators documentation: ElementAt + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single elementAt(long index, T defaultItem) { + if (index < 0) { + throw new IndexOutOfBoundsException("index >= 0 required but it was " + index); + } + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); + return RxJavaPlugins.onAssembly(new ObservableElementAtSingle(this, index, defaultItem)); + } + + /** + * Returns a Single that emits the item found at a specified index in a sequence of emissions from this Observable + * or signals a {@link NoSuchElementException} if this Observable signals fewer elements than index. + *

+ * + *

+ *
Scheduler:
+ *
{@code elementAtOrError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param index + * the zero-based index of the item to retrieve + * @return a Single that emits the item at the specified position in the sequence emitted by the source + * ObservableSource, or the default item if that index is outside the bounds of the source sequence + * @throws IndexOutOfBoundsException + * if {@code index} is less than 0 + * @see ReactiveX operators documentation: ElementAt + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single elementAtOrError(long index) { + if (index < 0) { + throw new IndexOutOfBoundsException("index >= 0 required but it was " + index); + } + return RxJavaPlugins.onAssembly(new ObservableElementAtSingle(this, index, null)); + } + + /** + * Filters items emitted by an ObservableSource by only emitting those that satisfy a specified predicate. + *

+ * + *

+ *
Scheduler:
+ *
{@code filter} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function that evaluates each item emitted by the source ObservableSource, returning {@code true} + * if it passes the filter + * @return an Observable that emits only those items emitted by the source ObservableSource that the filter + * evaluates as {@code true} + * @see ReactiveX operators documentation: Filter + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable filter(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new ObservableFilter(this, predicate)); + } + + /** + * Returns a Maybe that emits only the very first item emitted by the source ObservableSource, or + * completes if the source ObservableSource is empty. + *

+ * + *

+ *
Scheduler:
+ *
{@code firstElement} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Maybe instance + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe firstElement() { + return elementAt(0L); + } + + /** + * Returns a Single that emits only the very first item emitted by the source ObservableSource, or a default item + * if the source ObservableSource completes without emitting any items. + *

+ * + *

+ *
Scheduler:
+ *
{@code first} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * the default item to emit if the source ObservableSource doesn't emit anything + * @return the new Single instance + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single first(T defaultItem) { + return elementAt(0L, defaultItem); + } + + /** + * Returns a Single that emits only the very first item emitted by this Observable or + * signals a {@link NoSuchElementException} if this Observable is empty. + *

+ * + *

+ *
Scheduler:
+ *
{@code firstOrError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Single instance + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single firstOrError() { + return elementAtOrError(0L); + } + + /** + * Returns an Observable that emits items based on applying a function that you supply to each item emitted + * by the source ObservableSource, where that function returns an ObservableSource, and then merging those resulting + * ObservableSources and emitting the results of this merger. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner ObservableSources and the output type + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @return an Observable that emits the result of applying the transformation function to each item emitted + * by the source ObservableSource and merging the results of the ObservableSources obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(Function> mapper) { + return flatMap(mapper, false); + } + + /** + * Returns an Observable that emits items based on applying a function that you supply to each item emitted + * by the source ObservableSource, where that function returns an ObservableSource, and then merging those resulting + * ObservableSources and emitting the results of this merger. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner ObservableSources and the output type + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @param delayErrors + * if true, exceptions from the current Observable and all inner ObservableSources are delayed until all of them terminate + * if false, the first one signalling an exception will terminate the whole sequence immediately + * @return an Observable that emits the result of applying the transformation function to each item emitted + * by the source ObservableSource and merging the results of the ObservableSources obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(Function> mapper, boolean delayErrors) { + return flatMap(mapper, delayErrors, Integer.MAX_VALUE); + } + + /** + * Returns an Observable that emits items based on applying a function that you supply to each item emitted + * by the source ObservableSource, where that function returns an ObservableSource, and then merging those resulting + * ObservableSources and emitting the results of this merger, while limiting the maximum number of concurrent + * subscriptions to these ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner ObservableSources and the output type + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @param delayErrors + * if true, exceptions from the current Observable and all inner ObservableSources are delayed until all of them terminate + * if false, the first one signalling an exception will terminate the whole sequence immediately + * @return an Observable that emits the result of applying the transformation function to each item emitted + * by the source ObservableSource and merging the results of the ObservableSources obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(Function> mapper, boolean delayErrors, int maxConcurrency) { + return flatMap(mapper, delayErrors, maxConcurrency, bufferSize()); + } + + /** + * Returns an Observable that emits items based on applying a function that you supply to each item emitted + * by the source ObservableSource, where that function returns an ObservableSource, and then merging those resulting + * ObservableSources and emitting the results of this merger, while limiting the maximum number of concurrent + * subscriptions to these ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner ObservableSources and the output type + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @param delayErrors + * if true, exceptions from the current Observable and all inner ObservableSources are delayed until all of them terminate + * if false, the first one signalling an exception will terminate the whole sequence immediately + * @param bufferSize + * the number of elements to prefetch from each inner ObservableSource + * @return an Observable that emits the result of applying the transformation function to each item emitted + * by the source ObservableSource and merging the results of the ObservableSources obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(Function> mapper, + boolean delayErrors, int maxConcurrency, int bufferSize) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + if (this instanceof ScalarCallable) { + @SuppressWarnings("unchecked") + T v = ((ScalarCallable)this).call(); + if (v == null) { + return empty(); + } + return ObservableScalarXMap.scalarXMap(v, mapper); + } + return RxJavaPlugins.onAssembly(new ObservableFlatMap(this, mapper, delayErrors, maxConcurrency, bufferSize)); + } + + /** + * Returns an Observable that applies a function to each item emitted or notification raised by the source + * ObservableSource and then flattens the ObservableSources returned from these functions and emits the resulting items. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the result type + * @param onNextMapper + * a function that returns an ObservableSource to merge for each item emitted by the source ObservableSource + * @param onErrorMapper + * a function that returns an ObservableSource to merge for an onError notification from the source + * ObservableSource + * @param onCompleteSupplier + * a function that returns an ObservableSource to merge for an onComplete notification from the source + * ObservableSource + * @return an Observable that emits the results of merging the ObservableSources returned from applying the + * specified functions to the emissions and notifications of the source ObservableSource + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap( + Function> onNextMapper, + Function> onErrorMapper, + Callable> onCompleteSupplier) { + ObjectHelper.requireNonNull(onNextMapper, "onNextMapper is null"); + ObjectHelper.requireNonNull(onErrorMapper, "onErrorMapper is null"); + ObjectHelper.requireNonNull(onCompleteSupplier, "onCompleteSupplier is null"); + return merge(new ObservableMapNotification(this, onNextMapper, onErrorMapper, onCompleteSupplier)); + } + + /** + * Returns an Observable that applies a function to each item emitted or notification raised by the source + * ObservableSource and then flattens the ObservableSources returned from these functions and emits the resulting items, + * while limiting the maximum number of concurrent subscriptions to these ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the result type + * @param onNextMapper + * a function that returns an ObservableSource to merge for each item emitted by the source ObservableSource + * @param onErrorMapper + * a function that returns an ObservableSource to merge for an onError notification from the source + * ObservableSource + * @param onCompleteSupplier + * a function that returns an ObservableSource to merge for an onComplete notification from the source + * ObservableSource + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @return an Observable that emits the results of merging the ObservableSources returned from applying the + * specified functions to the emissions and notifications of the source ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap( + Function> onNextMapper, + Function> onErrorMapper, + Callable> onCompleteSupplier, + int maxConcurrency) { + ObjectHelper.requireNonNull(onNextMapper, "onNextMapper is null"); + ObjectHelper.requireNonNull(onErrorMapper, "onErrorMapper is null"); + ObjectHelper.requireNonNull(onCompleteSupplier, "onCompleteSupplier is null"); + return merge(new ObservableMapNotification(this, onNextMapper, onErrorMapper, onCompleteSupplier), maxConcurrency); + } + + /** + * Returns an Observable that emits items based on applying a function that you supply to each item emitted + * by the source ObservableSource, where that function returns an ObservableSource, and then merging those resulting + * ObservableSources and emitting the results of this merger, while limiting the maximum number of concurrent + * subscriptions to these ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the inner ObservableSources and the output type + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @return an Observable that emits the result of applying the transformation function to each item emitted + * by the source ObservableSource and merging the results of the ObservableSources obtained from this + * transformation + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(Function> mapper, int maxConcurrency) { + return flatMap(mapper, false, maxConcurrency, bufferSize()); + } + + /** + * Returns an Observable that emits the results of a specified function to the pair of values emitted by the + * source ObservableSource and a specified collection ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the collection ObservableSource + * @param + * the type of items emitted by the resulting ObservableSource + * @param mapper + * a function that returns an ObservableSource for each item emitted by the source ObservableSource + * @param resultSelector + * a function that combines one item emitted by each of the source and collection ObservableSources and + * returns an item to be emitted by the resulting ObservableSource + * @return an Observable that emits the results of applying a function to a pair of values emitted by the + * source ObservableSource and the collection ObservableSource + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(Function> mapper, + BiFunction resultSelector) { + return flatMap(mapper, resultSelector, false, bufferSize(), bufferSize()); + } + + /** + * Returns an Observable that emits the results of a specified function to the pair of values emitted by the + * source ObservableSource and a specified collection ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the collection ObservableSource + * @param + * the type of items emitted by the resulting ObservableSource + * @param mapper + * a function that returns an ObservableSource for each item emitted by the source ObservableSource + * @param combiner + * a function that combines one item emitted by each of the source and collection ObservableSources and + * returns an item to be emitted by the resulting ObservableSource + * @param delayErrors + * if true, exceptions from the current Observable and all inner ObservableSources are delayed until all of them terminate + * if false, the first one signalling an exception will terminate the whole sequence immediately + * @return an Observable that emits the results of applying a function to a pair of values emitted by the + * source ObservableSource and the collection ObservableSource + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(Function> mapper, + BiFunction combiner, boolean delayErrors) { + return flatMap(mapper, combiner, delayErrors, bufferSize(), bufferSize()); + } + + /** + * Returns an Observable that emits the results of a specified function to the pair of values emitted by the + * source ObservableSource and a specified collection ObservableSource, while limiting the maximum number of concurrent + * subscriptions to these ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the collection ObservableSource + * @param + * the type of items emitted by the resulting ObservableSource + * @param mapper + * a function that returns an ObservableSource for each item emitted by the source ObservableSource + * @param combiner + * a function that combines one item emitted by each of the source and collection ObservableSources and + * returns an item to be emitted by the resulting ObservableSource + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @param delayErrors + * if true, exceptions from the current Observable and all inner ObservableSources are delayed until all of them terminate + * if false, the first one signalling an exception will terminate the whole sequence immediately + * @return an Observable that emits the results of applying a function to a pair of values emitted by the + * source ObservableSource and the collection ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(Function> mapper, + BiFunction combiner, boolean delayErrors, int maxConcurrency) { + return flatMap(mapper, combiner, delayErrors, maxConcurrency, bufferSize()); + } + + /** + * Returns an Observable that emits the results of a specified function to the pair of values emitted by the + * source ObservableSource and a specified collection ObservableSource, while limiting the maximum number of concurrent + * subscriptions to these ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the collection ObservableSource + * @param + * the type of items emitted by the resulting ObservableSource + * @param mapper + * a function that returns an ObservableSource for each item emitted by the source ObservableSource + * @param combiner + * a function that combines one item emitted by each of the source and collection ObservableSources and + * returns an item to be emitted by the resulting ObservableSource + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @param delayErrors + * if true, exceptions from the current Observable and all inner ObservableSources are delayed until all of them terminate + * if false, the first one signalling an exception will terminate the whole sequence immediately + * @param bufferSize + * the number of elements to prefetch from the inner ObservableSources. + * @return an Observable that emits the results of applying a function to a pair of values emitted by the + * source ObservableSource and the collection ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(final Function> mapper, + final BiFunction combiner, boolean delayErrors, int maxConcurrency, int bufferSize) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + return flatMap(ObservableInternalHelper.flatMapWithCombiner(mapper, combiner), delayErrors, maxConcurrency, bufferSize); + } + + /** + * Returns an Observable that emits the results of a specified function to the pair of values emitted by the + * source ObservableSource and a specified collection ObservableSource, while limiting the maximum number of concurrent + * subscriptions to these ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the collection ObservableSource + * @param + * the type of items emitted by the resulting ObservableSource + * @param mapper + * a function that returns an ObservableSource for each item emitted by the source ObservableSource + * @param combiner + * a function that combines one item emitted by each of the source and collection ObservableSources and + * returns an item to be emitted by the resulting ObservableSource + * @param maxConcurrency + * the maximum number of ObservableSources that may be subscribed to concurrently + * @return an Observable that emits the results of applying a function to a pair of values emitted by the + * source ObservableSource and the collection ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMap(Function> mapper, + BiFunction combiner, int maxConcurrency) { + return flatMap(mapper, combiner, false, maxConcurrency, bufferSize()); + } + + /** + * Maps each element of the upstream Observable into CompletableSources, subscribes to them and + * waits until the upstream and all CompletableSources complete. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param mapper the function that received each source value and transforms them into CompletableSources. + * @return the new Completable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable flatMapCompletable(Function mapper) { + return flatMapCompletable(mapper, false); + } + + /** + * Maps each element of the upstream Observable into CompletableSources, subscribes to them and + * waits until the upstream and all CompletableSources complete, optionally delaying all errors. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param mapper the function that received each source value and transforms them into CompletableSources. + * @param delayErrors if true errors from the upstream and inner CompletableSources are delayed until each of them + * terminates. + * @return the new Completable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable flatMapCompletable(Function mapper, boolean delayErrors) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableFlatMapCompletableCompletable(this, mapper, delayErrors)); + } + + /** + * Returns an Observable that merges each item emitted by the source ObservableSource with the values in an + * Iterable corresponding to that item that is generated by a selector. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting Iterable + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source ObservableSource + * @return an Observable that emits the results of merging the items emitted by the source ObservableSource with + * the values in the Iterables corresponding to those items, as generated by {@code collectionSelector} + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMapIterable(final Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableFlattenIterable(this, mapper)); + } + + /** + * Returns an Observable that emits the results of applying a function to the pair of values from the source + * ObservableSource and an Iterable corresponding to that item that is generated by a selector. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapIterable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the collection element type + * @param + * the type of item emitted by the resulting Iterable + * @param mapper + * a function that returns an Iterable sequence of values for each item emitted by the source + * ObservableSource + * @param resultSelector + * a function that returns an item based on the item emitted by the source ObservableSource and the + * Iterable returned for that item by the {@code collectionSelector} + * @return an Observable that emits the items returned by {@code resultSelector} for each item in the source + * ObservableSource + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMapIterable(final Function> mapper, + BiFunction resultSelector) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.requireNonNull(resultSelector, "resultSelector is null"); + return flatMap(ObservableInternalHelper.flatMapIntoIterable(mapper), resultSelector, false, bufferSize(), bufferSize()); + } + + /** + * Maps each element of the upstream Observable into MaybeSources, subscribes to all of them + * and merges their onSuccess values, in no particular order, into a single Observable sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result value type + * @param mapper the function that received each source value and transforms them into MaybeSources. + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMapMaybe(Function> mapper) { + return flatMapMaybe(mapper, false); + } + + /** + * Maps each element of the upstream Observable into MaybeSources, subscribes to them + * and merges their onSuccess values, in no particular order, into a single Observable sequence, + * optionally delaying all errors. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result value type + * @param mapper the function that received each source value and transforms them into MaybeSources. + * @param delayErrors if true errors from the upstream and inner MaybeSources are delayed until each of them + * terminates. + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMapMaybe(Function> mapper, boolean delayErrors) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableFlatMapMaybe(this, mapper, delayErrors)); + } + + /** + * Maps each element of the upstream Observable into SingleSources, subscribes to all of them + * and merges their onSuccess values, in no particular order, into a single Observable sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result value type + * @param mapper the function that received each source value and transforms them into SingleSources. + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMapSingle(Function> mapper) { + return flatMapSingle(mapper, false); + } + + /** + * Maps each element of the upstream Observable into SingleSources, subscribes to them + * and merges their onSuccess values, in no particular order, into a single Observable sequence, + * optionally delaying all errors. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result value type + * @param mapper the function that received each source value and transforms them into SingleSources. + * @param delayErrors if true errors from the upstream and inner SingleSources are delayed until each of them + * terminates. + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMapSingle(Function> mapper, boolean delayErrors) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableFlatMapSingle(this, mapper, delayErrors)); + } + + /** + * Subscribes to the {@link ObservableSource} and receives notifications for each element. + *

+ * + *

+ * Alias to {@link #subscribe(Consumer)} + *

+ *
Scheduler:
+ *
{@code forEach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * {@link Consumer} to execute for each item. + * @return + * a Disposable that allows disposing of an asynchronous sequence + * @throws NullPointerException + * if {@code onNext} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable forEach(Consumer onNext) { + return subscribe(onNext); + } + + /** + * Subscribes to the {@link ObservableSource} and receives notifications for each element until the + * onNext Predicate returns false. + *

+ * + *

+ * If the Observable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Scheduler:
+ *
{@code forEachWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * {@link Predicate} to execute for each item. + * @return + * a Disposable that allows disposing of an asynchronous sequence + * @throws NullPointerException + * if {@code onNext} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable forEachWhile(Predicate onNext) { + return forEachWhile(onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to the {@link ObservableSource} and receives notifications for each element and error events until the + * onNext Predicate returns false. + *
+ *
Scheduler:
+ *
{@code forEachWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * {@link Predicate} to execute for each item. + * @param onError + * {@link Consumer} to execute when an error is emitted. + * @return + * a Disposable that allows disposing of an asynchronous sequence + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable forEachWhile(Predicate onNext, Consumer onError) { + return forEachWhile(onNext, onError, Functions.EMPTY_ACTION); + } + + /** + * Subscribes to the {@link ObservableSource} and receives notifications for each element and the terminal events until the + * onNext Predicate returns false. + *
+ *
Scheduler:
+ *
{@code forEachWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * {@link Predicate} to execute for each item. + * @param onError + * {@link Consumer} to execute when an error is emitted. + * @param onComplete + * {@link Action} to execute when completion is signalled. + * @return + * a Disposable that allows disposing of an asynchronous sequence + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null, or + * if {@code onComplete} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable forEachWhile(final Predicate onNext, Consumer onError, + final Action onComplete) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + + ForEachWhileObserver o = new ForEachWhileObserver(onNext, onError, onComplete); + subscribe(o); + return o; + } + + /** + * Groups the items emitted by an {@code ObservableSource} according to a specified criterion, and emits these + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservableSource} allows only a single + * {@link Observer} during its lifetime and if this {@code Observer} calls dispose() before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedObservableSource} emission. + *

+ * + *

+ * Note: A {@link GroupedObservable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedObservableSource}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param + * the key type + * @return an {@code ObservableSource} that emits {@link GroupedObservable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source ObservableSource that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> groupBy(Function keySelector) { + return groupBy(keySelector, (Function)Functions.identity(), false, bufferSize()); + } + + /** + * Groups the items emitted by an {@code ObservableSource} according to a specified criterion, and emits these + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservableSource} allows only a single + * {@link Observer} during its lifetime and if this {@code Observer} calls dispose() before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedObservableSource} emission. + *

+ * + *

+ * Note: A {@link GroupedObservable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedObservableSource}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param + * the key type + * @param delayError + * if true, the exception from the current Observable is delayed in each group until that specific group emitted + * the normal values; if false, the exception bypasses values in the groups and is reported immediately. + * @return an {@code ObservableSource} that emits {@link GroupedObservable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source ObservableSource that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> groupBy(Function keySelector, boolean delayError) { + return groupBy(keySelector, (Function)Functions.identity(), delayError, bufferSize()); + } + + /** + * Groups the items emitted by an {@code ObservableSource} according to a specified criterion, and emits these + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservableSource} allows only a single + * {@link Observer} during its lifetime and if this {@code Observer} calls dispose() before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedObservableSource} emission. + *

+ * + *

+ * Note: A {@link GroupedObservable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedObservableSource}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param valueSelector + * a function that extracts the return element for each item + * @param + * the key type + * @param + * the element type + * @return an {@code ObservableSource} that emits {@link GroupedObservable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source ObservableSource that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> groupBy(Function keySelector, + Function valueSelector) { + return groupBy(keySelector, valueSelector, false, bufferSize()); + } + + /** + * Groups the items emitted by an {@code ObservableSource} according to a specified criterion, and emits these + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservableSource} allows only a single + * {@link Observer} during its lifetime and if this {@code Observer} calls dispose() before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedObservableSource} emission. + *

+ * + *

+ * Note: A {@link GroupedObservable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedObservableSource}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param valueSelector + * a function that extracts the return element for each item + * @param + * the key type + * @param + * the element type + * @param delayError + * if true, the exception from the current Observable is delayed in each group until that specific group emitted + * the normal values; if false, the exception bypasses values in the groups and is reported immediately. + * @return an {@code ObservableSource} that emits {@link GroupedObservable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source ObservableSource that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> groupBy(Function keySelector, + Function valueSelector, boolean delayError) { + return groupBy(keySelector, valueSelector, delayError, bufferSize()); + } + + /** + * Groups the items emitted by an {@code ObservableSource} according to a specified criterion, and emits these + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservableSource} allows only a single + * {@link Observer} during its lifetime and if this {@code Observer} calls dispose() before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedObservableSource} emission. + *

+ * + *

+ * Note: A {@link GroupedObservable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedObservableSource}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item + * @param valueSelector + * a function that extracts the return element for each item + * @param delayError + * if true, the exception from the current Observable is delayed in each group until that specific group emitted + * the normal values; if false, the exception bypasses values in the groups and is reported immediately. + * @param bufferSize + * the hint for how many {@link GroupedObservable}s and element in each {@link GroupedObservable} should be buffered + * @param + * the key type + * @param + * the element type + * @return an {@code ObservableSource} that emits {@link GroupedObservable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source ObservableSource that share that + * key value + * @see ReactiveX operators documentation: GroupBy + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> groupBy(Function keySelector, + Function valueSelector, + boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + + return RxJavaPlugins.onAssembly(new ObservableGroupBy(this, keySelector, valueSelector, bufferSize, delayError)); + } + + /** + * Returns an Observable that correlates two ObservableSources when they overlap in time and groups the results. + *

+ * There are no guarantees in what order the items get combined when multiple + * items from one or both source ObservableSources overlap. + *

+ * + *

+ *
Scheduler:
+ *
{@code groupJoin} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the right ObservableSource source + * @param the element type of the left duration ObservableSources + * @param the element type of the right duration ObservableSources + * @param the result type + * @param other + * the other ObservableSource to correlate items from the source ObservableSource with + * @param leftEnd + * a function that returns an ObservableSource whose emissions indicate the duration of the values of + * the source ObservableSource + * @param rightEnd + * a function that returns an ObservableSource whose emissions indicate the duration of the values of + * the {@code right} ObservableSource + * @param resultSelector + * a function that takes an item emitted by each ObservableSource and returns the value to be emitted + * by the resulting ObservableSource + * @return an Observable that emits items based on combining those items emitted by the source ObservableSources + * whose durations overlap + * @see ReactiveX operators documentation: Join + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable groupJoin( + ObservableSource other, + Function> leftEnd, + Function> rightEnd, + BiFunction, ? extends R> resultSelector + ) { + ObjectHelper.requireNonNull(other, "other is null"); + ObjectHelper.requireNonNull(leftEnd, "leftEnd is null"); + ObjectHelper.requireNonNull(rightEnd, "rightEnd is null"); + ObjectHelper.requireNonNull(resultSelector, "resultSelector is null"); + return RxJavaPlugins.onAssembly(new ObservableGroupJoin( + this, other, leftEnd, rightEnd, resultSelector)); + } + + /** + * Hides the identity of this Observable and its Disposable. + *

Allows hiding extra features such as {@link io.reactivex.subjects.Subject}'s + * {@link Observer} methods or preventing certain identity-based + * optimizations (fusion). + *

+ * + *

+ *
Scheduler:
+ *
{@code hide} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Observable instance + * + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable hide() { + return RxJavaPlugins.onAssembly(new ObservableHide(this)); + } + + /** + * Ignores all items emitted by the source ObservableSource and only calls {@code onComplete} or {@code onError}. + *

+ * + *

+ *
Scheduler:
+ *
{@code ignoreElements} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Completable instance + * @see ReactiveX operators documentation: IgnoreElements + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable ignoreElements() { + return RxJavaPlugins.onAssembly(new ObservableIgnoreElementsCompletable(this)); + } + + /** + * Returns a Single that emits {@code true} if the source ObservableSource is empty, otherwise {@code false}. + *

+ * In Rx.Net this is negated as the {@code any} Observer but we renamed this in RxJava to better match Java + * naming idioms. + *

+ * + *

+ *
Scheduler:
+ *
{@code isEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Single that emits a Boolean + * @see ReactiveX operators documentation: Contains + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single isEmpty() { + return all(Functions.alwaysFalse()); + } + + /** + * Correlates the items emitted by two ObservableSources based on overlapping durations. + *

+ * There are no guarantees in what order the items get combined when multiple + * items from one or both source ObservableSources overlap. + *

+ * + *

+ *
Scheduler:
+ *
{@code join} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the right ObservableSource source + * @param the element type of the left duration ObservableSources + * @param the element type of the right duration ObservableSources + * @param the result type + * @param other + * the second ObservableSource to join items from + * @param leftEnd + * a function to select a duration for each item emitted by the source ObservableSource, used to + * determine overlap + * @param rightEnd + * a function to select a duration for each item emitted by the {@code right} ObservableSource, used to + * determine overlap + * @param resultSelector + * a function that computes an item to be emitted by the resulting ObservableSource for any two + * overlapping items emitted by the two ObservableSources + * @return an Observable that emits items correlating to items emitted by the source ObservableSources that have + * overlapping durations + * @see ReactiveX operators documentation: Join + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable join( + ObservableSource other, + Function> leftEnd, + Function> rightEnd, + BiFunction resultSelector + ) { + ObjectHelper.requireNonNull(other, "other is null"); + ObjectHelper.requireNonNull(leftEnd, "leftEnd is null"); + ObjectHelper.requireNonNull(rightEnd, "rightEnd is null"); + ObjectHelper.requireNonNull(resultSelector, "resultSelector is null"); + return RxJavaPlugins.onAssembly(new ObservableJoin( + this, other, leftEnd, rightEnd, resultSelector)); + } + + /** + * Returns a Maybe that emits the last item emitted by this Observable or + * completes if this Observable is empty. + *

+ * + *

+ *
Scheduler:
+ *
{@code lastElement} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Maybe that emits the last item from the source ObservableSource or notifies observers of an + * error + * @see ReactiveX operators documentation: Last + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe lastElement() { + return RxJavaPlugins.onAssembly(new ObservableLastMaybe(this)); + } + + /** + * Returns a Single that emits only the last item emitted by this Observable, or a default item + * if this Observable completes without emitting any items. + *

+ * + *

+ *
Scheduler:
+ *
{@code last} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * the default item to emit if the source ObservableSource is empty + * @return a Single that emits only the last item emitted by the source ObservableSource, or a default item + * if the source ObservableSource is empty + * @see ReactiveX operators documentation: Last + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single last(T defaultItem) { + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); + return RxJavaPlugins.onAssembly(new ObservableLastSingle(this, defaultItem)); + } + + /** + * Returns a Single that emits only the last item emitted by this Observable or + * signals a {@link NoSuchElementException} if this Observable is empty. + *

+ * + *

+ *
Scheduler:
+ *
{@code lastOrError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Single that emits only the last item emitted by the source ObservableSource. + * If the source ObservableSource completes without emitting any items a {@link NoSuchElementException} will be thrown. + * @see ReactiveX operators documentation: Last + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single lastOrError() { + return RxJavaPlugins.onAssembly(new ObservableLastSingle(this, null)); + } + + /** + * This method requires advanced knowledge about building operators, please consider + * other standard composition methods first; + * Returns an {@code Observable} which, when subscribed to, invokes the {@link ObservableOperator#apply(Observer) apply(Observer)} method + * of the provided {@link ObservableOperator} for each individual downstream {@link Observer} and allows the + * insertion of a custom operator by accessing the downstream's {@link Observer} during this subscription phase + * and providing a new {@code Observer}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + *

+ * Generally, such a new {@code Observer} will wrap the downstream's {@code Observer} and forwards the + * {@code onNext}, {@code onError} and {@code onComplete} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code dispose} and {@code isDisposed} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. + *

+ * Example: + *


+     * // Step 1: Create the consumer type that will be returned by the ObservableOperator.apply():
+     *
+     * public final class CustomObserver<T> implements Observer<T>, Disposable {
+     *
+     *     // The downstream's Observer that will receive the onXXX events
+     *     final Observer<? super String> downstream;
+     *
+     *     // The connection to the upstream source that will call this class' onXXX methods
+     *     Disposable upstream;
+     *
+     *     // The constructor takes the downstream subscriber and usually any other parameters
+     *     public CustomObserver(Observer<? super String> downstream) {
+     *         this.downstream = downstream;
+     *     }
+     *
+     *     // In the subscription phase, the upstream sends a Disposable to this class
+     *     // and subsequently this class has to send a Disposable to the downstream.
+     *     // Note that relaying the upstream's Disposable directly is not allowed in RxJava
+     *     @Override
+     *     public void onSubscribe(Disposable d) {
+     *         if (upstream != null) {
+     *             d.dispose();
+     *         } else {
+     *             upstream = d;
+     *             downstream.onSubscribe(this);
+     *         }
+     *     }
+     *
+     *     // The upstream calls this with the next item and the implementation's
+     *     // responsibility is to emit an item to the downstream based on the intended
+     *     // business logic, or if it can't do so for the particular item,
+     *     // request more from the upstream
+     *     @Override
+     *     public void onNext(T item) {
+     *         String str = item.toString();
+     *         if (str.length() < 2) {
+     *             downstream.onNext(str);
+     *         }
+     *         // Observable doesn't support backpressure, therefore, there is no
+     *         // need or opportunity to call upstream.request(1) if an item
+     *         // is not produced to the downstream
+     *     }
+     *
+     *     // Some operators may handle the upstream's error while others
+     *     // could just forward it to the downstream.
+     *     @Override
+     *     public void onError(Throwable throwable) {
+     *         downstream.onError(throwable);
+     *     }
+     *
+     *     // When the upstream completes, usually the downstream should complete as well.
+     *     @Override
+     *     public void onComplete() {
+     *         downstream.onComplete();
+     *     }
+     *
+     *     // Some operators may use their own resources which should be cleaned up if
+     *     // the downstream disposes the flow before it completed. Operators without
+     *     // resources can simply forward the dispose to the upstream.
+     *     // In some cases, a disposed flag may be set by this method so that other parts
+     *     // of this class may detect the dispose and stop sending events
+     *     // to the downstream.
+     *     @Override
+     *     public void dispose() {
+     *         upstream.dispose();
+     *     }
+     *
+     *     // Some operators may simply forward the call to the upstream while others
+     *     // can return the disposed flag set in dispose().
+     *     @Override
+     *     public boolean isDisposed() {
+     *         return upstream.isDisposed();
+     *     }
+     * }
+     *
+     * // Step 2: Create a class that implements the ObservableOperator interface and
+     * //         returns the custom consumer type from above in its apply() method.
+     * //         Such class may define additional parameters to be submitted to
+     * //         the custom consumer type.
+     *
+     * final class CustomOperator<T> implements ObservableOperator<String, T> {
+     *     @Override
+     *     public Observer<T> apply(Observer<? super String> downstream) {
+     *         return new CustomObserver<T>(downstream);
+     *     }
+     * }
+     *
+     * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it
+     * //         or reusing an existing one.
+     *
+     * Observable.range(5, 10)
+     * .lift(new CustomOperator<Integer>())
+     * .test()
+     * .assertResult("5", "6", "7", "8", "9");
+     * 
+ *

+ * Creating custom operators can be complicated and it is recommended one consults the + * RxJava wiki: Writing operators page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. + *

+ * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Observable} + * class and creating an {@link ObservableTransformer} with it is recommended. + *

+ * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code Observer} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Observable}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return an {@code Observer} that should immediately dispose the upstream's {@code Disposable} in its + * {@code onSubscribe} method. Again, using an {@code ObservableTransformer} and extending the {@code Observable} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. + *

+ *
Scheduler:
+ *
{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link ObservableOperator} may use a {@code Scheduler} to support its own asynchronous behavior.
+ *
+ * + * @param the output value type + * @param lifter the {@link ObservableOperator} that receives the downstream's {@code Observer} and should return + * an {@code Observer} with custom behavior to be used as the consumer for the current + * {@code Observable}. + * @return the new Observable instance + * @see RxJava wiki: Writing operators + * @see #compose(ObservableTransformer) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable lift(ObservableOperator lifter) { + ObjectHelper.requireNonNull(lifter, "lifter is null"); + return RxJavaPlugins.onAssembly(new ObservableLift(this, lifter)); + } + + /** + * Returns an Observable that applies a specified function to each item emitted by the source ObservableSource and + * emits the results of these function applications. + *

+ * + *

+ *
Scheduler:
+ *
{@code map} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output type + * @param mapper + * a function to apply to each item emitted by the ObservableSource + * @return an Observable that emits the items from the source ObservableSource, transformed by the specified + * function + * @see ReactiveX operators documentation: Map + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable map(Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableMap(this, mapper)); + } + + /** + * Returns an Observable that represents all of the emissions and notifications from the source + * ObservableSource into emissions marked with their original types within {@link Notification} objects. + *

+ * + *

+ *
Scheduler:
+ *
{@code materialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an Observable that emits items that are the result of materializing the items and notifications + * of the source ObservableSource + * @see ReactiveX operators documentation: Materialize + * @see #dematerialize(Function) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> materialize() { + return RxJavaPlugins.onAssembly(new ObservableMaterialize(this)); + } + + /** + * Flattens this and another ObservableSource into a single ObservableSource, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple ObservableSources so that they appear as a single ObservableSource, by + * using the {@code mergeWith} method. + *

+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * an ObservableSource to be merged + * @return an Observable that emits all of the items emitted by the source ObservableSources + * @see ReactiveX operators documentation: Merge + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable mergeWith(ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return merge(this, other); + } + + /** + * Merges the sequence of items of this Observable with the success value of the other SingleSource. + *

+ * + *

+ * The success value of the other {@code SingleSource} can get interleaved at any point of this + * {@code Observable} sequence. + *

+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the {@code SingleSource} whose success value to merge with + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable mergeWith(@NonNull SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableMergeWithSingle(this, other)); + } + + /** + * Merges the sequence of items of this Observable with the success value of the other MaybeSource + * or waits both to complete normally if the MaybeSource is empty. + *

+ * + *

+ * The success value of the other {@code MaybeSource} can get interleaved at any point of this + * {@code Observable} sequence. + *

+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the {@code MaybeSource} which provides a success value to merge with or completes + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable mergeWith(@NonNull MaybeSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableMergeWithMaybe(this, other)); + } + + /** + * Relays the items of this Observable and completes only when the other CompletableSource completes + * as well. + *

+ * + *

+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.10 - experimental + * @param other the {@code CompletableSource} to await for completion + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable mergeWith(@NonNull CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableMergeWithCompletable(this, other)); + } + + /** + * Modifies an ObservableSource to perform its emissions and notifications on a specified {@link Scheduler}, + * asynchronously with an unbounded buffer with {@link Flowable#bufferSize()} "island size". + * + *

Note that onError notifications will cut ahead of onNext notifications on the emission thread if Scheduler is truly + * asynchronous. If strict event ordering is required, consider using the {@link #observeOn(Scheduler, boolean)} overload. + *

+ * + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ *

"Island size" indicates how large chunks the unbounded buffer allocates to store the excess elements waiting to be consumed + * on the other side of the asynchronous boundary. + * + * @param scheduler + * the {@link Scheduler} to notify {@link Observer}s on + * @return the source ObservableSource modified so that its {@link Observer}s are notified on the specified + * {@link Scheduler} + * @see ReactiveX operators documentation: ObserveOn + * @see RxJava Threading Examples + * @see #subscribeOn + * @see #observeOn(Scheduler, boolean) + * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable observeOn(Scheduler scheduler) { + return observeOn(scheduler, false, bufferSize()); + } + + /** + * Modifies an ObservableSource to perform its emissions and notifications on a specified {@link Scheduler}, + * asynchronously with an unbounded buffer with {@link Flowable#bufferSize()} "island size" and optionally delays onError notifications. + *

+ * + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ *

"Island size" indicates how large chunks the unbounded buffer allocates to store the excess elements waiting to be consumed + * on the other side of the asynchronous boundary. + * + * @param scheduler + * the {@link Scheduler} to notify {@link Observer}s on + * @param delayError + * indicates if the onError notification may not cut ahead of onNext notification on the other side of the + * scheduling boundary. If true a sequence ending in onError will be replayed in the same order as was received + * from upstream + * @return the source ObservableSource modified so that its {@link Observer}s are notified on the specified + * {@link Scheduler} + * @see ReactiveX operators documentation: ObserveOn + * @see RxJava Threading Examples + * @see #subscribeOn + * @see #observeOn(Scheduler) + * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler, boolean) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable observeOn(Scheduler scheduler, boolean delayError) { + return observeOn(scheduler, delayError, bufferSize()); + } + + /** + * Modifies an ObservableSource to perform its emissions and notifications on a specified {@link Scheduler}, + * asynchronously with an unbounded buffer of configurable "island size" and optionally delays onError notifications. + *

+ * + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ *

"Island size" indicates how large chunks the unbounded buffer allocates to store the excess elements waiting to be consumed + * on the other side of the asynchronous boundary. Values below 16 are not recommended in performance sensitive scenarios. + * + * @param scheduler + * the {@link Scheduler} to notify {@link Observer}s on + * @param delayError + * indicates if the onError notification may not cut ahead of onNext notification on the other side of the + * scheduling boundary. If true a sequence ending in onError will be replayed in the same order as was received + * from upstream + * @param bufferSize the size of the buffer. + * @return the source ObservableSource modified so that its {@link Observer}s are notified on the specified + * {@link Scheduler} + * @see ReactiveX operators documentation: ObserveOn + * @see RxJava Threading Examples + * @see #subscribeOn + * @see #observeOn(Scheduler) + * @see #observeOn(Scheduler, boolean) + * @see #delay(long, TimeUnit, Scheduler, boolean) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable observeOn(Scheduler scheduler, boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new ObservableObserveOn(this, scheduler, delayError, bufferSize)); + } + + /** + * Filters the items emitted by an ObservableSource, only emitting those of the specified type. + *

+ * + *

+ *
Scheduler:
+ *
{@code ofType} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the output type + * @param clazz + * the class type to filter the items emitted by the source ObservableSource + * @return an Observable that emits items from the source ObservableSource of type {@code clazz} + * @see ReactiveX operators documentation: Filter + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable ofType(final Class clazz) { + ObjectHelper.requireNonNull(clazz, "clazz is null"); + return filter(Functions.isInstanceOf(clazz)).cast(clazz); + } + + /** + * Instructs an ObservableSource to pass control to another ObservableSource rather than invoking + * {@link Observer#onError onError} if it encounters an error. + *

+ * + *

+ * By default, when an ObservableSource encounters an error that prevents it from emitting the expected item to + * its {@link Observer}, the ObservableSource invokes its Observer's {@code onError} method, and then quits + * without invoking any more of its Observer's methods. The {@code onErrorResumeNext} method changes this + * behavior. If you pass a function that returns an ObservableSource ({@code resumeFunction}) to + * {@code onErrorResumeNext}, if the original ObservableSource encounters an error, instead of invoking its + * Observer's {@code onError} method, it will instead relinquish control to the ObservableSource returned from + * {@code resumeFunction}, which will invoke the Observer's {@link Observer#onNext onNext} method if it is + * able to do so. In such a case, because no ObservableSource necessarily invokes {@code onError}, the Observer + * may never know that an error happened. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param resumeFunction + * a function that returns an ObservableSource that will take over if the source ObservableSource encounters + * an error + * @return the original ObservableSource, with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable onErrorResumeNext(Function> resumeFunction) { + ObjectHelper.requireNonNull(resumeFunction, "resumeFunction is null"); + return RxJavaPlugins.onAssembly(new ObservableOnErrorNext(this, resumeFunction, false)); + } + + /** + * Instructs an ObservableSource to pass control to another ObservableSource rather than invoking + * {@link Observer#onError onError} if it encounters an error. + *

+ * + *

+ * By default, when an ObservableSource encounters an error that prevents it from emitting the expected item to + * its {@link Observer}, the ObservableSource invokes its Observer's {@code onError} method, and then quits + * without invoking any more of its Observer's methods. The {@code onErrorResumeNext} method changes this + * behavior. If you pass another ObservableSource ({@code resumeSequence}) to an ObservableSource's + * {@code onErrorResumeNext} method, if the original ObservableSource encounters an error, instead of invoking its + * Observer's {@code onError} method, it will instead relinquish control to {@code resumeSequence} which + * will invoke the Observer's {@link Observer#onNext onNext} method if it is able to do so. In such a case, + * because no ObservableSource necessarily invokes {@code onError}, the Observer may never know that an error + * happened. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param next + * the next ObservableSource source that will take over if the source ObservableSource encounters + * an error + * @return the original ObservableSource, with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable onErrorResumeNext(final ObservableSource next) { + ObjectHelper.requireNonNull(next, "next is null"); + return onErrorResumeNext(Functions.justFunction(next)); + } + + /** + * Instructs an ObservableSource to emit an item (returned by a specified function) rather than invoking + * {@link Observer#onError onError} if it encounters an error. + *

+ * + *

+ * By default, when an ObservableSource encounters an error that prevents it from emitting the expected item to + * its {@link Observer}, the ObservableSource invokes its Observer's {@code onError} method, and then quits + * without invoking any more of its Observer's methods. The {@code onErrorReturn} method changes this + * behavior. If you pass a function ({@code resumeFunction}) to an ObservableSource's {@code onErrorReturn} + * method, if the original ObservableSource encounters an error, instead of invoking its Observer's + * {@code onError} method, it will instead emit the return value of {@code resumeFunction}. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorReturn} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param valueSupplier + * a function that returns a single value that will be emitted along with a regular onComplete in case + * the current Observable signals an onError event + * @return the original ObservableSource with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable onErrorReturn(Function valueSupplier) { + ObjectHelper.requireNonNull(valueSupplier, "valueSupplier is null"); + return RxJavaPlugins.onAssembly(new ObservableOnErrorReturn(this, valueSupplier)); + } + + /** + * Instructs an ObservableSource to emit an item (returned by a specified function) rather than invoking + * {@link Observer#onError onError} if it encounters an error. + *

+ * + *

+ * By default, when an ObservableSource encounters an error that prevents it from emitting the expected item to + * its {@link Observer}, the ObservableSource invokes its Observer's {@code onError} method, and then quits + * without invoking any more of its Observer's methods. The {@code onErrorReturn} method changes this + * behavior. If you pass a function ({@code resumeFunction}) to an ObservableSource's {@code onErrorReturn} + * method, if the original ObservableSource encounters an error, instead of invoking its Observer's + * {@code onError} method, it will instead emit the return value of {@code resumeFunction}. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorReturnItem} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the value that is emitted along with a regular onComplete in case the current + * Observable signals an exception + * @return the original ObservableSource with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable onErrorReturnItem(final T item) { + ObjectHelper.requireNonNull(item, "item is null"); + return onErrorReturn(Functions.justFunction(item)); + } + + /** + * Instructs an ObservableSource to pass control to another ObservableSource rather than invoking + * {@link Observer#onError onError} if it encounters an {@link Exception}. + *

+ * This differs from {@link #onErrorResumeNext} in that this one does not handle {@link Throwable} + * or {@link Error} but lets those continue through. + *

+ * + *

+ * By default, when an ObservableSource encounters an exception that prevents it from emitting the expected item + * to its {@link Observer}, the ObservableSource invokes its Observer's {@code onError} method, and then quits + * without invoking any more of its Observer's methods. The {@code onExceptionResumeNext} method changes + * this behavior. If you pass another ObservableSource ({@code resumeSequence}) to an ObservableSource's + * {@code onExceptionResumeNext} method, if the original ObservableSource encounters an exception, instead of + * invoking its Observer's {@code onError} method, it will instead relinquish control to + * {@code resumeSequence} which will invoke the Observer's {@link Observer#onNext onNext} method if it is + * able to do so. In such a case, because no ObservableSource necessarily invokes {@code onError}, the Observer + * may never know that an exception happened. + *

+ * You can use this to prevent exceptions from propagating or to supply fallback data should exceptions be + * encountered. + *

+ *
Scheduler:
+ *
{@code onExceptionResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param next + * the next ObservableSource that will take over if the source ObservableSource encounters + * an exception + * @return the original ObservableSource, with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable onExceptionResumeNext(final ObservableSource next) { + ObjectHelper.requireNonNull(next, "next is null"); + return RxJavaPlugins.onAssembly(new ObservableOnErrorNext(this, Functions.justFunction(next), true)); + } + + /** + * Nulls out references to the upstream producer and downstream Observer if + * the sequence is terminated or downstream calls dispose(). + *

+ * + *

+ *
Scheduler:
+ *
{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return an Observable which nulls out references to the upstream producer and downstream Observer if + * the sequence is terminated or downstream calls dispose() + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable onTerminateDetach() { + return RxJavaPlugins.onAssembly(new ObservableDetach(this)); + } + + /** + * Returns a {@link ConnectableObservable}, which is a variety of ObservableSource that waits until its + * {@link ConnectableObservable#connect connect} method is called before it begins emitting items to those + * {@link Observer}s that have subscribed to it. + *

+ * + *

+ *
Scheduler:
+ *
{@code publish} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link ConnectableObservable} that upon connection causes the source ObservableSource to emit items + * to its {@link Observer}s + * @see ReactiveX operators documentation: Publish + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final ConnectableObservable publish() { + return ObservablePublish.create(this); + } + + /** + * Returns an Observable that emits the results of invoking a specified selector on items emitted by a + * {@link ConnectableObservable} that shares a single subscription to the underlying sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code publish} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting ObservableSource + * @param selector + * a function that can use the multicasted source sequence as many times as needed, without + * causing multiple subscriptions to the source sequence. Observers to the given source will + * receive all notifications of the source from the time of the subscription forward. + * @return an Observable that emits the results of invoking the selector on the items emitted by a {@link ConnectableObservable} that shares a single subscription to the underlying sequence + * @see ReactiveX operators documentation: Publish + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable publish(Function, ? extends ObservableSource> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new ObservablePublishSelector(this, selector)); + } + + /** + * Returns a Maybe that applies a specified accumulator function to the first item emitted by a source + * ObservableSource, then feeds the result of that function along with the second item emitted by the source + * ObservableSource into the same function, and so on until all items have been emitted by the finite source ObservableSource, + * and emits the final result from the final call to your function as its sole item. + *

+ * + *

+ * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," + * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method + * that does a similar operation on lists. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code reduce} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param reducer + * an accumulator function to be invoked on each item emitted by the source ObservableSource, whose + * result will be used in the next accumulator call + * @return a Maybe that emits a single item that is the result of accumulating the items emitted by + * the source ObservableSource + * @see ReactiveX operators documentation: Reduce + * @see Wikipedia: Fold (higher-order function) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe reduce(BiFunction reducer) { + ObjectHelper.requireNonNull(reducer, "reducer is null"); + return RxJavaPlugins.onAssembly(new ObservableReduceMaybe(this, reducer)); + } + + /** + * Returns a Single that applies a specified accumulator function to the first item emitted by a source + * ObservableSource and a specified seed value, then feeds the result of that function along with the second item + * emitted by an ObservableSource into the same function, and so on until all items have been emitted by the + * finite source ObservableSource, emitting the final result from the final call to your function as its sole item. + *

+ * + *

+ * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," + * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method + * that does a similar operation on lists. + *

+ * Note that the {@code seed} is shared among all subscribers to the resulting ObservableSource + * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer + * the application of this operator via {@link #defer(Callable)}: + *


+     * ObservableSource<T> source = ...
+     * Single.defer(() -> source.reduce(new ArrayList<>(), (list, item) -> list.add(item)));
+     *
+     * // alternatively, by using compose to stay fluent
+     *
+     * source.compose(o ->
+     *     Observable.defer(() -> o.reduce(new ArrayList<>(), (list, item) -> list.add(item)).toObservable())
+     * ).firstOrError();
+     *
+     * // or, by using reduceWith instead of reduce
+     *
+     * source.reduceWith(() -> new ArrayList<>(), (list, item) -> list.add(item)));
+     * 
+ *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code reduce} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the accumulator and output value type + * @param seed + * the initial (seed) accumulator value + * @param reducer + * an accumulator function to be invoked on each item emitted by the source ObservableSource, the + * result of which will be used in the next accumulator call + * @return a Single that emits a single item that is the result of accumulating the output from the + * items emitted by the source ObservableSource + * @see ReactiveX operators documentation: Reduce + * @see Wikipedia: Fold (higher-order function) + * @see #reduceWith(Callable, BiFunction) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single reduce(R seed, BiFunction reducer) { + ObjectHelper.requireNonNull(seed, "seed is null"); + ObjectHelper.requireNonNull(reducer, "reducer is null"); + return RxJavaPlugins.onAssembly(new ObservableReduceSeedSingle(this, seed, reducer)); + } + + /** + * Returns a Single that applies a specified accumulator function to the first item emitted by a source + * ObservableSource and a seed value derived from calling a specified seedSupplier, then feeds the result + * of that function along with the second item emitted by an ObservableSource into the same function, + * and so on until all items have been emitted by the finite source ObservableSource, emitting the final result + * from the final call to your function as its sole item. + *

+ * + *

+ * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," + * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method + * that does a similar operation on lists. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulator object to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code reduceWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the accumulator and output value type + * @param seedSupplier + * the Callable that provides the initial (seed) accumulator value for each individual Observer + * @param reducer + * an accumulator function to be invoked on each item emitted by the source ObservableSource, the + * result of which will be used in the next accumulator call + * @return a Single that emits a single item that is the result of accumulating the output from the + * items emitted by the source ObservableSource + * @see ReactiveX operators documentation: Reduce + * @see Wikipedia: Fold (higher-order function) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single reduceWith(Callable seedSupplier, BiFunction reducer) { + ObjectHelper.requireNonNull(seedSupplier, "seedSupplier is null"); + ObjectHelper.requireNonNull(reducer, "reducer is null"); + return RxJavaPlugins.onAssembly(new ObservableReduceWithSingle(this, seedSupplier, reducer)); + } + + /** + * Returns an Observable that repeats the sequence of items emitted by the source ObservableSource indefinitely. + *

+ * + *

+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an Observable that emits the items emitted by the source ObservableSource repeatedly and in sequence + * @see ReactiveX operators documentation: Repeat + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable repeat() { + return repeat(Long.MAX_VALUE); + } + + /** + * Returns an Observable that repeats the sequence of items emitted by the source ObservableSource at most + * {@code count} times. + *

+ * + *

+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param times + * the number of times the source ObservableSource items are repeated, a count of 0 will yield an empty + * sequence + * @return an Observable that repeats the sequence of items emitted by the source ObservableSource at most + * {@code count} times + * @throws IllegalArgumentException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: Repeat + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable repeat(long times) { + if (times < 0) { + throw new IllegalArgumentException("times >= 0 required but it was " + times); + } + if (times == 0) { + return empty(); + } + return RxJavaPlugins.onAssembly(new ObservableRepeat(this, times)); + } + + /** + * Returns an Observable that repeats the sequence of items emitted by the source ObservableSource until + * the provided stop function returns true. + *

+ * + *

+ *
Scheduler:
+ *
{@code repeatUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param stop + * a boolean supplier that is called when the current Observable completes; + * if it returns true, the returned Observable completes; if it returns false, + * the upstream Observable is resubscribed. + * @return the new Observable instance + * @throws NullPointerException + * if {@code stop} is null + * @see ReactiveX operators documentation: Repeat + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable repeatUntil(BooleanSupplier stop) { + ObjectHelper.requireNonNull(stop, "stop is null"); + return RxJavaPlugins.onAssembly(new ObservableRepeatUntil(this, stop)); + } + + /** + * Returns an Observable that emits the same values as the source ObservableSource with the exception of an + * {@code onComplete}. An {@code onComplete} notification from the source will result in the emission of + * a {@code void} item to the ObservableSource provided as an argument to the {@code notificationHandler} + * function. If that ObservableSource calls {@code onComplete} or {@code onError} then {@code repeatWhen} will + * call {@code onComplete} or {@code onError} on the child subscription. Otherwise, this ObservableSource will + * resubscribe to the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code repeatWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param handler + * receives an ObservableSource of notifications with which a user can complete or error, aborting the repeat. + * @return the source ObservableSource modified with repeat logic + * @see ReactiveX operators documentation: Repeat + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable repeatWhen(final Function, ? extends ObservableSource> handler) { + ObjectHelper.requireNonNull(handler, "handler is null"); + return RxJavaPlugins.onAssembly(new ObservableRepeatWhen(this, handler)); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the underlying ObservableSource + * that will replay all of its items and notifications to any future {@link Observer}. A Connectable + * ObservableSource resembles an ordinary ObservableSource, except that it does not begin emitting items when it is + * subscribed to, but only when its {@code connect} method is called. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link ConnectableObservable} that upon connection causes the source ObservableSource to emit its + * items to its {@link Observer}s + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final ConnectableObservable replay() { + return ObservableReplay.createFrom(this); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on the items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting ObservableSource + * @param selector + * the selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the ObservableSource + * @return an Observable that emits items that are the results of invoking the selector on a + * {@link ConnectableObservable} that shares a single subscription to the source ObservableSource + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable replay(Function, ? extends ObservableSource> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return ObservableReplay.multicastSelector(ObservableInternalHelper.replayCallable(this), selector); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying {@code bufferSize} notifications. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting ObservableSource + * @param selector + * the selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the ObservableSource + * @param bufferSize + * the buffer size that limits the number of items the connectable ObservableSource can replay + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource + * replaying no more than {@code bufferSize} items + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable replay(Function, ? extends ObservableSource> selector, final int bufferSize) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return ObservableReplay.multicastSelector(ObservableInternalHelper.replayCallable(this, bufferSize), selector); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying no more than {@code bufferSize} items that were emitted within a specified time window. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting ObservableSource + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the ObservableSource + * @param bufferSize + * the buffer size that limits the number of items the connectable ObservableSource can replay + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, and + * replays no more than {@code bufferSize} items that were emitted within the window defined by + * {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable replay(Function, ? extends ObservableSource> selector, int bufferSize, long time, TimeUnit unit) { + return replay(selector, bufferSize, time, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying no more than {@code bufferSize} items that were emitted within a specified time window. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param + * the type of items emitted by the resulting ObservableSource + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the ObservableSource + * @param bufferSize + * the buffer size that limits the number of items the connectable ObservableSource can replay + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that is the time source for the window + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, and + * replays no more than {@code bufferSize} items that were emitted within the window defined by + * {@code time} + * @throws IllegalArgumentException + * if {@code bufferSize} is less than zero + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable replay(Function, ? extends ObservableSource> selector, final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return ObservableReplay.multicastSelector( + ObservableInternalHelper.replayCallable(this, bufferSize, time, unit, scheduler), selector); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying a maximum of {@code bufferSize} items. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param + * the type of items emitted by the resulting ObservableSource + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the ObservableSource + * @param bufferSize + * the buffer size that limits the number of items the connectable ObservableSource can replay + * @param scheduler + * the Scheduler on which the replay is observed + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying no more than {@code bufferSize} notifications + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable replay(final Function, ? extends ObservableSource> selector, final int bufferSize, final Scheduler scheduler) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return ObservableReplay.multicastSelector(ObservableInternalHelper.replayCallable(this, bufferSize), + ObservableInternalHelper.replayFunction(selector, scheduler)); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying all items that were emitted within a specified time window. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the resulting ObservableSource + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the ObservableSource + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying all items that were emitted within the window defined by {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable replay(Function, ? extends ObservableSource> selector, long time, TimeUnit unit) { + return replay(selector, time, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying all items that were emitted within a specified time window. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param + * the type of items emitted by the resulting ObservableSource + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the ObservableSource + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler that is the time source for the window + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying all items that were emitted within the window defined by {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable replay(Function, ? extends ObservableSource> selector, final long time, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return ObservableReplay.multicastSelector(ObservableInternalHelper.replayCallable(this, time, unit, scheduler), selector); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param + * the type of items emitted by the resulting ObservableSource + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the ObservableSource + * @param scheduler + * the Scheduler where the replay is observed + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource, + * replaying all items + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable replay(final Function, ? extends ObservableSource> selector, final Scheduler scheduler) { + ObjectHelper.requireNonNull(selector, "selector is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return ObservableReplay.multicastSelector(ObservableInternalHelper.replayCallable(this), + ObservableInternalHelper.replayFunction(selector, scheduler)); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource that + * replays at most {@code bufferSize} items emitted by that ObservableSource. A Connectable ObservableSource resembles + * an ordinary ObservableSource, except that it does not begin emitting items when it is subscribed to, but only + * when its {@code connect} method is called. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code replay} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @return a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays at most {@code bufferSize} items emitted by that ObservableSource + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final ConnectableObservable replay(final int bufferSize) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return ObservableReplay.create(this, bufferSize); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays at most {@code bufferSize} items that were emitted during a specified time window. A Connectable + * ObservableSource resembles an ordinary ObservableSource, except that it does not begin emitting items when it is + * subscribed to, but only when its {@code connect} method is called. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays at most {@code bufferSize} items that were emitted during the window defined by + * {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final ConnectableObservable replay(int bufferSize, long time, TimeUnit unit) { + return replay(bufferSize, time, unit, Schedulers.computation()); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * that replays a maximum of {@code bufferSize} items that are emitted within a specified time window. A + * Connectable ObservableSource resembles an ordinary ObservableSource, except that it does not begin emitting items + * when it is subscribed to, but only when its {@code connect} method is called. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler that is used as a time source for the window + * @return a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays at most {@code bufferSize} items that were emitted during the window defined by + * {@code time} + * @throws IllegalArgumentException + * if {@code bufferSize} is less than zero + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final ConnectableObservable replay(final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return ObservableReplay.create(this, time, unit, scheduler, bufferSize); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays at most {@code bufferSize} items emitted by that ObservableSource. A Connectable ObservableSource resembles + * an ordinary ObservableSource, except that it does not begin emitting items when it is subscribed to, but only + * when its {@code connect} method is called. + *

+ * Note that due to concurrency requirements, {@code replay(bufferSize)} may hold strong references to more than + * {@code bufferSize} source emissions. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @param scheduler + * the scheduler on which the Observers will observe the emitted items + * @return a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays at most {@code bufferSize} items that were emitted by the ObservableSource + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final ConnectableObservable replay(final int bufferSize, final Scheduler scheduler) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return ObservableReplay.observeOn(replay(bufferSize), scheduler); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays all items emitted by that ObservableSource within a specified time window. A Connectable ObservableSource + * resembles an ordinary ObservableSource, except that it does not begin emitting items when it is subscribed to, + * but only when its {@code connect} method is called. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code replay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays the items that were emitted during the window defined by {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final ConnectableObservable replay(long time, TimeUnit unit) { + return replay(time, unit, Schedulers.computation()); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays all items emitted by that ObservableSource within a specified time window. A Connectable ObservableSource + * resembles an ordinary ObservableSource, except that it does not begin emitting items when it is subscribed to, + * but only when its {@code connect} method is called. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that is the time source for the window + * @return a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource and + * replays the items that were emitted during the window defined by {@code time} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final ConnectableObservable replay(final long time, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return ObservableReplay.create(this, time, unit, scheduler); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource that + * will replay all of its items and notifications to any future {@link Observer} on the given + * {@link Scheduler}. A Connectable ObservableSource resembles an ordinary ObservableSource, except that it does not + * begin emitting items when it is subscribed to, but only when its {@code connect} method is called. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the Scheduler on which the Observers will observe the emitted items + * @return a {@link ConnectableObservable} that shares a single subscription to the source ObservableSource that + * will replay all of its items and notifications to any future {@link Observer} on the given + * {@link Scheduler} + * @see ReactiveX operators documentation: Replay + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final ConnectableObservable replay(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return ObservableReplay.observeOn(replay(), scheduler); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, resubscribing to it if it calls {@code onError} + * (infinite retry count). + *

+ * + *

+ * If the source ObservableSource calls {@link Observer#onError}, this method will resubscribe to the source + * ObservableSource rather than propagating the {@code onError} call. + *

+ * Any and all items emitted by the source ObservableSource will be emitted by the resulting ObservableSource, even + * those emitted during failed subscriptions. For example, if an ObservableSource fails at first but emits + * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence + * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onComplete]}. + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the source ObservableSource modified with retry logic + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable retry() { + return retry(Long.MAX_VALUE, Functions.alwaysTrue()); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, resubscribing to it if it calls {@code onError} + * and the predicate returns true for that specific exception and retry count. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * the predicate that determines if a resubscription may happen in case of a specific exception + * and retry count + * @return the source ObservableSource modified with retry logic + * @see #retry() + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable retry(BiPredicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + + return RxJavaPlugins.onAssembly(new ObservableRetryBiPredicate(this, predicate)); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, resubscribing to it if it calls {@code onError} + * up to a specified number of retries. + *

+ * + *

+ * If the source ObservableSource calls {@link Observer#onError}, this method will resubscribe to the source + * ObservableSource for a maximum of {@code count} resubscriptions rather than propagating the + * {@code onError} call. + *

+ * Any and all items emitted by the source ObservableSource will be emitted by the resulting ObservableSource, even + * those emitted during failed subscriptions. For example, if an ObservableSource fails at first but emits + * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence + * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onComplete]}. + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param times + * the number of times to resubscribe if the current Observable fails + * @return the source ObservableSource modified with retry logic + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable retry(long times) { + return retry(times, Functions.alwaysTrue()); + } + + /** + * Retries at most times or until the predicate returns false, whichever happens first. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param times the number of times to resubscribe if the current Observable fails + * @param predicate the predicate called with the failure Throwable and should return true to trigger a retry. + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable retry(long times, Predicate predicate) { + if (times < 0) { + throw new IllegalArgumentException("times >= 0 required but it was " + times); + } + ObjectHelper.requireNonNull(predicate, "predicate is null"); + + return RxJavaPlugins.onAssembly(new ObservableRetryPredicate(this, times, predicate)); + } + + /** + * Retries the current Observable if the predicate returns true. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate the predicate that receives the failure Throwable and should return true to trigger a retry. + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable retry(Predicate predicate) { + return retry(Long.MAX_VALUE, predicate); + } + + /** + * Retries until the given stop function returns true. + *

+ * + *

+ *
Scheduler:
+ *
{@code retryUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param stop the function that should return true to stop retrying + * @return the new Observable instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable retryUntil(final BooleanSupplier stop) { + ObjectHelper.requireNonNull(stop, "stop is null"); + return retry(Long.MAX_VALUE, Functions.predicateReverseFor(stop)); + } + + /** + * Returns an Observable that emits the same values as the source ObservableSource with the exception of an + * {@code onError}. An {@code onError} notification from the source will result in the emission of a + * {@link Throwable} item to the ObservableSource provided as an argument to the {@code notificationHandler} + * function. If that ObservableSource calls {@code onComplete} or {@code onError} then {@code retry} will call + * {@code onComplete} or {@code onError} on the child subscription. Otherwise, this ObservableSource will + * resubscribe to the source ObservableSource. + *

+ * + *

+ * Example: + * + * This retries 3 times, each time incrementing the number of seconds it waits. + * + *


+     *  Observable.create((ObservableEmitter<? super String> s) -> {
+     *      System.out.println("subscribing");
+     *      s.onError(new RuntimeException("always fails"));
+     *  }).retryWhen(attempts -> {
+     *      return attempts.zipWith(Observable.range(1, 3), (n, i) -> i).flatMap(i -> {
+     *          System.out.println("delay retry by " + i + " second(s)");
+     *          return Observable.timer(i, TimeUnit.SECONDS);
+     *      });
+     *  }).blockingForEach(System.out::println);
+     * 
+ * + * Output is: + * + *
 {@code
+     * subscribing
+     * delay retry by 1 second(s)
+     * subscribing
+     * delay retry by 2 second(s)
+     * subscribing
+     * delay retry by 3 second(s)
+     * subscribing
+     * } 
+ *

+ * Note that the inner {@code ObservableSource} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signalling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code ObservableSource} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + *

+ * The following example demonstrates how to retry an asynchronous source with a delay: + *


+     * Observable.timer(1, TimeUnit.SECONDS)
+     *     .doOnSubscribe(s -> System.out.println("subscribing"))
+     *     .map(v -> { throw new RuntimeException(); })
+     *     .retryWhen(errors -> {
+     *         AtomicInteger counter = new AtomicInteger();
+     *         return errors
+     *                   .takeWhile(e -> counter.getAndIncrement() != 3)
+     *                   .flatMap(e -> {
+     *                       System.out.println("delay retry by " + counter.get() + " second(s)");
+     *                       return Observable.timer(counter.get(), TimeUnit.SECONDS);
+     *                   });
+     *     })
+     *     .blockingSubscribe(System.out::println, System.out::println);
+     * 
+ *
+ *
Scheduler:
+ *
{@code retryWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param handler + * receives an ObservableSource of notifications with which a user can complete or error, aborting the + * retry + * @return the source ObservableSource modified with retry logic + * @see ReactiveX operators documentation: Retry + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable retryWhen( + final Function, ? extends ObservableSource> handler) { + ObjectHelper.requireNonNull(handler, "handler is null"); + return RxJavaPlugins.onAssembly(new ObservableRetryWhen(this, handler)); + } + + /** + * Subscribes to the current Observable and wraps the given Observer into a SafeObserver + * (if not already a SafeObserver) that + * deals with exceptions thrown by a misbehaving Observer (that doesn't follow the + * Reactive Streams specification). + *
+ *
Scheduler:
+ *
{@code safeSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param observer the incoming Observer instance + * @throws NullPointerException if s is null + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final void safeSubscribe(Observer observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); + if (observer instanceof SafeObserver) { + subscribe(observer); + } else { + subscribe(new SafeObserver(observer)); + } + } + + /** + * Returns an Observable that emits the most recently emitted item (if any) emitted by the source ObservableSource + * within periodic time intervals. + *

+ * + *

+ *
Scheduler:
+ *
{@code sample} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param period + * the sampling rate + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @return an Observable that emits the results of sampling the items emitted by the source ObservableSource at + * the specified time interval + * @see ReactiveX operators documentation: Sample + * @see #throttleLast(long, TimeUnit) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable sample(long period, TimeUnit unit) { + return sample(period, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits the most recently emitted item (if any) emitted by the source ObservableSource + * within periodic time intervals and optionally emit the very last upstream item when the upstream completes. + *

+ * + *

+ *
Scheduler:
+ *
{@code sample} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + *

History: 2.0.5 - experimental + * @param period + * the sampling rate + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @return an Observable that emits the results of sampling the items emitted by the source ObservableSource at + * the specified time interval + * @param emitLast + * if true and the upstream completes while there is still an unsampled item available, + * that item is emitted to downstream before completion + * if false, an unsampled last item is ignored. + * @see ReactiveX operators documentation: Sample + * @see #throttleLast(long, TimeUnit) + * @since 2.1 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable sample(long period, TimeUnit unit, boolean emitLast) { + return sample(period, unit, Schedulers.computation(), emitLast); + } + + /** + * Returns an Observable that emits the most recently emitted item (if any) emitted by the source ObservableSource + * within periodic time intervals, where the intervals are defined on a particular Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param period + * the sampling rate + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @param scheduler + * the {@link Scheduler} to use when sampling + * @return an Observable that emits the results of sampling the items emitted by the source ObservableSource at + * the specified time interval + * @see ReactiveX operators documentation: Sample + * @see #throttleLast(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable sample(long period, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableSampleTimed(this, period, unit, scheduler, false)); + } + + /** + * Returns an Observable that emits the most recently emitted item (if any) emitted by the source ObservableSource + * within periodic time intervals, where the intervals are defined on a particular Scheduler + * and optionally emit the very last upstream item when the upstream completes. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + *

History: 2.0.5 - experimental + * @param period + * the sampling rate + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @param scheduler + * the {@link Scheduler} to use when sampling + * @param emitLast + * if true and the upstream completes while there is still an unsampled item available, + * that item is emitted to downstream before completion + * if false, an unsampled last item is ignored. + * @return an Observable that emits the results of sampling the items emitted by the source ObservableSource at + * the specified time interval + * @see ReactiveX operators documentation: Sample + * @see #throttleLast(long, TimeUnit, Scheduler) + * @since 2.1 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable sample(long period, TimeUnit unit, Scheduler scheduler, boolean emitLast) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableSampleTimed(this, period, unit, scheduler, emitLast)); + } + + /** + * Returns an Observable that, when the specified {@code sampler} ObservableSource emits an item or completes, + * emits the most recently emitted item (if any) emitted by the source ObservableSource since the previous + * emission from the {@code sampler} ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code sample} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the sampler ObservableSource + * @param sampler + * the ObservableSource to use for sampling the source ObservableSource + * @return an Observable that emits the results of sampling the items emitted by this ObservableSource whenever + * the {@code sampler} ObservableSource emits an item or completes + * @see ReactiveX operators documentation: Sample + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable sample(ObservableSource sampler) { + ObjectHelper.requireNonNull(sampler, "sampler is null"); + return RxJavaPlugins.onAssembly(new ObservableSampleWithObservable(this, sampler, false)); + } + + /** + * Returns an Observable that, when the specified {@code sampler} ObservableSource emits an item or completes, + * emits the most recently emitted item (if any) emitted by the source ObservableSource since the previous + * emission from the {@code sampler} ObservableSource + * and optionally emit the very last upstream item when the upstream or other ObservableSource complete. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code sample} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + *

History: 2.0.5 - experimental + * @param the element type of the sampler ObservableSource + * @param sampler + * the ObservableSource to use for sampling the source ObservableSource + * @param emitLast + * if true and the upstream completes while there is still an unsampled item available, + * that item is emitted to downstream before completion + * if false, an unsampled last item is ignored. + * @return an Observable that emits the results of sampling the items emitted by this ObservableSource whenever + * the {@code sampler} ObservableSource emits an item or completes + * @see ReactiveX operators documentation: Sample + * @since 2.1 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable sample(ObservableSource sampler, boolean emitLast) { + ObjectHelper.requireNonNull(sampler, "sampler is null"); + return RxJavaPlugins.onAssembly(new ObservableSampleWithObservable(this, sampler, emitLast)); + } + + /** + * Returns an Observable that applies a specified accumulator function to the first item emitted by a source + * ObservableSource, then feeds the result of that function along with the second item emitted by the source + * ObservableSource into the same function, and so on until all items have been emitted by the source ObservableSource, + * emitting the result of each of these iterations. + *

+ * + *

+ * This sort of function is sometimes called an accumulator. + *

+ *
Scheduler:
+ *
{@code scan} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param accumulator + * an accumulator function to be invoked on each item emitted by the source ObservableSource, whose + * result will be emitted to {@link Observer}s via {@link Observer#onNext onNext} and used in the + * next accumulator call + * @return an Observable that emits the results of each call to the accumulator function + * @see ReactiveX operators documentation: Scan + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable scan(BiFunction accumulator) { + ObjectHelper.requireNonNull(accumulator, "accumulator is null"); + return RxJavaPlugins.onAssembly(new ObservableScan(this, accumulator)); + } + + /** + * Returns an Observable that applies a specified accumulator function to the first item emitted by a source + * ObservableSource and a seed value, then feeds the result of that function along with the second item emitted by + * the source ObservableSource into the same function, and so on until all items have been emitted by the source + * ObservableSource, emitting the result of each of these iterations. + *

+ * + *

+ * This sort of function is sometimes called an accumulator. + *

+ * Note that the ObservableSource that results from this method will emit {@code initialValue} as its first + * emitted item. + *

+ * Note that the {@code initialValue} is shared among all subscribers to the resulting ObservableSource + * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer + * the application of this operator via {@link #defer(Callable)}: + *


+     * ObservableSource<T> source = ...
+     * Observable.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item)));
+     *
+     * // alternatively, by using compose to stay fluent
+     *
+     * source.compose(o ->
+     *     Observable.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item)))
+     * );
+     * 
+ *
+ *
Scheduler:
+ *
{@code scan} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the initial, accumulator and result type + * @param initialValue + * the initial (seed) accumulator item + * @param accumulator + * an accumulator function to be invoked on each item emitted by the source ObservableSource, whose + * result will be emitted to {@link Observer}s via {@link Observer#onNext onNext} and used in the + * next accumulator call + * @return an Observable that emits {@code initialValue} followed by the results of each call to the + * accumulator function + * @see ReactiveX operators documentation: Scan + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable scan(final R initialValue, BiFunction accumulator) { + ObjectHelper.requireNonNull(initialValue, "initialValue is null"); + return scanWith(Functions.justCallable(initialValue), accumulator); + } + + /** + * Returns an Observable that applies a specified accumulator function to the first item emitted by a source + * ObservableSource and a seed value, then feeds the result of that function along with the second item emitted by + * the source ObservableSource into the same function, and so on until all items have been emitted by the source + * ObservableSource, emitting the result of each of these iterations. + *

+ * + *

+ * This sort of function is sometimes called an accumulator. + *

+ * Note that the ObservableSource that results from this method will emit the value returned + * by the {@code seedSupplier} as its first item. + *

+ *
Scheduler:
+ *
{@code scanWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the initial, accumulator and result type + * @param seedSupplier + * a Callable that returns the initial (seed) accumulator item for each individual Observer + * @param accumulator + * an accumulator function to be invoked on each item emitted by the source ObservableSource, whose + * result will be emitted to {@link Observer}s via {@link Observer#onNext onNext} and used in the + * next accumulator call + * @return an Observable that emits {@code initialValue} followed by the results of each call to the + * accumulator function + * @see ReactiveX operators documentation: Scan + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable scanWith(Callable seedSupplier, BiFunction accumulator) { + ObjectHelper.requireNonNull(seedSupplier, "seedSupplier is null"); + ObjectHelper.requireNonNull(accumulator, "accumulator is null"); + return RxJavaPlugins.onAssembly(new ObservableScanSeed(this, seedSupplier, accumulator)); + } + + /** + * Forces an ObservableSource's emissions and notifications to be serialized and for it to obey + * the ObservableSource contract in other ways. + *

+ * It is possible for an ObservableSource to invoke its Observers' methods asynchronously, perhaps from + * different threads. This could make such an ObservableSource poorly-behaved, in that it might try to invoke + * {@code onComplete} or {@code onError} before one of its {@code onNext} invocations, or it might call + * {@code onNext} from two different threads concurrently. You can force such an ObservableSource to be + * well-behaved and sequential by applying the {@code serialize} method to it. + *

+ * + *

+ *
Scheduler:
+ *
{@code serialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an {@link ObservableSource} that is guaranteed to be well-behaved and to make only serialized calls to + * its observers + * @see ReactiveX operators documentation: Serialize + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable serialize() { + return RxJavaPlugins.onAssembly(new ObservableSerialized(this)); + } + + /** + * Returns a new {@link ObservableSource} that multicasts (and shares a single subscription to) the original {@link ObservableSource}. As long as + * there is at least one {@link Observer} this {@link ObservableSource} will be subscribed and emitting data. + * When all subscribers have disposed it will dispose the source {@link ObservableSource}. + *

+ * This is an alias for {@link #publish()}.{@link ConnectableObservable#refCount() refCount()}. + *

+ * + *

+ *
Scheduler:
+ *
{@code share} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an {@code ObservableSource} that upon connection causes the source {@code ObservableSource} to emit items + * to its {@link Observer}s + * @see ReactiveX operators documentation: RefCount + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable share() { + return publish().refCount(); + } + + /** + * Returns a Maybe that completes if this Observable is empty or emits the single item emitted by this Observable, + * or signals an {@code IllegalArgumentException} if this Observable emits more than one item. + *

+ * + *

+ *
Scheduler:
+ *
{@code singleElement} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Maybe} that emits the single item emitted by the source ObservableSource + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe singleElement() { + return RxJavaPlugins.onAssembly(new ObservableSingleMaybe(this)); + } + + /** + * Returns a Single that emits the single item emitted by this Observable, if this Observable + * emits only a single item, or a default item if the source ObservableSource emits no items. If the source + * ObservableSource emits more than one item, an {@code IllegalArgumentException} is signalled instead. + *

+ * + *

+ *
Scheduler:
+ *
{@code single} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param defaultItem + * a default value to emit if the source ObservableSource emits no item + * @return the new Single instance + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single single(T defaultItem) { + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); + return RxJavaPlugins.onAssembly(new ObservableSingleSingle(this, defaultItem)); + } + + /** + * Returns a Single that emits the single item emitted by this Observable if this Observable + * emits only a single item, otherwise + * if this Observable completes without emitting any items or emits more than one item a + * {@link NoSuchElementException} or {@code IllegalArgumentException} will be signalled respectively. + *

+ * + *

+ *
Scheduler:
+ *
{@code singleOrError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Single instance + * @see ReactiveX operators documentation: First + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single singleOrError() { + return RxJavaPlugins.onAssembly(new ObservableSingleSingle(this, null)); + } + + /** + * Returns an Observable that skips the first {@code count} items emitted by the source ObservableSource and emits + * the remainder. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code skip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the number of items to skip + * @return an Observable that is identical to the source ObservableSource except that it does not emit the first + * {@code count} items that the source ObservableSource emits + * @see ReactiveX operators documentation: Skip + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable skip(long count) { + if (count <= 0) { + return RxJavaPlugins.onAssembly(this); + } + return RxJavaPlugins.onAssembly(new ObservableSkip(this, count)); + } + + /** + * Returns an Observable that skips values emitted by the source ObservableSource before a specified time window + * elapses. + *

+ * + *

+ *
Scheduler:
+ *
{@code skip} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window to skip + * @param unit + * the time unit of {@code time} + * @return an Observable that skips values emitted by the source ObservableSource before the time window defined + * by {@code time} elapses and the emits the remainder + * @see ReactiveX operators documentation: Skip + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable skip(long time, TimeUnit unit) { + return skipUntil(timer(time, unit)); + } + + /** + * Returns an Observable that skips values emitted by the source ObservableSource before a specified time window + * on a specified {@link Scheduler} elapses. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use for the timed skipping
+ *
+ * + * @param time + * the length of the time window to skip + * @param unit + * the time unit of {@code time} + * @param scheduler + * the {@link Scheduler} on which the timed wait happens + * @return an Observable that skips values emitted by the source ObservableSource before the time window defined + * by {@code time} and {@code scheduler} elapses, and then emits the remainder + * @see ReactiveX operators documentation: Skip + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable skip(long time, TimeUnit unit, Scheduler scheduler) { + return skipUntil(timer(time, unit, scheduler)); + } + + /** + * Returns an Observable that drops a specified number of items from the end of the sequence emitted by the + * source ObservableSource. + *

+ * + *

+ * This Observer accumulates a queue long enough to store the first {@code count} items. As more items are + * received, items are taken from the front of the queue and emitted by the returned ObservableSource. This causes + * such items to be delayed. + *

+ *
Scheduler:
+ *
This version of {@code skipLast} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * number of items to drop from the end of the source sequence + * @return an Observable that emits the items emitted by the source ObservableSource except for the dropped ones + * at the end + * @throws IndexOutOfBoundsException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable skipLast(int count) { + if (count < 0) { + throw new IndexOutOfBoundsException("count >= 0 required but it was " + count); + } + if (count == 0) { + return RxJavaPlugins.onAssembly(this); + } + return RxJavaPlugins.onAssembly(new ObservableSkipLast(this, count)); + } + + /** + * Returns an Observable that drops items emitted by the source ObservableSource during a specified time window + * before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Scheduler:
+ *
{@code skipLast} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that drops those items emitted by the source ObservableSource in a time window before the + * source completes defined by {@code time} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.TRAMPOLINE) + public final Observable skipLast(long time, TimeUnit unit) { + return skipLast(time, unit, Schedulers.trampoline(), false, bufferSize()); + } + + /** + * Returns an Observable that drops items emitted by the source ObservableSource during a specified time window + * before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Scheduler:
+ *
{@code skipLast} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param delayError + * if true, an exception signalled by the current Observable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * @return an Observable that drops those items emitted by the source ObservableSource in a time window before the + * source completes defined by {@code time} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.TRAMPOLINE) + public final Observable skipLast(long time, TimeUnit unit, boolean delayError) { + return skipLast(time, unit, Schedulers.trampoline(), delayError, bufferSize()); + } + + /** + * Returns an Observable that drops items emitted by the source ObservableSource during a specified time window + * (defined on a specified scheduler) before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use for tracking the current time
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler used as the time source + * @return an Observable that drops those items emitted by the source ObservableSource in a time window before the + * source completes defined by {@code time} and {@code scheduler} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable skipLast(long time, TimeUnit unit, Scheduler scheduler) { + return skipLast(time, unit, scheduler, false, bufferSize()); + } + + /** + * Returns an Observable that drops items emitted by the source ObservableSource during a specified time window + * (defined on a specified scheduler) before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use to track the current time
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler used as the time source + * @param delayError + * if true, an exception signalled by the current Observable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * @return an Observable that drops those items emitted by the source ObservableSource in a time window before the + * source completes defined by {@code time} and {@code scheduler} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable skipLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError) { + return skipLast(time, unit, scheduler, delayError, bufferSize()); + } + + /** + * Returns an Observable that drops items emitted by the source ObservableSource during a specified time window + * (defined on a specified scheduler) before the source completes. + *

+ * + *

+ * Note: this action will cache the latest items arriving in the specified time window. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler used as the time source + * @param delayError + * if true, an exception signalled by the current Observable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * @param bufferSize + * the hint about how many elements to expect to be skipped + * @return an Observable that drops those items emitted by the source ObservableSource in a time window before the + * source completes defined by {@code time} and {@code scheduler} + * @see ReactiveX operators documentation: SkipLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable skipLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + // the internal buffer holds pairs of (timestamp, value) so double the default buffer size + int s = bufferSize << 1; + return RxJavaPlugins.onAssembly(new ObservableSkipLastTimed(this, time, unit, scheduler, s, delayError)); + } + + /** + * Returns an Observable that skips items emitted by the source ObservableSource until a second ObservableSource emits + * an item. + *

+ * + *

+ *
Scheduler:
+ *
{@code skipUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the other ObservableSource + * @param other + * the second ObservableSource that has to emit an item before the source ObservableSource's elements begin + * to be mirrored by the resulting ObservableSource + * @return an Observable that skips items from the source ObservableSource until the second ObservableSource emits an + * item, then emits the remaining items + * @see ReactiveX operators documentation: SkipUntil + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable skipUntil(ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableSkipUntil(this, other)); + } + + /** + * Returns an Observable that skips all items emitted by the source ObservableSource as long as a specified + * condition holds true, but emits all further source items as soon as the condition becomes false. + *

+ * + *

+ *
Scheduler:
+ *
{@code skipWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function to test each item emitted from the source ObservableSource + * @return an Observable that begins emitting items emitted by the source ObservableSource when the specified + * predicate becomes false + * @see ReactiveX operators documentation: SkipWhile + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable skipWhile(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new ObservableSkipWhile(this, predicate)); + } + + /** + * Returns an Observable that emits the events emitted by source ObservableSource, in a + * sorted order. Each item emitted by the ObservableSource must implement {@link Comparable} with respect to all + * other items in the sequence. + *

+ * + *

+ * If any item emitted by this Observable does not implement {@link Comparable} with respect to + * all other items emitted by this Observable, no items will be emitted and the + * sequence is terminated with a {@link ClassCastException}. + * + *

Note that calling {@code sorted} with long, non-terminating or infinite sources + * might cause {@link OutOfMemoryError} + * + *

+ *
Scheduler:
+ *
{@code sorted} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return an Observable that emits the items emitted by the source ObservableSource in sorted order + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable sorted() { + return toList().toObservable().map(Functions.listSorter(Functions.naturalComparator())).flatMapIterable(Functions.>identity()); + } + + /** + * Returns an Observable that emits the events emitted by source ObservableSource, in a + * sorted order based on a specified comparison function. + * + *

Note that calling {@code sorted} with long, non-terminating or infinite sources + * might cause {@link OutOfMemoryError} + * + *

+ *
Scheduler:
+ *
{@code sorted} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param sortFunction + * a function that compares two items emitted by the source ObservableSource and returns an Integer + * that indicates their sort order + * @return an Observable that emits the items emitted by the source ObservableSource in sorted order + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable sorted(Comparator sortFunction) { + ObjectHelper.requireNonNull(sortFunction, "sortFunction is null"); + return toList().toObservable().map(Functions.listSorter(sortFunction)).flatMapIterable(Functions.>identity()); + } + + /** + * Returns an Observable that emits the items in a specified {@link Iterable} before it begins to emit items + * emitted by the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code startWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param items + * an Iterable that contains the items you want the modified ObservableSource to emit first + * @return an Observable that emits the items in the specified {@link Iterable} and then emits the items + * emitted by the source ObservableSource + * @see ReactiveX operators documentation: StartWith + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable startWith(Iterable items) { + return concatArray(fromIterable(items), this); + } + + /** + * Returns an Observable that emits the items in a specified {@link ObservableSource} before it begins to emit + * items emitted by the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code startWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * an ObservableSource that contains the items you want the modified ObservableSource to emit first + * @return an Observable that emits the items in the specified {@link ObservableSource} and then emits the items + * emitted by the source ObservableSource + * @see ReactiveX operators documentation: StartWith + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable startWith(ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return concatArray(other, this); + } + + /** + * Returns an Observable that emits a specified item before it begins to emit items emitted by the source + * ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code startWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the item to emit first + * @return an Observable that emits the specified item before it begins to emit items emitted by the source + * ObservableSource + * @see ReactiveX operators documentation: StartWith + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable startWith(T item) { + ObjectHelper.requireNonNull(item, "item is null"); + return concatArray(just(item), this); + } + + /** + * Returns an Observable that emits the specified items before it begins to emit items emitted by the source + * ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code startWithArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param items + * the array of values to emit first + * @return an Observable that emits the specified items before it begins to emit items emitted by the source + * ObservableSource + * @see ReactiveX operators documentation: StartWith + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable startWithArray(T... items) { + Observable fromArray = fromArray(items); + if (fromArray == empty()) { + return RxJavaPlugins.onAssembly(this); + } + return concatArray(fromArray, this); + } + + /** + * Subscribes to an ObservableSource and ignores {@code onNext} and {@code onComplete} emissions. + *

+ * If the Observable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the ObservableSource has finished sending them + * @see ReactiveX operators documentation: Subscribe + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe() { + return subscribe(Functions.emptyConsumer(), Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION, Functions.emptyConsumer()); + } + + /** + * Subscribes to an ObservableSource and provides a callback to handle the items it emits. + *

+ * If the Observable emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the {@code Consumer} you have designed to accept emissions from the ObservableSource + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the ObservableSource has finished sending them + * @throws NullPointerException + * if {@code onNext} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onNext) { + return subscribe(onNext, Functions.ON_ERROR_MISSING, Functions.EMPTY_ACTION, Functions.emptyConsumer()); + } + + /** + * Subscribes to an ObservableSource and provides callbacks to handle the items it emits and any error + * notification it issues. + *
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the {@code Consumer} you have designed to accept emissions from the ObservableSource + * @param onError + * the {@code Consumer} you have designed to accept any error notification from the + * ObservableSource + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the ObservableSource has finished sending them + * @see ReactiveX operators documentation: Subscribe + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onNext, Consumer onError) { + return subscribe(onNext, onError, Functions.EMPTY_ACTION, Functions.emptyConsumer()); + } + + /** + * Subscribes to an ObservableSource and provides callbacks to handle the items it emits and any error or + * completion notification it issues. + *
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the {@code Consumer} you have designed to accept emissions from the ObservableSource + * @param onError + * the {@code Consumer} you have designed to accept any error notification from the + * ObservableSource + * @param onComplete + * the {@code Action} you have designed to accept a completion notification from the + * ObservableSource + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the ObservableSource has finished sending them + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null, or + * if {@code onComplete} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onNext, Consumer onError, + Action onComplete) { + return subscribe(onNext, onError, onComplete, Functions.emptyConsumer()); + } + + /** + * Subscribes to an ObservableSource and provides callbacks to handle the items it emits and any error or + * completion notification it issues. + *
+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onNext + * the {@code Consumer} you have designed to accept emissions from the ObservableSource + * @param onError + * the {@code Consumer} you have designed to accept any error notification from the + * ObservableSource + * @param onComplete + * the {@code Action} you have designed to accept a completion notification from the + * ObservableSource + * @param onSubscribe + * the {@code Consumer} that receives the upstream's Disposable + * @return a {@link Disposable} reference with which the caller can stop receiving items before + * the ObservableSource has finished sending them + * @throws NullPointerException + * if {@code onNext} is null, or + * if {@code onError} is null, or + * if {@code onComplete} is null, or + * if {@code onSubscribe} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onNext, Consumer onError, + Action onComplete, Consumer onSubscribe) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + + LambdaObserver ls = new LambdaObserver(onNext, onError, onComplete, onSubscribe); + + subscribe(ls); + + return ls; + } + + @SchedulerSupport(SchedulerSupport.NONE) + @Override + public final void subscribe(Observer observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); + try { + observer = RxJavaPlugins.onSubscribe(this, observer); + + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null Observer. Please change the handler provided to RxJavaPlugins.setOnObservableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); + + subscribeActual(observer); + } catch (NullPointerException e) { // NOPMD + throw e; + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because no way to know if a Disposable has been set or not + // can't call onSubscribe because the call might have set a Subscription already + RxJavaPlugins.onError(e); + + NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS"); + npe.initCause(e); + throw npe; + } + } + + /** + * Operator implementations (both source and intermediate) should implement this method that + * performs the necessary business logic and handles the incoming {@link Observer}s. + *

There is no need to call any of the plugin hooks on the current {@code Observable} instance or + * the {@code Observer}; all hooks and basic safeguards have been + * applied by {@link #subscribe(Observer)} before this method gets called. + * @param observer the incoming Observer, never null + */ + protected abstract void subscribeActual(Observer observer); + + /** + * Subscribes a given Observer (subclass) to this Observable and returns the given + * Observer as is. + *

Usage example: + *


+     * Observable<Integer> source = Observable.range(1, 10);
+     * CompositeDisposable composite = new CompositeDisposable();
+     *
+     * DisposableObserver<Integer> ds = new DisposableObserver<>() {
+     *     // ...
+     * };
+     *
+     * composite.add(source.subscribeWith(ds));
+     * 
+ *
+ *
Scheduler:
+ *
{@code subscribeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the type of the Observer to use and return + * @param observer the Observer (subclass) to use and return, not null + * @return the input {@code observer} + * @throws NullPointerException if {@code observer} is null + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final > E subscribeWith(E observer) { + subscribe(observer); + return observer; + } + + /** + * Asynchronously subscribes Observers to this ObservableSource on the specified {@link Scheduler}. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to perform subscription actions on + * @return the source ObservableSource modified so that its subscriptions happen on the + * specified {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + * @see RxJava Threading Examples + * @see #observeOn + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable subscribeOn(Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableSubscribeOn(this, scheduler)); + } + + /** + * Returns an Observable that emits the items emitted by the source ObservableSource or the items of an alternate + * ObservableSource if the source ObservableSource is empty. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the alternate ObservableSource to subscribe to if the source does not emit any items + * @return an ObservableSource that emits the items emitted by the source ObservableSource or the items of an + * alternate ObservableSource if the source ObservableSource is empty. + * @since 1.1.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable switchIfEmpty(ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchIfEmpty(this, other)); + } + + /** + * Returns a new ObservableSource by applying a function that you supply to each item emitted by the source + * ObservableSource that returns an ObservableSource, and then emitting the items emitted by the most recently emitted + * of these ObservableSources. + *

+ * The resulting ObservableSource completes if both the upstream ObservableSource and the last inner ObservableSource, if any, complete. + * If the upstream ObservableSource signals an onError, the inner ObservableSource is disposed and the error delivered in-sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the inner ObservableSources and the output + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @return an Observable that emits the items emitted by the ObservableSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable switchMap(Function> mapper) { + return switchMap(mapper, bufferSize()); + } + + /** + * Returns a new ObservableSource by applying a function that you supply to each item emitted by the source + * ObservableSource that returns an ObservableSource, and then emitting the items emitted by the most recently emitted + * of these ObservableSources. + *

+ * The resulting ObservableSource completes if both the upstream ObservableSource and the last inner ObservableSource, if any, complete. + * If the upstream ObservableSource signals an onError, the inner ObservableSource is disposed and the error delivered in-sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the inner ObservableSources and the output + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @param bufferSize + * the number of elements to prefetch from the current active inner ObservableSource + * @return an Observable that emits the items emitted by the ObservableSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function, int) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable switchMap(Function> mapper, int bufferSize) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + if (this instanceof ScalarCallable) { + @SuppressWarnings("unchecked") + T v = ((ScalarCallable)this).call(); + if (v == null) { + return empty(); + } + return ObservableScalarXMap.scalarXMap(v, mapper); + } + return RxJavaPlugins.onAssembly(new ObservableSwitchMap(this, mapper, bufferSize, false)); + } + + /** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running. + *

+ * + *

+ * Since a {@code CompletableSource} doesn't produce any items, the resulting reactive type of + * this operator is a {@link Completable} that can only indicate successful completion or + * a failure in any of the inner {@code CompletableSource}s or the failure of the current + * {@link Observable}. + *

+ *
Scheduler:
+ *
{@code switchMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If either this {@code Observable} or the active {@code CompletableSource} signals an {@code onError}, + * the resulting {@code Completable} is terminated immediately with that {@code Throwable}. + * Use the {@link #switchMapCompletableDelayError(Function)} to delay such inner failures until + * every inner {@code CompletableSource}s and the main {@code Observable} terminates in some fashion. + * If they fail concurrently, the operator may combine the {@code Throwable}s into a + * {@link io.reactivex.exceptions.CompositeException CompositeException} + * and signal it to the downstream instead. If any inactivated (switched out) {@code CompletableSource} + * signals an {@code onError} late, the {@code Throwable}s will be signalled to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. + *
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with each upstream item and should return a + * {@link CompletableSource} to be subscribed to and awaited for + * (non blockingly) for its terminal event + * @return the new Completable instance + * @see #switchMapCompletableDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable switchMapCompletable(@NonNull Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapCompletable(this, mapper, false)); + } + + /** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running and delaying any main or inner errors until all + * of them terminate. + *

+ * + *

+ * Since a {@code CompletableSource} doesn't produce any items, the resulting reactive type of + * this operator is a {@link Completable} that can only indicate successful completion or + * a failure in any of the inner {@code CompletableSource}s or the failure of the current + * {@link Observable}. + *

+ *
Scheduler:
+ *
{@code switchMapCompletableDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
Errors of this {@code Observable} and all the {@code CompletableSource}s, who had the chance + * to run to their completion, are delayed until + * all of them terminate in some fashion. At this point, if there was only one failure, the respective + * {@code Throwable} is emitted to the downstream. It there were more than one failures, the + * operator combines all {@code Throwable}s into a {@link io.reactivex.exceptions.CompositeException CompositeException} + * and signals that to the downstream. + * If any inactivated (switched out) {@code CompletableSource} + * signals an {@code onError} late, the {@code Throwable}s will be signalled to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. + *
+ *
+ *

History: 2.1.11 - experimental + * @param mapper the function called with each upstream item and should return a + * {@link CompletableSource} to be subscribed to and awaited for + * (non blockingly) for its terminal event + * @return the new Completable instance + * @see #switchMapCompletable(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable switchMapCompletableDelayError(@NonNull Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapCompletable(this, mapper, true)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one if + * available while failing immediately if this {@code Observable} or any of the + * active inner {@code MaybeSource}s fail. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
This operator terminates with an {@code onError} if this {@code Observable} or any of + * the inner {@code MaybeSource}s fail while they are active. When this happens concurrently, their + * individual {@code Throwable} errors may get combined and emitted as a single + * {@link io.reactivex.exceptions.CompositeException CompositeException}. Otherwise, a late + * (i.e., inactive or switched out) {@code onError} from this {@code Observable} or from any of + * the inner {@code MaybeSource}s will be forwarded to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as + * {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}
+ *
+ *

History: 2.1.11 - experimental + * @param the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code MaybeSource} to replace the current active inner source + * and get subscribed to. + * @return the new Observable instance + * @see #switchMapMaybeDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable switchMapMaybe(@NonNull Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapMaybe(this, mapper, false)); + } + + /** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one if + * available, delaying errors from this {@code Observable} or the inner {@code MaybeSource}s until all terminate. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.11 - experimental + * @param the output value type + * @param mapper the function called with the current upstream event and should + * return a {@code MaybeSource} to replace the current active inner source + * and get subscribed to. + * @return the new Observable instance + * @see #switchMapMaybe(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable switchMapMaybeDelayError(@NonNull Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapMaybe(this, mapper, true)); + } + + /** + * Returns a new ObservableSource by applying a function that you supply to each item emitted by the source + * ObservableSource that returns a SingleSource, and then emitting the item emitted by the most recently emitted + * of these SingleSources. + *

+ * The resulting ObservableSource completes if both the upstream ObservableSource and the last inner SingleSource, if any, complete. + * If the upstream ObservableSource signals an onError, the inner SingleSource is disposed and the error delivered in-sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.8 - experimental + * @param the element type of the inner SingleSources and the output + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns a + * SingleSource + * @return an Observable that emits the item emitted by the SingleSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @see #switchMapSingleDelayError(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final Observable switchMapSingle(@NonNull Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapSingle(this, mapper, false)); + } + + /** + * Returns a new ObservableSource by applying a function that you supply to each item emitted by the source + * ObservableSource that returns a SingleSource, and then emitting the item emitted by the most recently emitted + * of these SingleSources and delays any error until all SingleSources terminate. + *

+ * The resulting ObservableSource completes if both the upstream ObservableSource and the last inner SingleSource, if any, complete. + * If the upstream ObservableSource signals an onError, the termination of the last inner SingleSource will emit that error as is + * or wrapped into a CompositeException along with the other possible errors the former inner SingleSources signalled. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.8 - experimental + * @param the element type of the inner SingleSources and the output + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns a + * SingleSource + * @return an Observable that emits the item emitted by the SingleSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @see #switchMapSingle(Function) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final Observable switchMapSingleDelayError(@NonNull Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableSwitchMapSingle(this, mapper, true)); + } + + /** + * Returns a new ObservableSource by applying a function that you supply to each item emitted by the source + * ObservableSource that returns an ObservableSource, and then emitting the items emitted by the most recently emitted + * of these ObservableSources and delays any error until all ObservableSources terminate. + *

+ * The resulting ObservableSource completes if both the upstream ObservableSource and the last inner ObservableSource, if any, complete. + * If the upstream ObservableSource signals an onError, the termination of the last inner ObservableSource will emit that error as is + * or wrapped into a CompositeException along with the other possible errors the former inner ObservableSources signalled. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchMapDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the inner ObservableSources and the output + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @return an Observable that emits the items emitted by the ObservableSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @see #switchMap(Function) + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable switchMapDelayError(Function> mapper) { + return switchMapDelayError(mapper, bufferSize()); + } + + /** + * Returns a new ObservableSource by applying a function that you supply to each item emitted by the source + * ObservableSource that returns an ObservableSource, and then emitting the items emitted by the most recently emitted + * of these ObservableSources and delays any error until all ObservableSources terminate. + *

+ * The resulting ObservableSource completes if both the upstream ObservableSource and the last inner ObservableSource, if any, complete. + * If the upstream ObservableSource signals an onError, the termination of the last inner ObservableSource will emit that error as is + * or wrapped into a CompositeException along with the other possible errors the former inner ObservableSources signalled. + *

+ * + *

+ *
Scheduler:
+ *
{@code switchMapDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the inner ObservableSources and the output + * @param mapper + * a function that, when applied to an item emitted by the source ObservableSource, returns an + * ObservableSource + * @param bufferSize + * the number of elements to prefetch from the current active inner ObservableSource + * @return an Observable that emits the items emitted by the ObservableSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource + * @see ReactiveX operators documentation: FlatMap + * @see #switchMap(Function, int) + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable switchMapDelayError(Function> mapper, int bufferSize) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + if (this instanceof ScalarCallable) { + @SuppressWarnings("unchecked") + T v = ((ScalarCallable)this).call(); + if (v == null) { + return empty(); + } + return ObservableScalarXMap.scalarXMap(v, mapper); + } + return RxJavaPlugins.onAssembly(new ObservableSwitchMap(this, mapper, bufferSize, true)); + } + + /** + * Returns an Observable that emits only the first {@code count} items emitted by the source ObservableSource. If the source emits fewer than + * {@code count} items then all of its items are emitted. + *

+ * + *

+ * This method returns an ObservableSource that will invoke a subscribing {@link Observer}'s + * {@link Observer#onNext onNext} function a maximum of {@code count} times before invoking + * {@link Observer#onComplete onComplete}. + *

+ *
Scheduler:
+ *
This version of {@code take} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum number of items to emit + * @return an Observable that emits only the first {@code count} items emitted by the source ObservableSource, or + * all of the items from the source ObservableSource if that ObservableSource emits fewer than {@code count} items + * @see ReactiveX operators documentation: Take + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable take(long count) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required but it was " + count); + } + return RxJavaPlugins.onAssembly(new ObservableTake(this, count)); + } + + /** + * Returns an Observable that emits those items emitted by source ObservableSource before a specified time runs + * out. + *

+ * If time runs out before the {@code Observable} completes normally, the {@code onComplete} event will be + * signaled on the default {@code computation} {@link Scheduler}. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code take} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that emits those items emitted by the source ObservableSource before the time runs out + * @see ReactiveX operators documentation: Take + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable take(long time, TimeUnit unit) { + return takeUntil(timer(time, unit)); + } + + /** + * Returns an Observable that emits those items emitted by source ObservableSource before a specified time (on a + * specified Scheduler) runs out. + *

+ * If time runs out before the {@code Observable} completes normally, the {@code onComplete} event will be + * signaled on the provided {@link Scheduler}. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler used for time source + * @return an Observable that emits those items emitted by the source ObservableSource before the time runs out, + * according to the specified Scheduler + * @see ReactiveX operators documentation: Take + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable take(long time, TimeUnit unit, Scheduler scheduler) { + return takeUntil(timer(time, unit, scheduler)); + } + + /** + * Returns an Observable that emits at most the last {@code count} items emitted by the source ObservableSource. If the source emits fewer than + * {@code count} items then all of its items are emitted. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code takeLast} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum number of items to emit from the end of the sequence of items emitted by the source + * ObservableSource + * @return an Observable that emits at most the last {@code count} items emitted by the source ObservableSource + * @throws IndexOutOfBoundsException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable takeLast(int count) { + if (count < 0) { + throw new IndexOutOfBoundsException("count >= 0 required but it was " + count); + } + if (count == 0) { + return RxJavaPlugins.onAssembly(new ObservableIgnoreElements(this)); + } + if (count == 1) { + return RxJavaPlugins.onAssembly(new ObservableTakeLastOne(this)); + } + return RxJavaPlugins.onAssembly(new ObservableTakeLast(this, count)); + } + + /** + * Returns an Observable that emits at most a specified number of items from the source ObservableSource that were + * emitted in a specified window of time before the ObservableSource completed. + *

+ * + *

+ *
Scheduler:
+ *
{@code takeLast} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that emits at most {@code count} items from the source ObservableSource that were emitted + * in a specified window of time before the ObservableSource completed + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.TRAMPOLINE) + public final Observable takeLast(long count, long time, TimeUnit unit) { + return takeLast(count, time, unit, Schedulers.trampoline(), false, bufferSize()); + } + + /** + * Returns an Observable that emits at most a specified number of items from the source ObservableSource that were + * emitted in a specified window of time before the ObservableSource completed, where the timing information is + * provided by a given Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use for tracking the current time
+ *
+ * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the {@link Scheduler} that provides the timestamps for the observed items + * @return an Observable that emits at most {@code count} items from the source ObservableSource that were emitted + * in a specified window of time before the ObservableSource completed, where the timing information is + * provided by the given {@code scheduler} + * @throws IndexOutOfBoundsException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable takeLast(long count, long time, TimeUnit unit, Scheduler scheduler) { + return takeLast(count, time, unit, scheduler, false, bufferSize()); + } + + /** + * Returns an Observable that emits at most a specified number of items from the source ObservableSource that were + * emitted in a specified window of time before the ObservableSource completed, where the timing information is + * provided by a given Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use for tracking the current time
+ *
+ * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the {@link Scheduler} that provides the timestamps for the observed items + * @param delayError + * if true, an exception signalled by the current Observable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * @param bufferSize + * the hint about how many elements to expect to be last + * @return an Observable that emits at most {@code count} items from the source ObservableSource that were emitted + * in a specified window of time before the ObservableSource completed, where the timing information is + * provided by the given {@code scheduler} + * @throws IndexOutOfBoundsException + * if {@code count} is less than zero + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable takeLast(long count, long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + if (count < 0) { + throw new IndexOutOfBoundsException("count >= 0 required but it was " + count); + } + return RxJavaPlugins.onAssembly(new ObservableTakeLastTimed(this, count, time, unit, scheduler, bufferSize, delayError)); + } + + /** + * Returns an Observable that emits the items from the source ObservableSource that were emitted in a specified + * window of time before the ObservableSource completed. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code takeLast} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that emits the items from the source ObservableSource that were emitted in the window of + * time before the ObservableSource completed specified by {@code time} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.TRAMPOLINE) + public final Observable takeLast(long time, TimeUnit unit) { + return takeLast(time, unit, Schedulers.trampoline(), false, bufferSize()); + } + + /** + * Returns an Observable that emits the items from the source ObservableSource that were emitted in a specified + * window of time before the ObservableSource completed. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code takeLast} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param delayError + * if true, an exception signalled by the current Observable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * @return an Observable that emits the items from the source ObservableSource that were emitted in the window of + * time before the ObservableSource completed specified by {@code time} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.TRAMPOLINE) + public final Observable takeLast(long time, TimeUnit unit, boolean delayError) { + return takeLast(time, unit, Schedulers.trampoline(), delayError, bufferSize()); + } + + /** + * Returns an Observable that emits the items from the source ObservableSource that were emitted in a specified + * window of time before the ObservableSource completed, where the timing information is provided by a specified + * Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the Observed items + * @return an Observable that emits the items from the source ObservableSource that were emitted in the window of + * time before the ObservableSource completed specified by {@code time}, where the timing information is + * provided by {@code scheduler} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable takeLast(long time, TimeUnit unit, Scheduler scheduler) { + return takeLast(time, unit, scheduler, false, bufferSize()); + } + + /** + * Returns an Observable that emits the items from the source ObservableSource that were emitted in a specified + * window of time before the ObservableSource completed, where the timing information is provided by a specified + * Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the Observed items + * @param delayError + * if true, an exception signalled by the current Observable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * @return an Observable that emits the items from the source ObservableSource that were emitted in the window of + * time before the ObservableSource completed specified by {@code time}, where the timing information is + * provided by {@code scheduler} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable takeLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError) { + return takeLast(time, unit, scheduler, delayError, bufferSize()); + } + + /** + * Returns an Observable that emits the items from the source ObservableSource that were emitted in a specified + * window of time before the ObservableSource completed, where the timing information is provided by a specified + * Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the Observed items + * @param delayError + * if true, an exception signalled by the current Observable is delayed until the regular elements are consumed + * by the downstream; if false, an exception is immediately signalled and all regular elements dropped + * @param bufferSize + * the hint about how many elements to expect to be last + * @return an Observable that emits the items from the source ObservableSource that were emitted in the window of + * time before the ObservableSource completed specified by {@code time}, where the timing information is + * provided by {@code scheduler} + * @see ReactiveX operators documentation: TakeLast + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable takeLast(long time, TimeUnit unit, Scheduler scheduler, boolean delayError, int bufferSize) { + return takeLast(Long.MAX_VALUE, time, unit, scheduler, delayError, bufferSize); + } + + /** + * Returns an Observable that emits the items emitted by the source Observable until a second ObservableSource + * emits an item. + *

+ * + *

+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the ObservableSource whose first emitted item will cause {@code takeUntil} to stop emitting items + * from the source Observable + * @param + * the type of items emitted by {@code other} + * @return an Observable that emits the items emitted by the source Observable until such time as {@code other} emits its first item + * @see ReactiveX operators documentation: TakeUntil + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable takeUntil(ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new ObservableTakeUntil(this, other)); + } + + /** + * Returns an Observable that emits items emitted by the source Observable, checks the specified predicate + * for each item, and then completes when the condition is satisfied. + *

+ * + *

+ * The difference between this operator and {@link #takeWhile(Predicate)} is that here, the condition is + * evaluated after the item is emitted. + * + *

+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param stopPredicate + * a function that evaluates an item emitted by the source Observable and returns a Boolean + * @return an Observable that first emits items emitted by the source Observable, checks the specified + * condition after each item, and then completes when the condition is satisfied. + * @see ReactiveX operators documentation: TakeUntil + * @see Observable#takeWhile(Predicate) + * @since 1.1.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable takeUntil(Predicate stopPredicate) { + ObjectHelper.requireNonNull(stopPredicate, "stopPredicate is null"); + return RxJavaPlugins.onAssembly(new ObservableTakeUntilPredicate(this, stopPredicate)); + } + + /** + * Returns an Observable that emits items emitted by the source ObservableSource so long as each item satisfied a + * specified condition, and then completes as soon as this condition is not satisfied. + *

+ * + *

+ *
Scheduler:
+ *
{@code takeWhile} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function that evaluates an item emitted by the source ObservableSource and returns a Boolean + * @return an Observable that emits the items from the source ObservableSource so long as each item satisfies the + * condition defined by {@code predicate}, then completes + * @see ReactiveX operators documentation: TakeWhile + * @see Observable#takeUntil(Predicate) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable takeWhile(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new ObservableTakeWhile(this, predicate)); + } + + /** + * Returns an Observable that emits only the first item emitted by the source ObservableSource during sequential + * time windows of a specified duration. + *

+ * This differs from {@link #throttleLast} in that this only tracks passage of time whereas + * {@link #throttleLast} ticks at scheduled intervals. + *

+ * + *

+ *
Scheduler:
+ *
{@code throttleFirst} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param windowDuration + * time to wait before emitting another item after emitting the last item + * @param unit + * the unit of time of {@code windowDuration} + * @return an Observable that performs the throttle operation + * @see ReactiveX operators documentation: Sample + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable throttleFirst(long windowDuration, TimeUnit unit) { + return throttleFirst(windowDuration, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits only the first item emitted by the source ObservableSource during sequential + * time windows of a specified duration, where the windows are managed by a specified Scheduler. + *

+ * This differs from {@link #throttleLast} in that this only tracks passage of time whereas + * {@link #throttleLast} ticks at scheduled intervals. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param skipDuration + * time to wait before emitting another item after emitting the last item + * @param unit + * the unit of time of {@code skipDuration} + * @param scheduler + * the {@link Scheduler} to use internally to manage the timers that handle timeout for each + * event + * @return an Observable that performs the throttle operation + * @see ReactiveX operators documentation: Sample + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable throttleFirst(long skipDuration, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableThrottleFirstTimed(this, skipDuration, unit, scheduler)); + } + + /** + * Returns an Observable that emits only the last item emitted by the source ObservableSource during sequential + * time windows of a specified duration. + *

+ * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas + * {@link #throttleFirst} does not tick, it just tracks passage of time. + *

+ * + *

+ *
Scheduler:
+ *
{@code throttleLast} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param intervalDuration + * duration of windows within which the last item emitted by the source ObservableSource will be + * emitted + * @param unit + * the unit of time of {@code intervalDuration} + * @return an Observable that performs the throttle operation + * @see ReactiveX operators documentation: Sample + * @see #sample(long, TimeUnit) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable throttleLast(long intervalDuration, TimeUnit unit) { + return sample(intervalDuration, unit); + } + + /** + * Returns an Observable that emits only the last item emitted by the source ObservableSource during sequential + * time windows of a specified duration, where the duration is governed by a specified Scheduler. + *

+ * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas + * {@link #throttleFirst} does not tick, it just tracks passage of time. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param intervalDuration + * duration of windows within which the last item emitted by the source ObservableSource will be + * emitted + * @param unit + * the unit of time of {@code intervalDuration} + * @param scheduler + * the {@link Scheduler} to use internally to manage the timers that handle timeout for each + * event + * @return an Observable that performs the throttle operation + * @see ReactiveX operators documentation: Sample + * @see #sample(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable throttleLast(long intervalDuration, TimeUnit unit, Scheduler scheduler) { + return sample(intervalDuration, unit, scheduler); + } + + /** + * Throttles items from the upstream {@code Observable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + *

+ * + *

+ * Unlike the option with {@link #throttleLatest(long, TimeUnit, boolean)}, the very last item being held back + * (if any) is not emitted when the upstream completes. + *

+ * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + *

+ *
Scheduler:
+ *
{@code throttleLatest} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @return the new Observable instance + * @see #throttleLatest(long, TimeUnit, boolean) + * @see #throttleLatest(long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable throttleLatest(long timeout, TimeUnit unit) { + return throttleLatest(timeout, unit, Schedulers.computation(), false); + } + + /** + * Throttles items from the upstream {@code Observable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + *

+ * + *

+ * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + *

+ *
Scheduler:
+ *
{@code throttleLatest} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param emitLast If {@code true}, the very last item from the upstream will be emitted + * immediately when the upstream completes, regardless if there is + * a timeout window active or not. If {@code false}, the very last + * upstream item is ignored and the flow terminates. + * @return the new Observable instance + * @see #throttleLatest(long, TimeUnit, Scheduler, boolean) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable throttleLatest(long timeout, TimeUnit unit, boolean emitLast) { + return throttleLatest(timeout, unit, Schedulers.computation(), emitLast); + } + + /** + * Throttles items from the upstream {@code Observable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + *

+ * + *

+ * Unlike the option with {@link #throttleLatest(long, TimeUnit, Scheduler, boolean)}, the very last item being held back + * (if any) is not emitted when the upstream completes. + *

+ * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param scheduler the {@link Scheduler} where the timed wait and latest item + * emission will be performed + * @return the new Observable instance + * @see #throttleLatest(long, TimeUnit, Scheduler, boolean) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable throttleLatest(long timeout, TimeUnit unit, Scheduler scheduler) { + return throttleLatest(timeout, unit, scheduler, false); + } + + /** + * Throttles items from the upstream {@code Observable} by first emitting the next + * item from upstream, then periodically emitting the latest item (if any) when + * the specified timeout elapses between them. + *

+ * + *

+ * If no items were emitted from the upstream during this timeout phase, the next + * upstream item is emitted immediately and the timeout window starts from then. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait after an item emission towards the downstream + * before trying to emit the latest item from upstream again + * @param unit the time unit + * @param scheduler the {@link Scheduler} where the timed wait and latest item + * emission will be performed + * @param emitLast If {@code true}, the very last item from the upstream will be emitted + * immediately when the upstream completes, regardless if there is + * a timeout window active or not. If {@code false}, the very last + * upstream item is ignored and the flow terminates. + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable throttleLatest(long timeout, TimeUnit unit, Scheduler scheduler, boolean emitLast) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableThrottleLatest(this, timeout, unit, scheduler, emitLast)); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, except that it drops items emitted by the + * source ObservableSource that are followed by newer items before a timeout value expires. The timer resets on + * each emission (alias to {@link #debounce(long, TimeUnit, Scheduler)}). + *

+ * Note: If items keep being emitted by the source ObservableSource faster than the timeout then no items + * will be emitted by the resulting ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code throttleWithTimeout} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * the length of the window of time that must pass after the emission of an item from the source + * ObservableSource in which that ObservableSource emits no items in order for the item to be emitted by the + * resulting ObservableSource + * @param unit + * the unit of time for the specified {@code timeout} + * @return an Observable that filters out items from the source ObservableSource that are too quickly followed by + * newer items + * @see ReactiveX operators documentation: Debounce + * @see #debounce(long, TimeUnit) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable throttleWithTimeout(long timeout, TimeUnit unit) { + return debounce(timeout, unit); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, except that it drops items emitted by the + * source ObservableSource that are followed by newer items before a timeout value expires on a specified + * Scheduler. The timer resets on each emission (Alias to {@link #debounce(long, TimeUnit, Scheduler)}). + *

+ * Note: If items keep being emitted by the source ObservableSource faster than the timeout then no items + * will be emitted by the resulting ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * the length of the window of time that must pass after the emission of an item from the source + * ObservableSource in which that ObservableSource emits no items in order for the item to be emitted by the + * resulting ObservableSource + * @param unit + * the unit of time for the specified {@code timeout} + * @param scheduler + * the {@link Scheduler} to use internally to manage the timers that handle the timeout for each + * item + * @return an Observable that filters out items from the source ObservableSource that are too quickly followed by + * newer items + * @see ReactiveX operators documentation: Debounce + * @see #debounce(long, TimeUnit, Scheduler) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable throttleWithTimeout(long timeout, TimeUnit unit, Scheduler scheduler) { + return debounce(timeout, unit, scheduler); + } + + /** + * Returns an Observable that emits records of the time interval between consecutive items emitted by the + * source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeInterval} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @return an Observable that emits time interval information items + * @see ReactiveX operators documentation: TimeInterval + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> timeInterval() { + return timeInterval(TimeUnit.MILLISECONDS, Schedulers.computation()); + } + + /** + * Returns an Observable that emits records of the time interval between consecutive items emitted by the + * source ObservableSource, where this interval is computed on a specified Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
The operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
+ *
+ * + * @param scheduler + * the {@link Scheduler} used to compute time intervals + * @return an Observable that emits time interval information items + * @see ReactiveX operators documentation: TimeInterval + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. + public final Observable> timeInterval(Scheduler scheduler) { + return timeInterval(TimeUnit.MILLISECONDS, scheduler); + } + + /** + * Returns an Observable that emits records of the time interval between consecutive items emitted by the + * source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeInterval} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param unit the time unit for the current time + * @return an Observable that emits time interval information items + * @see ReactiveX operators documentation: TimeInterval + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> timeInterval(TimeUnit unit) { + return timeInterval(unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits records of the time interval between consecutive items emitted by the + * source ObservableSource, where this interval is computed on a specified Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
The operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
+ *
+ * + * @param unit the time unit for the current time + * @param scheduler + * the {@link Scheduler} used to compute time intervals + * @return an Observable that emits time interval information items + * @see ReactiveX operators documentation: TimeInterval + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. + public final Observable> timeInterval(TimeUnit unit, Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableTimeInterval(this, unit, scheduler)); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, but notifies observers of a + * {@code TimeoutException} if an item emitted by the source ObservableSource doesn't arrive within a window of + * time after the emission of the previous item, where that period of time is measured by an ObservableSource that + * is a function of the previous item. + *

+ * + *

+ * Note: The arrival of the first source item is never timed out. + *

+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
+ *
+ * + * @param + * the timeout value type (ignored) + * @param itemTimeoutIndicator + * a function that returns an ObservableSource for each item emitted by the source + * ObservableSource and that determines the timeout window for the subsequent item + * @return an Observable that mirrors the source ObservableSource, but notifies observers of a + * {@code TimeoutException} if an item emitted by the source ObservableSource takes longer to arrive than + * the time window defined by the selector for the previously emitted item + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable timeout(Function> itemTimeoutIndicator) { + return timeout0(null, itemTimeoutIndicator, null); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, but that switches to a fallback ObservableSource if + * an item emitted by the source ObservableSource doesn't arrive within a window of time after the emission of the + * previous item, where that period of time is measured by an ObservableSource that is a function of the previous + * item. + *

+ * + *

+ * Note: The arrival of the first source item is never timed out. + *

+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
+ *
+ * + * @param + * the timeout value type (ignored) + * @param itemTimeoutIndicator + * a function that returns an ObservableSource, for each item emitted by the source ObservableSource, that + * determines the timeout window for the subsequent item + * @param other + * the fallback ObservableSource to switch to if the source ObservableSource times out + * @return an Observable that mirrors the source ObservableSource, but switches to mirroring a fallback ObservableSource + * if an item emitted by the source ObservableSource takes longer to arrive than the time window defined + * by the selector for the previously emitted item + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable timeout(Function> itemTimeoutIndicator, + ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(null, itemTimeoutIndicator, other); + } + + /** + * Returns an Observable that mirrors the source ObservableSource but applies a timeout policy for each emitted + * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, + * the resulting ObservableSource terminates and notifies observers of a {@code TimeoutException}. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * maximum duration between emitted items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument. + * @return the source ObservableSource modified to notify observers of a {@code TimeoutException} in case of a + * timeout + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable timeout(long timeout, TimeUnit timeUnit) { + return timeout0(timeout, timeUnit, null, Schedulers.computation()); + } + + /** + * Returns an Observable that mirrors the source ObservableSource but applies a timeout policy for each emitted + * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, + * the source ObservableSource is disposed and resulting ObservableSource begins instead + * to mirror a fallback ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param other + * the fallback ObservableSource to use in case of a timeout + * @return the source ObservableSource modified to switch to the fallback ObservableSource in case of a timeout + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable timeout(long timeout, TimeUnit timeUnit, ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(timeout, timeUnit, other, Schedulers.computation()); + } + + /** + * Returns an Observable that mirrors the source ObservableSource but applies a timeout policy for each emitted + * item using a specified Scheduler. If the next item isn't emitted within the specified timeout duration + * starting from its predecessor, the source ObservableSource is disposed and resulting ObservableSource + * begins instead to mirror a fallback ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param scheduler + * the {@link Scheduler} to run the timeout timers on + * @param other + * the ObservableSource to use as the fallback in case of a timeout + * @return the source ObservableSource modified so that it will switch to the fallback ObservableSource in case of a + * timeout + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler, ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(timeout, timeUnit, other, scheduler); + } + + /** + * Returns an Observable that mirrors the source ObservableSource but applies a timeout policy for each emitted + * item, where this policy is governed on a specified Scheduler. If the next item isn't emitted within the + * specified timeout duration starting from its predecessor, the resulting ObservableSource terminates and + * notifies observers of a {@code TimeoutException}. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param scheduler + * the Scheduler to run the timeout timers on + * @return the source ObservableSource modified to notify observers of a {@code TimeoutException} in case of a + * timeout + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler) { + return timeout0(timeout, timeUnit, null, scheduler); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, but notifies observers of a + * {@code TimeoutException} if either the first item emitted by the source ObservableSource or any subsequent item + * doesn't arrive within time windows defined by other ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
+ *
+ * + * @param + * the first timeout value type (ignored) + * @param + * the subsequent timeout value type (ignored) + * @param firstTimeoutIndicator + * a function that returns an ObservableSource that determines the timeout window for the first source + * item + * @param itemTimeoutIndicator + * a function that returns an ObservableSource for each item emitted by the source ObservableSource and that + * determines the timeout window in which the subsequent source item must arrive in order to + * continue the sequence + * @return an Observable that mirrors the source ObservableSource, but notifies observers of a + * {@code TimeoutException} if either the first item or any subsequent item doesn't arrive within + * the time windows specified by the timeout selectors + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable timeout(ObservableSource firstTimeoutIndicator, + Function> itemTimeoutIndicator) { + ObjectHelper.requireNonNull(firstTimeoutIndicator, "firstTimeoutIndicator is null"); + return timeout0(firstTimeoutIndicator, itemTimeoutIndicator, null); + } + + /** + * Returns an Observable that mirrors the source ObservableSource, but switches to a fallback ObservableSource if either + * the first item emitted by the source ObservableSource or any subsequent item doesn't arrive within time windows + * defined by other ObservableSources. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code timeout} operates by default on the {@code immediate} {@link Scheduler}.
+ *
+ * + * @param + * the first timeout value type (ignored) + * @param + * the subsequent timeout value type (ignored) + * @param firstTimeoutIndicator + * a function that returns an ObservableSource which determines the timeout window for the first source + * item + * @param itemTimeoutIndicator + * a function that returns an ObservableSource for each item emitted by the source ObservableSource and that + * determines the timeout window in which the subsequent source item must arrive in order to + * continue the sequence + * @param other + * the fallback ObservableSource to switch to if the source ObservableSource times out + * @return an Observable that mirrors the source ObservableSource, but switches to the {@code other} ObservableSource if + * either the first item emitted by the source ObservableSource or any subsequent item doesn't arrive + * within time windows defined by the timeout selectors + * @throws NullPointerException + * if {@code itemTimeoutIndicator} is null, or + * if {@code other} is null + * @see ReactiveX operators documentation: Timeout + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable timeout( + ObservableSource firstTimeoutIndicator, + Function> itemTimeoutIndicator, + ObservableSource other) { + ObjectHelper.requireNonNull(firstTimeoutIndicator, "firstTimeoutIndicator is null"); + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(firstTimeoutIndicator, itemTimeoutIndicator, other); + } + + private Observable timeout0(long timeout, TimeUnit timeUnit, ObservableSource other, + Scheduler scheduler) { + ObjectHelper.requireNonNull(timeUnit, "timeUnit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableTimeoutTimed(this, timeout, timeUnit, scheduler, other)); + } + + private Observable timeout0( + ObservableSource firstTimeoutIndicator, + Function> itemTimeoutIndicator, + ObservableSource other) { + ObjectHelper.requireNonNull(itemTimeoutIndicator, "itemTimeoutIndicator is null"); + return RxJavaPlugins.onAssembly(new ObservableTimeout(this, firstTimeoutIndicator, itemTimeoutIndicator, other)); + } + + /** + * Returns an Observable that emits each item emitted by the source ObservableSource, wrapped in a + * {@link Timed} object. + *

+ * + *

+ *
Scheduler:
+ *
{@code timestamp} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @return an Observable that emits timestamped items from the source ObservableSource + * @see ReactiveX operators documentation: Timestamp + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> timestamp() { + return timestamp(TimeUnit.MILLISECONDS, Schedulers.computation()); + } + + /** + * Returns an Observable that emits each item emitted by the source ObservableSource, wrapped in a + * {@link Timed} object whose timestamps are provided by a specified Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
This operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to use as a time source + * @return an Observable that emits timestamped items from the source ObservableSource with timestamps provided by + * the {@code scheduler} + * @see ReactiveX operators documentation: Timestamp + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. + public final Observable> timestamp(Scheduler scheduler) { + return timestamp(TimeUnit.MILLISECONDS, scheduler); + } + + /** + * Returns an Observable that emits each item emitted by the source ObservableSource, wrapped in a + * {@link Timed} object. + *

+ * + *

+ *
Scheduler:
+ *
{@code timestamp} does not operate on any particular scheduler but uses the current time + * from the {@code computation} {@link Scheduler}.
+ *
+ * + * @param unit the time unit for the current time + * @return an Observable that emits timestamped items from the source ObservableSource + * @see ReactiveX operators documentation: Timestamp + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> timestamp(TimeUnit unit) { + return timestamp(unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits each item emitted by the source ObservableSource, wrapped in a + * {@link Timed} object whose timestamps are provided by a specified Scheduler. + *

+ * + *

+ *
Scheduler:
+ *
This operator does not operate on any particular scheduler but uses the current time + * from the specified {@link Scheduler}.
+ *
+ * + * @param unit the time unit for the current time + * @param scheduler + * the {@link Scheduler} to use as a time source + * @return an Observable that emits timestamped items from the source ObservableSource with timestamps provided by + * the {@code scheduler} + * @see ReactiveX operators documentation: Timestamp + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) // Supplied scheduler is only used for creating timestamps. + public final Observable> timestamp(final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return map(Functions.timestampWith(unit, scheduler)); + } + + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * This allows fluent conversion to any other type. + *

+ *
Scheduler:
+ *
{@code to} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the resulting object type + * @param converter the function that receives the current Observable instance and returns a value + * @return the value returned by the function + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final R to(Function, R> converter) { + try { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Returns a Single that emits a single item, a list composed of all the items emitted by the + * finite source ObservableSource. + *

+ * + *

+ * Normally, an ObservableSource that returns multiple items will do so by invoking its {@link Observer}'s + * {@link Observer#onNext onNext} method for each such item. You can change this behavior, instructing the + * ObservableSource to compose a list of all of these items and then to invoke the Observer's {@code onNext} + * function once, passing it the entire list, by calling the ObservableSource's {@code toList} method prior to + * calling its {@link #subscribe} method. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a Single that emits a single item: a List containing all of the items emitted by the source + * ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toList() { + return toList(16); + } + + /** + * Returns a Single that emits a single item, a list composed of all the items emitted by the + * finite source ObservableSource. + *

+ * + *

+ * Normally, an ObservableSource that returns multiple items will do so by invoking its {@link Observer}'s + * {@link Observer#onNext onNext} method for each such item. You can change this behavior, instructing the + * ObservableSource to compose a list of all of these items and then to invoke the Observer's {@code onNext} + * function once, passing it the entire list, by calling the ObservableSource's {@code toList} method prior to + * calling its {@link #subscribe} method. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacityHint + * the number of elements expected from the current Observable + * @return a Single that emits a single item: a List containing all of the items emitted by the source + * ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toList(final int capacityHint) { + ObjectHelper.verifyPositive(capacityHint, "capacityHint"); + return RxJavaPlugins.onAssembly(new ObservableToListSingle>(this, capacityHint)); + } + + /** + * Returns a Single that emits a single item, a list composed of all the items emitted by the + * finite source ObservableSource. + *

+ * + *

+ * Normally, an ObservableSource that returns multiple items will do so by invoking its {@link Observer}'s + * {@link Observer#onNext onNext} method for each such item. You can change this behavior, instructing the + * ObservableSource to compose a list of all of these items and then to invoke the Observer's {@code onNext} + * function once, passing it the entire list, by calling the ObservableSource's {@code toList} method prior to + * calling its {@link #subscribe} method. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated collection to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the subclass of a collection of Ts + * @param collectionSupplier + * the Callable returning the collection (for each individual Observer) to be filled in + * @return a Single that emits a single item: a List containing all of the items emitted by the source + * ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final > Single toList(Callable collectionSupplier) { + ObjectHelper.requireNonNull(collectionSupplier, "collectionSupplier is null"); + return RxJavaPlugins.onAssembly(new ObservableToListSingle(this, collectionSupplier)); + } + + /** + * Returns a Single that emits a single HashMap containing all items emitted by the + * finite source ObservableSource, mapped by the keys returned by a specified + * {@code keySelector} function. + *

+ * + *

+ * If more than one source item maps to the same key, the HashMap will contain the latest of those items. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param keySelector + * the function that extracts the key from a source item to be used in the HashMap + * @return a Single that emits a single item: a HashMap containing the mapped items from the source + * ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toMap(final Function keySelector) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + return collect(HashMapSupplier.asCallable(), Functions.toMapKeySelector(keySelector)); + } + + /** + * Returns a Single that emits a single HashMap containing values corresponding to items emitted by the + * finite source ObservableSource, mapped by the keys returned by a specified {@code keySelector} function. + *

+ * + *

+ * If more than one source item maps to the same key, the HashMap will contain a single entry that + * corresponds to the latest of those items. + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts the key from a source item to be used in the HashMap + * @param valueSelector + * the function that extracts the value from a source item to be used in the HashMap + * @return a Single that emits a single item: a HashMap containing the mapped items from the source + * ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toMap( + final Function keySelector, + final Function valueSelector) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + return collect(HashMapSupplier.asCallable(), Functions.toMapKeyValueSelector(keySelector, valueSelector)); + } + + /** + * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that + * contains keys and values extracted from the items emitted by the finite source ObservableSource. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts the key from a source item to be used in the Map + * @param valueSelector + * the function that extracts the value from the source items to be used as value in the Map + * @param mapSupplier + * the function that returns a Map instance to be used + * @return a Single that emits a single item: a Map that contains the mapped items emitted by the + * source ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toMap( + final Function keySelector, + final Function valueSelector, + Callable> mapSupplier) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + ObjectHelper.requireNonNull(mapSupplier, "mapSupplier is null"); + return collect(mapSupplier, Functions.toMapKeyValueSelector(keySelector, valueSelector)); + } + + /** + * Returns a Single that emits a single HashMap that contains an ArrayList of items emitted by the + * finite source ObservableSource keyed by a specified {@code keySelector} function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toMultimap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param keySelector + * the function that extracts the key from the source items to be used as key in the HashMap + * @return a Single that emits a single item: a HashMap that contains an ArrayList of items mapped from + * the source ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single>> toMultimap(Function keySelector) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + Function valueSelector = (Function)Functions.identity(); + Callable>> mapSupplier = HashMapSupplier.asCallable(); + Function> collectionFactory = ArrayListSupplier.asFunction(); + return toMultimap(keySelector, valueSelector, mapSupplier, collectionFactory); + } + + /** + * Returns a Single that emits a single HashMap that contains an ArrayList of values extracted by a + * specified {@code valueSelector} function from items emitted by the finite source ObservableSource, + * keyed by a specified {@code keySelector} function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toMultimap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts a key from the source items to be used as key in the HashMap + * @param valueSelector + * the function that extracts a value from the source items to be used as value in the HashMap + * @return a Single that emits a single item: a HashMap that contains an ArrayList of items mapped from + * the source ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single>> toMultimap(Function keySelector, Function valueSelector) { + Callable>> mapSupplier = HashMapSupplier.asCallable(); + Function> collectionFactory = ArrayListSupplier.asFunction(); + return toMultimap(keySelector, valueSelector, mapSupplier, collectionFactory); + } + + /** + * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that + * contains a custom collection of values, extracted by a specified {@code valueSelector} function from + * items emitted by the source ObservableSource, and keyed by the {@code keySelector} function. + *

+ * + *

+ *
Scheduler:
+ *
{@code toMultimap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts a key from the source items to be used as the key in the Map + * @param valueSelector + * the function that extracts a value from the source items to be used as the value in the Map + * @param mapSupplier + * the function that returns a Map instance to be used + * @param collectionFactory + * the function that returns a Collection instance for a particular key to be used in the Map + * @return a Single that emits a single item: a Map that contains the collection of mapped items from + * the source ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single>> toMultimap( + final Function keySelector, + final Function valueSelector, + final Callable>> mapSupplier, + final Function> collectionFactory) { + ObjectHelper.requireNonNull(keySelector, "keySelector is null"); + ObjectHelper.requireNonNull(valueSelector, "valueSelector is null"); + ObjectHelper.requireNonNull(mapSupplier, "mapSupplier is null"); + ObjectHelper.requireNonNull(collectionFactory, "collectionFactory is null"); + return collect(mapSupplier, Functions.toMultimapKeyValueSelector(keySelector, valueSelector, collectionFactory)); + } + + /** + * Returns a Single that emits a single Map, returned by a specified {@code mapFactory} function, that + * contains an ArrayList of values, extracted by a specified {@code valueSelector} function from items + * emitted by the finite source ObservableSource and keyed by the {@code keySelector} function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated map to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toMultimap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the key type of the Map + * @param the value type of the Map + * @param keySelector + * the function that extracts a key from the source items to be used as the key in the Map + * @param valueSelector + * the function that extracts a value from the source items to be used as the value in the Map + * @param mapSupplier + * the function that returns a Map instance to be used + * @return a Single that emits a single item: a Map that contains a list items mapped from the source + * ObservableSource + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single>> toMultimap( + Function keySelector, + Function valueSelector, + Callable>> mapSupplier + ) { + return toMultimap(keySelector, valueSelector, mapSupplier, ArrayListSupplier.asFunction()); + } + + /** + * Converts the current Observable into a Flowable by applying the specified backpressure strategy. + *

+ * Marble diagrams for the various backpressure strategies are as follows: + *

    + *
  • {@link BackpressureStrategy#BUFFER} + *

    + * + *

  • + *
  • {@link BackpressureStrategy#DROP} + *

    + * + *

  • + *
  • {@link BackpressureStrategy#LATEST} + *

    + * + *

  • + *
  • {@link BackpressureStrategy#ERROR} + *

    + * + *

  • + *
  • {@link BackpressureStrategy#MISSING} + *

    + * + *

  • + *
+ *
+ *
Backpressure:
+ *
The operator applies the chosen backpressure strategy of {@link BackpressureStrategy} enum.
+ *
Scheduler:
+ *
{@code toFlowable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param strategy the backpressure strategy to apply + * @return the new Flowable instance + */ + @BackpressureSupport(BackpressureKind.SPECIAL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable toFlowable(BackpressureStrategy strategy) { + Flowable f = new FlowableFromObservable(this); + + switch (strategy) { + case DROP: + return f.onBackpressureDrop(); + case LATEST: + return f.onBackpressureLatest(); + case MISSING: + return f; + case ERROR: + return RxJavaPlugins.onAssembly(new FlowableOnBackpressureError(f)); + default: + return f.onBackpressureBuffer(); + } + } + + /** + * Returns a Single that emits a list that contains the items emitted by the finite source ObservableSource, in a + * sorted order. Each item emitted by the ObservableSource must implement {@link Comparable} with respect to all + * other items in the sequence. + * + *

If any item emitted by this Observable does not implement {@link Comparable} with respect to + * all other items emitted by this Observable, no items will be emitted and the + * sequence is terminated with a {@link ClassCastException}. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toSortedList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return a Single that emits a list that contains the items emitted by the source ObservableSource in + * sorted order + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toSortedList() { + return toSortedList(Functions.naturalOrder()); + } + + /** + * Returns a Single that emits a list that contains the items emitted by the finite source ObservableSource, in a + * sorted order based on a specified comparison function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toSortedList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param comparator + * a function that compares two items emitted by the source ObservableSource and returns an Integer + * that indicates their sort order + * @return a Single that emits a list that contains the items emitted by the source ObservableSource in + * sorted order + * @see ReactiveX operators documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toSortedList(final Comparator comparator) { + ObjectHelper.requireNonNull(comparator, "comparator is null"); + return toList().map(Functions.listSorter(comparator)); + } + + /** + * Returns a Single that emits a list that contains the items emitted by the finite source ObservableSource, in a + * sorted order based on a specified comparison function. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toSortedList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param comparator + * a function that compares two items emitted by the source ObservableSource and returns an Integer + * that indicates their sort order + * @param capacityHint + * the initial capacity of the ArrayList used to accumulate items before sorting + * @return a Single that emits a list that contains the items emitted by the source ObservableSource in + * sorted order + * @see ReactiveX operators documentation: To + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toSortedList(final Comparator comparator, int capacityHint) { + ObjectHelper.requireNonNull(comparator, "comparator is null"); + return toList(capacityHint).map(Functions.listSorter(comparator)); + } + + /** + * Returns a Single that emits a list that contains the items emitted by the finite source ObservableSource, in a + * sorted order. Each item emitted by the ObservableSource must implement {@link Comparable} with respect to all + * other items in the sequence. + * + *

If any item emitted by this Observable does not implement {@link Comparable} with respect to + * all other items emitted by this Observable, no items will be emitted and the + * sequence is terminated with a {@link ClassCastException}. + *

+ * + *

+ * Note that this operator requires the upstream to signal {@code onComplete} for the accumulated list to + * be emitted. Sources that are infinite and never complete will never emit anything through this + * operator and an infinite source may lead to a fatal {@code OutOfMemoryError}. + *

+ *
Scheduler:
+ *
{@code toSortedList} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param capacityHint + * the initial capacity of the ArrayList used to accumulate items before sorting + * @return a Single that emits a list that contains the items emitted by the source ObservableSource in + * sorted order + * @see ReactiveX operators documentation: To + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> toSortedList(int capacityHint) { + return toSortedList(Functions.naturalOrder(), capacityHint); + } + + /** + * Modifies the source ObservableSource so that subscribers will dispose it on a specified + * {@link Scheduler}. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to perform the call to dispose() of the upstream Disposable + * @return the source ObservableSource modified so that its dispose() calls happen on the specified + * {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable unsubscribeOn(Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableUnsubscribeOn(this, scheduler)); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows, each containing {@code count} items. When the source + * ObservableSource completes or encounters an error, the resulting ObservableSource emits the current window and + * propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum size of each window before it should be emitted + * @return an Observable that emits connected, non-overlapping windows, each containing at most + * {@code count} items from the source ObservableSource + * @throws IllegalArgumentException if either count is non-positive + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> window(long count) { + return window(count, count, bufferSize()); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits windows every {@code skip} items, each containing no more than {@code count} items. When + * the source ObservableSource completes or encounters an error, the resulting ObservableSource emits the current window + * and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum size of each window before it should be emitted + * @param skip + * how many items need to be skipped before starting a new window. Note that if {@code skip} and + * {@code count} are equal this is the same operation as {@link #window(long)}. + * @return an Observable that emits windows every {@code skip} items containing at most {@code count} items + * from the source ObservableSource + * @throws IllegalArgumentException if either count or skip is non-positive + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> window(long count, long skip) { + return window(count, skip, bufferSize()); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits windows every {@code skip} items, each containing no more than {@code count} items. When + * the source ObservableSource completes or encounters an error, the resulting ObservableSource emits the current window + * and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param count + * the maximum size of each window before it should be emitted + * @param skip + * how many items need to be skipped before starting a new window. Note that if {@code skip} and + * {@code count} are equal this is the same operation as {@link #window(long)}. + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return an Observable that emits windows every {@code skip} items containing at most {@code count} items + * from the source ObservableSource + * @throws IllegalArgumentException if either count or skip is non-positive + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> window(long count, long skip, int bufferSize) { + ObjectHelper.verifyPositive(count, "count"); + ObjectHelper.verifyPositive(skip, "skip"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new ObservableWindow(this, count, skip, bufferSize)); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource starts a new window periodically, as determined by the {@code timeskip} argument. It emits + * each window after a fixed timespan, specified by the {@code timespan} argument. When the source + * ObservableSource completes or ObservableSource completes or encounters an error, the resulting ObservableSource emits the + * current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted + * @param timeskip + * the period of time after which a new window will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @return an Observable that emits new windows periodically as a fixed timespan elapses + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable> window(long timespan, long timeskip, TimeUnit unit) { + return window(timespan, timeskip, unit, Schedulers.computation(), bufferSize()); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource starts a new window periodically, as determined by the {@code timeskip} argument. It emits + * each window after a fixed timespan, specified by the {@code timespan} argument. When the source + * ObservableSource completes or ObservableSource completes or encounters an error, the resulting ObservableSource emits the + * current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted + * @param timeskip + * the period of time after which a new window will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @return an Observable that emits new windows periodically as a fixed timespan elapses + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable> window(long timespan, long timeskip, TimeUnit unit, Scheduler scheduler) { + return window(timespan, timeskip, unit, scheduler, bufferSize()); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource starts a new window periodically, as determined by the {@code timeskip} argument. It emits + * each window after a fixed timespan, specified by the {@code timespan} argument. When the source + * ObservableSource completes or ObservableSource completes or encounters an error, the resulting ObservableSource emits the + * current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted + * @param timeskip + * the period of time after which a new window will be created + * @param unit + * the unit of time that applies to the {@code timespan} and {@code timeskip} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return an Observable that emits new windows periodically as a fixed timespan elapses + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable> window(long timespan, long timeskip, TimeUnit unit, Scheduler scheduler, int bufferSize) { + ObjectHelper.verifyPositive(timespan, "timespan"); + ObjectHelper.verifyPositive(timeskip, "timeskip"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + return RxJavaPlugins.onAssembly(new ObservableWindowTimed(this, timespan, timeskip, unit, scheduler, Long.MAX_VALUE, bufferSize, false)); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument. When the source ObservableSource completes or encounters an error, the resulting + * ObservableSource emits the current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time that applies to the {@code timespan} argument + * @return an Observable that emits connected, non-overlapping windows representing items emitted by the + * source ObservableSource during fixed, consecutive durations + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable> window(long timespan, TimeUnit unit) { + return window(timespan, unit, Schedulers.computation(), Long.MAX_VALUE, false); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows, each of a fixed duration as specified by the + * {@code timespan} argument or a maximum size as specified by the {@code count} argument (whichever is + * reached first). When the source ObservableSource completes or encounters an error, the resulting ObservableSource + * emits the current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time that applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @return an Observable that emits connected, non-overlapping windows of items from the source ObservableSource + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable> window(long timespan, TimeUnit unit, + long count) { + return window(timespan, unit, Schedulers.computation(), count, false); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows, each of a fixed duration as specified by the + * {@code timespan} argument or a maximum size as specified by the {@code count} argument (whichever is + * reached first). When the source ObservableSource completes or encounters an error, the resulting ObservableSource + * emits the current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time that applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @param restart + * if true, when a window reaches the capacity limit, the timer is restarted as well + * @return an Observable that emits connected, non-overlapping windows of items from the source ObservableSource + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable> window(long timespan, TimeUnit unit, + long count, boolean restart) { + return window(timespan, unit, Schedulers.computation(), count, restart); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows, each of a fixed duration as specified by the + * {@code timespan} argument. When the source ObservableSource completes or encounters an error, the resulting + * ObservableSource emits the current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @return an Observable that emits connected, non-overlapping windows containing items emitted by the + * source ObservableSource within a fixed duration + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable> window(long timespan, TimeUnit unit, + Scheduler scheduler) { + return window(timespan, unit, scheduler, Long.MAX_VALUE, false); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source ObservableSource completes or encounters an error, the resulting ObservableSource emits the + * current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @return an Observable that emits connected, non-overlapping windows of items from the source ObservableSource + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable> window(long timespan, TimeUnit unit, + Scheduler scheduler, long count) { + return window(timespan, unit, scheduler, count, false); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source ObservableSource completes or encounters an error, the resulting ObservableSource emits the + * current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @param restart + * if true, when a window reaches the capacity limit, the timer is restarted as well + * @return an Observable that emits connected, non-overlapping windows of items from the source ObservableSource + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable> window(long timespan, TimeUnit unit, + Scheduler scheduler, long count, boolean restart) { + return window(timespan, unit, scheduler, count, restart, bufferSize()); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source ObservableSource completes or encounters an error, the resulting ObservableSource emits the + * current window and propagates the notification from the source ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param timespan + * the period of time each window collects items before it should be emitted and replaced with a + * new window + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @param restart + * if true, when a window reaches the capacity limit, the timer is restarted as well + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return an Observable that emits connected, non-overlapping windows of items from the source ObservableSource + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable> window( + long timespan, TimeUnit unit, Scheduler scheduler, + long count, boolean restart, int bufferSize) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.verifyPositive(count, "count"); + return RxJavaPlugins.onAssembly(new ObservableWindowTimed(this, timespan, timespan, unit, scheduler, count, bufferSize, restart)); + } + + /** + * Returns an Observable that emits non-overlapping windows of items it collects from the source ObservableSource + * where the boundary of each window is determined by the items emitted from a specified boundary-governing + * ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the window element type (ignored) + * @param boundary + * an ObservableSource whose emitted items close and open windows + * @return an Observable that emits non-overlapping windows of items it collects from the source ObservableSource + * where the boundary of each window is determined by the items emitted from the {@code boundary} + * ObservableSource + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> window(ObservableSource boundary) { + return window(boundary, bufferSize()); + } + + /** + * Returns an Observable that emits non-overlapping windows of items it collects from the source ObservableSource + * where the boundary of each window is determined by the items emitted from a specified boundary-governing + * ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the window element type (ignored) + * @param boundary + * an ObservableSource whose emitted items close and open windows + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return an Observable that emits non-overlapping windows of items it collects from the source ObservableSource + * where the boundary of each window is determined by the items emitted from the {@code boundary} + * ObservableSource + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> window(ObservableSource boundary, int bufferSize) { + ObjectHelper.requireNonNull(boundary, "boundary is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new ObservableWindowBoundary(this, boundary, bufferSize)); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits windows that contain those items emitted by the source ObservableSource between the time when + * the {@code openingIndicator} ObservableSource emits an item and when the ObservableSource returned by + * {@code closingIndicator} emits an item. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the window-opening ObservableSource + * @param the element type of the window-closing ObservableSources + * @param openingIndicator + * an ObservableSource that, when it emits an item, causes another window to be created + * @param closingIndicator + * a {@link Function} that produces an ObservableSource for every window created. When this ObservableSource + * emits an item, the associated window is closed and emitted + * @return an Observable that emits windows of items emitted by the source ObservableSource that are governed by + * the specified window-governing ObservableSources + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> window( + ObservableSource openingIndicator, + Function> closingIndicator) { + return window(openingIndicator, closingIndicator, bufferSize()); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits windows that contain those items emitted by the source ObservableSource between the time when + * the {@code openingIndicator} ObservableSource emits an item and when the ObservableSource returned by + * {@code closingIndicator} emits an item. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the window-opening ObservableSource + * @param the element type of the window-closing ObservableSources + * @param openingIndicator + * an ObservableSource that, when it emits an item, causes another window to be created + * @param closingIndicator + * a {@link Function} that produces an ObservableSource for every window created. When this ObservableSource + * emits an item, the associated window is closed and emitted + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return an Observable that emits windows of items emitted by the source ObservableSource that are governed by + * the specified window-governing ObservableSources + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> window( + ObservableSource openingIndicator, + Function> closingIndicator, int bufferSize) { + ObjectHelper.requireNonNull(openingIndicator, "openingIndicator is null"); + ObjectHelper.requireNonNull(closingIndicator, "closingIndicator is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new ObservableWindowBoundarySelector(this, openingIndicator, closingIndicator, bufferSize)); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows. It emits the current window and opens a new one + * whenever the ObservableSource produced by the specified {@code closingIndicator} emits an item. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the boundary ObservableSource + * @param boundary + * a {@link Callable} that returns an {@code ObservableSource} that governs the boundary between windows. + * When the source {@code ObservableSource} emits an item, {@code window} emits the current window and begins + * a new one. + * @return an Observable that emits connected, non-overlapping windows of items from the source ObservableSource + * whenever {@code closingIndicator} emits an item + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> window(Callable> boundary) { + return window(boundary, bufferSize()); + } + + /** + * Returns an Observable that emits windows of items it collects from the source ObservableSource. The resulting + * ObservableSource emits connected, non-overlapping windows. It emits the current window and opens a new one + * whenever the ObservableSource produced by the specified {@code closingIndicator} emits an item. + *

+ * + *

+ *
Scheduler:
+ *
This version of {@code window} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the boundary ObservableSource + * @param boundary + * a {@link Callable} that returns an {@code ObservableSource} that governs the boundary between windows. + * When the source {@code ObservableSource} emits an item, {@code window} emits the current window and begins + * a new one. + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @return an Observable that emits connected, non-overlapping windows of items from the source ObservableSource + * whenever {@code closingIndicator} emits an item + * @see ReactiveX operators documentation: Window + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable> window(Callable> boundary, int bufferSize) { + ObjectHelper.requireNonNull(boundary, "boundary is null"); + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + return RxJavaPlugins.onAssembly(new ObservableWindowBoundarySupplier(this, boundary, bufferSize)); + } + + /** + * Merges the specified ObservableSource into this ObservableSource sequence by using the {@code resultSelector} + * function only when the source ObservableSource (this instance) emits an item. + *

+ * + * + *

+ *
Scheduler:
+ *
This operator, by default, doesn't run any particular {@link Scheduler}.
+ *
+ * + * @param the element type of the other ObservableSource + * @param the result type of the combination + * @param other + * the other ObservableSource + * @param combiner + * the function to call when this ObservableSource emits an item and the other ObservableSource has already + * emitted an item, to generate the item to be emitted by the resulting ObservableSource + * @return an Observable that merges the specified ObservableSource into this ObservableSource by using the + * {@code resultSelector} function only when the source ObservableSource sequence (this instance) emits an + * item + * @since 2.0 + * @see ReactiveX operators documentation: CombineLatest + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable withLatestFrom(ObservableSource other, BiFunction combiner) { + ObjectHelper.requireNonNull(other, "other is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + + return RxJavaPlugins.onAssembly(new ObservableWithLatestFrom(this, combiner, other)); + } + + /** + * Combines the value emission from this ObservableSource with the latest emissions from the + * other ObservableSources via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this ObservableSource emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + *

+ * + *

+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first other source's value type + * @param the second other source's value type + * @param the result value type + * @param o1 the first other ObservableSource + * @param o2 the second other ObservableSource + * @param combiner the function called with an array of values from each participating ObservableSource + * @return the new ObservableSource instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable withLatestFrom( + ObservableSource o1, ObservableSource o2, + Function3 combiner) { + ObjectHelper.requireNonNull(o1, "o1 is null"); + ObjectHelper.requireNonNull(o2, "o2 is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + Function f = Functions.toFunction(combiner); + return withLatestFrom(new ObservableSource[] { o1, o2 }, f); + } + + /** + * Combines the value emission from this ObservableSource with the latest emissions from the + * other ObservableSources via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this ObservableSource emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + *

+ * + *

+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first other source's value type + * @param the second other source's value type + * @param the third other source's value type + * @param the result value type + * @param o1 the first other ObservableSource + * @param o2 the second other ObservableSource + * @param o3 the third other ObservableSource + * @param combiner the function called with an array of values from each participating ObservableSource + * @return the new ObservableSource instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable withLatestFrom( + ObservableSource o1, ObservableSource o2, + ObservableSource o3, + Function4 combiner) { + ObjectHelper.requireNonNull(o1, "o1 is null"); + ObjectHelper.requireNonNull(o2, "o2 is null"); + ObjectHelper.requireNonNull(o3, "o3 is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + Function f = Functions.toFunction(combiner); + return withLatestFrom(new ObservableSource[] { o1, o2, o3 }, f); + } + + /** + * Combines the value emission from this ObservableSource with the latest emissions from the + * other ObservableSources via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this ObservableSource emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + *

+ * + *

+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first other source's value type + * @param the second other source's value type + * @param the third other source's value type + * @param the fourth other source's value type + * @param the result value type + * @param o1 the first other ObservableSource + * @param o2 the second other ObservableSource + * @param o3 the third other ObservableSource + * @param o4 the fourth other ObservableSource + * @param combiner the function called with an array of values from each participating ObservableSource + * @return the new ObservableSource instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable withLatestFrom( + ObservableSource o1, ObservableSource o2, + ObservableSource o3, ObservableSource o4, + Function5 combiner) { + ObjectHelper.requireNonNull(o1, "o1 is null"); + ObjectHelper.requireNonNull(o2, "o2 is null"); + ObjectHelper.requireNonNull(o3, "o3 is null"); + ObjectHelper.requireNonNull(o4, "o4 is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + Function f = Functions.toFunction(combiner); + return withLatestFrom(new ObservableSource[] { o1, o2, o3, o4 }, f); + } + + /** + * Combines the value emission from this ObservableSource with the latest emissions from the + * other ObservableSources via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this ObservableSource emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + *

+ * + *

+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param others the array of other sources + * @param combiner the function called with an array of values from each participating ObservableSource + * @return the new ObservableSource instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable withLatestFrom(ObservableSource[] others, Function combiner) { + ObjectHelper.requireNonNull(others, "others is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + return RxJavaPlugins.onAssembly(new ObservableWithLatestFromMany(this, others, combiner)); + } + + /** + * Combines the value emission from this ObservableSource with the latest emissions from the + * other ObservableSources via a function to produce the output item. + * + *

Note that this operator doesn't emit anything until all other sources have produced at + * least one value. The resulting emission only happens when this ObservableSource emits (and + * not when any of the other sources emit, unlike combineLatest). + * If a source doesn't produce any value and just completes, the sequence is completed immediately. + *

+ * + *

+ *
Scheduler:
+ *
This operator does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param others the iterable of other sources + * @param combiner the function called with an array of values from each participating ObservableSource + * @return the new ObservableSource instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable withLatestFrom(Iterable> others, Function combiner) { + ObjectHelper.requireNonNull(others, "others is null"); + ObjectHelper.requireNonNull(combiner, "combiner is null"); + return RxJavaPlugins.onAssembly(new ObservableWithLatestFromMany(this, others, combiner)); + } + + /** + * Returns an Observable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source ObservableSource and a specified Iterable sequence. + *

+ * + *

+ * Note that the {@code other} Iterable is evaluated as items are observed from the source ObservableSource; it is + * not pre-consumed. This allows you to zip infinite streams on either side. + *

+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items in the {@code other} Iterable + * @param + * the type of items emitted by the resulting ObservableSource + * @param other + * the Iterable sequence + * @param zipper + * a function that combines the pairs of items from the ObservableSource and the Iterable to generate + * the items to be emitted by the resulting ObservableSource + * @return an Observable that pairs up values from the source ObservableSource and the {@code other} Iterable + * sequence and emits the results of {@code zipFunction} applied to these pairs + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable zipWith(Iterable other, BiFunction zipper) { + ObjectHelper.requireNonNull(other, "other is null"); + ObjectHelper.requireNonNull(zipper, "zipper is null"); + return RxJavaPlugins.onAssembly(new ObservableZipIterable(this, other, zipper)); + } + + /** + * Returns an Observable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source ObservableSource and another specified ObservableSource. + *

+ * + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

range(1, 5).doOnComplete(action1).zipWith(range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the {@code other} ObservableSource + * @param + * the type of items emitted by the resulting ObservableSource + * @param other + * the other ObservableSource + * @param zipper + * a function that combines the pairs of items from the two ObservableSources to generate the items to + * be emitted by the resulting ObservableSource + * @return an Observable that pairs up values from the source ObservableSource and the {@code other} ObservableSource + * and emits the results of {@code zipFunction} applied to these pairs + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable zipWith(ObservableSource other, + BiFunction zipper) { + ObjectHelper.requireNonNull(other, "other is null"); + return zip(this, other, zipper); + } + + /** + * Returns an Observable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source ObservableSource and another specified ObservableSource. + *

+ * + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

range(1, 5).doOnComplete(action1).zipWith(range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the {@code other} ObservableSource + * @param + * the type of items emitted by the resulting ObservableSource + * @param other + * the other ObservableSource + * @param zipper + * a function that combines the pairs of items from the two ObservableSources to generate the items to + * be emitted by the resulting ObservableSource + * @param delayError + * if true, errors from the current Observable or the other ObservableSource is delayed until both terminate + * @return an Observable that pairs up values from the source ObservableSource and the {@code other} ObservableSource + * and emits the results of {@code zipFunction} applied to these pairs + * @see ReactiveX operators documentation: Zip + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable zipWith(ObservableSource other, + BiFunction zipper, boolean delayError) { + return zip(this, other, zipper, delayError); + } + + /** + * Returns an Observable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source ObservableSource and another specified ObservableSource. + *

+ * + *

+ * The operator subscribes to its sources in order they are specified and completes eagerly if + * one of the sources is shorter than the rest while disposing the other sources. Therefore, it + * is possible those other sources will never be able to run to completion (and thus not calling + * {@code doOnComplete()}). This can also happen if the sources are exactly the same length; if + * source A completes and B has been consumed and is about to complete, the operator detects A won't + * be sending further values and it will dispose B immediately. For example: + *

range(1, 5).doOnComplete(action1).zipWith(range(6, 5).doOnComplete(action2), (a, b) -> a + b)
+ * {@code action1} will be called but {@code action2} won't. + *
To work around this termination property, + * use {@link #doOnDispose(Action)} as well or use {@code using()} to do cleanup in case of completion + * or a dispose() call. + *
+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the {@code other} ObservableSource + * @param + * the type of items emitted by the resulting ObservableSource + * @param other + * the other ObservableSource + * @param zipper + * a function that combines the pairs of items from the two ObservableSources to generate the items to + * be emitted by the resulting ObservableSource + * @param bufferSize + * the capacity hint for the buffer in the inner windows + * @param delayError + * if true, errors from the current Observable or the other ObservableSource is delayed until both terminate + * @return an Observable that pairs up values from the source ObservableSource and the {@code other} ObservableSource + * and emits the results of {@code zipFunction} applied to these pairs + * @see ReactiveX operators documentation: Zip + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable zipWith(ObservableSource other, + BiFunction zipper, boolean delayError, int bufferSize) { + return zip(this, other, zipper, delayError, bufferSize); + } + + // ------------------------------------------------------------------------- + // Fluent test support, super handy and reduces test preparation boilerplate + // ------------------------------------------------------------------------- + /** + * Creates a TestObserver and subscribes + * it to this Observable. + *
+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new TestObserver instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final TestObserver test() { // NoPMD + TestObserver to = new TestObserver(); + subscribe(to); + return to; + } + + /** + * Creates a TestObserver, optionally disposes it and then subscribes + * it to this Observable. + * + *
+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param dispose dispose the TestObserver before it is subscribed to this Observable? + * @return the new TestObserver instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final TestObserver test(boolean dispose) { // NoPMD + TestObserver to = new TestObserver(); + if (dispose) { + to.dispose(); + } + subscribe(to); + return to; + } +} diff --git a/src/main/java/io/reactivex/ObservableConverter.java b/src/main/java/io/reactivex/ObservableConverter.java new file mode 100755 index 0000000..12c4615 --- /dev/null +++ b/src/main/java/io/reactivex/ObservableConverter.java @@ -0,0 +1,35 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Observable#as} operator to turn an Observable into another + * value fluently. + *

History: 2.1.7 - experimental + * @param the upstream type + * @param the output type + * @since 2.2 + */ +public interface ObservableConverter { + /** + * Applies a function to the upstream Observable and returns a converted value of type {@code R}. + * + * @param upstream the upstream Observable instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Observable upstream); +} diff --git a/src/main/java/io/reactivex/ObservableEmitter.java b/src/main/java/io/reactivex/ObservableEmitter.java new file mode 100755 index 0000000..9faccf5 --- /dev/null +++ b/src/main/java/io/reactivex/ObservableEmitter.java @@ -0,0 +1,96 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Cancellable; + +/** + * Abstraction over an RxJava {@link Observer} that allows associating + * a resource with it. + *

+ * The {@link #onNext(Object)}, {@link #onError(Throwable)}, {@link #tryOnError(Throwable)} + * and {@link #onComplete()} methods should be called in a sequential manner, just like the + * {@link Observer}'s methods should be. + * Use the {@code ObservableEmitter} the {@link #serialize()} method returns instead of the original + * {@code ObservableEmitter} instance provided by the generator routine if you want to ensure this. + * The other methods are thread-safe. + *

+ * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls {@link #onError(Throwable)}, + * {@link #onComplete()} or when {@link #tryOnError(Throwable)} succeeds. + *

+ * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + *

+ * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * + * @param the value type to emit + */ +public interface ObservableEmitter extends Emitter { + + /** + * Sets a Disposable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param d the disposable, null is allowed + */ + void setDisposable(@Nullable Disposable d); + + /** + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param c the cancellable resource, null is allowed + */ + void setCancellable(@Nullable Cancellable c); + + /** + * Returns true if the downstream disposed the sequence or the + * emitter was terminated via {@link #onError(Throwable)}, {@link #onComplete} or a + * successful {@link #tryOnError(Throwable)}. + *

This method is thread-safe. + * @return true if the downstream disposed the sequence or the emitter was terminated + */ + boolean isDisposed(); + + /** + * Ensures that calls to onNext, onError and onComplete are properly serialized. + * @return the serialized ObservableEmitter + */ + @NonNull + ObservableEmitter serialize(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

+ * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); +} diff --git a/src/main/java/io/reactivex/ObservableOnSubscribe.java b/src/main/java/io/reactivex/ObservableOnSubscribe.java new file mode 100755 index 0000000..bce34e1 --- /dev/null +++ b/src/main/java/io/reactivex/ObservableOnSubscribe.java @@ -0,0 +1,33 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * A functional interface that has a {@code subscribe()} method that receives + * an instance of an {@link ObservableEmitter} instance that allows pushing + * events in a cancellation-safe manner. + * + * @param the value type pushed + */ +public interface ObservableOnSubscribe { + + /** + * Called for each Observer that subscribes. + * @param emitter the safe emitter instance, never null + * @throws Exception on error + */ + void subscribe(@NonNull ObservableEmitter emitter) throws Exception; +} + diff --git a/src/main/java/io/reactivex/ObservableOperator.java b/src/main/java/io/reactivex/ObservableOperator.java new file mode 100755 index 0000000..18ec3ec --- /dev/null +++ b/src/main/java/io/reactivex/ObservableOperator.java @@ -0,0 +1,33 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Interface to map/wrap a downstream observer to an upstream observer. + * + * @param the value type of the downstream + * @param the value type of the upstream + */ +public interface ObservableOperator { + /** + * Applies a function to the child Observer and returns a new parent Observer. + * @param observer the child Observer instance + * @return the parent Observer instance + * @throws Exception on failure + */ + @NonNull + Observer apply(@NonNull Observer observer) throws Exception; +} diff --git a/src/main/java/io/reactivex/ObservableSource.java b/src/main/java/io/reactivex/ObservableSource.java new file mode 100755 index 0000000..ea0b164 --- /dev/null +++ b/src/main/java/io/reactivex/ObservableSource.java @@ -0,0 +1,32 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Represents a basic, non-backpressured {@link Observable} source base interface, + * consumable via an {@link Observer}. + * + * @param the element type + * @since 2.0 + */ +public interface ObservableSource { + + /** + * Subscribes the given Observer to this ObservableSource instance. + * @param observer the Observer, not null + * @throws NullPointerException if {@code observer} is null + */ + void subscribe(@NonNull Observer observer); +} diff --git a/src/main/java/io/reactivex/ObservableTransformer.java b/src/main/java/io/reactivex/ObservableTransformer.java new file mode 100755 index 0000000..a7f20eb --- /dev/null +++ b/src/main/java/io/reactivex/ObservableTransformer.java @@ -0,0 +1,33 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Interface to compose Observables. + * + * @param the upstream value type + * @param the downstream value type + */ +public interface ObservableTransformer { + /** + * Applies a function to the upstream Observable and returns an ObservableSource with + * optionally different element type. + * @param upstream the upstream Observable instance + * @return the transformed ObservableSource instance + */ + @NonNull + ObservableSource apply(@NonNull Observable upstream); +} diff --git a/src/main/java/io/reactivex/Observer.java b/src/main/java/io/reactivex/Observer.java new file mode 100755 index 0000000..0e85ba2 --- /dev/null +++ b/src/main/java/io/reactivex/Observer.java @@ -0,0 +1,119 @@ +/** + * 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; + +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; + +/** + * Provides a mechanism for receiving push-based notifications. + *

+ * When an {@code Observer} is subscribed to an {@link ObservableSource} through the {@link ObservableSource#subscribe(Observer)} method, + * the {@code ObservableSource} calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows + * disposing the sequence at any time, then the + * {@code ObservableSource} may call the Observer's {@link #onNext} method any number of times + * to provide notifications. A well-behaved + * {@code ObservableSource} will call an {@code Observer}'s {@link #onComplete} method exactly once or the {@code Observer}'s + * {@link #onError} method exactly once. + *

+ * Calling the {@code Observer}'s method must happen in a serialized fashion, that is, they must not + * be invoked concurrently by multiple threads in an overlapping fashion and the invocation pattern must + * adhere to the following protocol: + *

    onSubscribe onNext* (onError | onComplete)?
+ *

+ * Subscribing an {@code Observer} to multiple {@code ObservableSource}s is not recommended. If such reuse + * happens, it is the duty of the {@code Observer} implementation to be ready to receive multiple calls to + * its methods and ensure proper concurrent behavior of its business logic. + *

+ * Calling {@link #onSubscribe(Disposable)}, {@link #onNext(Object)} or {@link #onError(Throwable)} with a + * {@code null} argument is forbidden. + *

+ * The implementations of the {@code onXXX} methods should avoid throwing runtime exceptions other than the following cases + * (see Rule 2.13 of the Reactive Streams specification): + *

    + *
  • If the argument is {@code null}, the methods can throw a {@code NullPointerException}. + * Note though that RxJava prevents {@code null}s to enter into the flow and thus there is generally no + * need to check for nulls in flows assembled from standard sources and intermediate operators. + *
  • + *
  • If there is a fatal error (such as {@code VirtualMachineError}).
  • + *
+ *

+ * Violating Rule 2.13 results in undefined flow behavior. Generally, the following can happen: + *

    + *
  • An upstream operator turns it into an {@link #onError} call.
  • + *
  • If the flow is synchronous, the {@link ObservableSource#subscribe(Observer)} throws instead of returning normally.
  • + *
  • If the flow is asynchronous, the exception propagates up to the component ({@link Scheduler} or {@link java.util.concurrent.Executor}) + * providing the asynchronous boundary the code is running and either routes the exception to the global + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)} handler or the current thread's + * {@link Thread.UncaughtExceptionHandler#uncaughtException(Thread, Throwable)} handler.
  • + *
+ * From the {@code Observable}'s perspective, an {@code Observer} is the end consumer thus it is the {@code Observer}'s + * responsibility to handle the error case and signal it "further down". This means unreliable code in the {@code onXXX} + * methods should be wrapped into `try-catch`es, specifically in {@link #onError(Throwable)} or {@link #onComplete()}, and handled there + * (for example, by logging it or presenting the user with an error dialog). However, if the error would be thrown from + * {@link #onNext(Object)}, Rule 2.13 mandates + * the implementation calls {@link Disposable#dispose()} and signals the exception in a way that is adequate to the target context, + * for example, by calling {@link #onError(Throwable)} on the same {@code Observer} instance. + *

+ * If, for some reason, the {@code Observer} won't follow Rule 2.13, the {@link Observable#safeSubscribe(Observer)} can wrap it + * with the necessary safeguards and route exceptions thrown from {@code onNext} into {@code onError} and route exceptions thrown + * from {@code onError} and {@code onComplete} into the global error handler via {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * @see ReactiveX documentation: Observable + * @param + * the type of item the Observer expects to observe + */ +public interface Observer { + + /** + * Provides the Observer with the means of cancelling (disposing) the + * connection (channel) with the Observable in both + * synchronous (from within {@link #onNext(Object)}) and asynchronous manner. + * @param d the Disposable instance whose {@link Disposable#dispose()} can + * be called anytime to cancel the connection + * @since 2.0 + */ + void onSubscribe(@NonNull Disposable d); + + /** + * Provides the Observer with a new item to observe. + *

+ * The {@link Observable} may call this method 0 or more times. + *

+ * The {@code Observable} will not call this method again after it calls either {@link #onComplete} or + * {@link #onError}. + * + * @param t + * the item emitted by the Observable + */ + void onNext(@NonNull T t); + + /** + * Notifies the Observer that the {@link Observable} has experienced an error condition. + *

+ * If the {@link Observable} calls this method, it will not thereafter call {@link #onNext} or + * {@link #onComplete}. + * + * @param e + * the exception encountered by the Observable + */ + void onError(@NonNull Throwable e); + + /** + * Notifies the Observer that the {@link Observable} has finished sending push-based notifications. + *

+ * The {@link Observable} will not call this method if it calls {@link #onError}. + */ + void onComplete(); + +} diff --git a/src/main/java/io/reactivex/Scheduler.java b/src/main/java/io/reactivex/Scheduler.java new file mode 100755 index 0000000..545e223 --- /dev/null +++ b/src/main/java/io/reactivex/Scheduler.java @@ -0,0 +1,634 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.schedulers.*; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.SchedulerRunnableIntrospection; + +import java.util.concurrent.TimeUnit; + +/** + * A {@code Scheduler} is an object that specifies an API for scheduling + * units of work provided in the form of {@link Runnable}s to be + * executed without delay (effectively as soon as possible), after a specified time delay or periodically + * and represents an abstraction over an asynchronous boundary that ensures + * these units of work get executed by some underlying task-execution scheme + * (such as custom Threads, event loop, {@link java.util.concurrent.Executor Executor} or Actor system) + * with some uniform properties and guarantees regardless of the particular underlying + * scheme. + *

+ * You can get various standard, RxJava-specific instances of this class via + * the static methods of the {@link io.reactivex.schedulers.Schedulers} utility class. + *

+ * The so-called {@link Worker}s of a {@code Scheduler} can be created via the {@link #createWorker()} method which allow the scheduling + * of multiple {@link Runnable} tasks in an isolated manner. {@code Runnable} tasks scheduled on a {@code Worker} are guaranteed to be + * executed sequentially and in a non-overlapping fashion. Non-delayed {@code Runnable} tasks are guaranteed to execute in a + * First-In-First-Out order but their execution may be interleaved with delayed tasks. + * In addition, outstanding or running tasks can be cancelled together via + * {@link Worker#dispose()} without affecting any other {@code Worker} instances of the same {@code Scheduler}. + *

+ * Implementations of the {@link #scheduleDirect} and {@link Worker#schedule} methods are encouraged to call the {@link RxJavaPlugins#onSchedule(Runnable)} + * method to allow a scheduler hook to manipulate (wrap or replace) the original {@code Runnable} task before it is submitted to the + * underlying task-execution scheme. + *

+ * The default implementations of the {@code scheduleDirect} methods provided by this abstract class + * delegate to the respective {@code schedule} methods in the {@link Worker} instance created via {@link #createWorker()} + * for each individual {@link Runnable} task submitted. Implementors of this class are encouraged to provide + * a more efficient direct scheduling implementation to avoid the time and memory overhead of creating such {@code Worker}s + * for every task. + * This delegation is done via special wrapper instances around the original {@code Runnable} before calling the respective + * {@code Worker.schedule} method. Note that this can lead to multiple {@code RxJavaPlugins.onSchedule} calls and potentially + * multiple hooks applied. Therefore, the default implementations of {@code scheduleDirect} (and the {@link Worker#schedulePeriodically(Runnable, long, long, TimeUnit)}) + * wrap the incoming {@code Runnable} into a class that implements the {@link SchedulerRunnableIntrospection} + * interface which can grant access to the original or hooked {@code Runnable}, thus, a repeated {@code RxJavaPlugins.onSchedule} + * can detect the earlier hook and not apply a new one over again. + *

+ * The default implementation of {@link #now(TimeUnit)} and {@link Worker#now(TimeUnit)} methods to return current {@link System#currentTimeMillis()} + * value in the desired time unit, unless {@code rx2.scheduler.use-nanotime} (boolean) is set. When the property is set to + * {@code true}, the method uses {@link System#nanoTime()} as its basis instead. Custom {@code Scheduler} implementations can override this + * to provide specialized time accounting (such as virtual time to be advanced programmatically). + * Note that operators requiring a {@code Scheduler} may rely on either of the {@code now()} calls provided by + * {@code Scheduler} or {@code Worker} respectively, therefore, it is recommended they represent a logically + * consistent source of the current time. + *

+ * The default implementation of the {@link Worker#schedulePeriodically(Runnable, long, long, TimeUnit)} method uses + * the {@link Worker#schedule(Runnable, long, TimeUnit)} for scheduling the {@code Runnable} task periodically. + * The algorithm calculates the next absolute time when the task should run again and schedules this execution + * based on the relative time between it and {@link Worker#now(TimeUnit)}. However, drifts or changes in the + * system clock could affect this calculation either by scheduling subsequent runs too frequently or too far apart. + * Therefore, the default implementation uses the {@link #clockDriftTolerance()} value (set via + * {@code rx2.scheduler.drift-tolerance} in minutes) to detect a drift in {@link Worker#now(TimeUnit)} and + * re-adjust the absolute/relative time calculation accordingly. + *

+ * The default implementations of {@link #start()} and {@link #shutdown()} do nothing and should be overridden if the + * underlying task-execution scheme supports stopping and restarting itself. + *

+ * If the {@code Scheduler} is shut down or a {@code Worker} is disposed, the {@code schedule} methods + * should return the {@link io.reactivex.disposables.Disposables#disposed()} singleton instance indicating the shut down/disposed + * state to the caller. Since the shutdown or dispose can happen from any thread, the {@code schedule} implementations + * should make best effort to cancel tasks immediately after those tasks have been submitted to the + * underlying task-execution scheme if the shutdown/dispose was detected after this submission. + *

+ * All methods on the {@code Scheduler} and {@code Worker} classes should be thread safe. + */ +public abstract class Scheduler { + /** + * Value representing whether to use {@link System#nanoTime()}, or default as clock for {@link #now(TimeUnit)} + * and {@link Worker#now(TimeUnit)} + *

+ * Associated system parameter: + *

    + *
  • {@code rx2.scheduler.use-nanotime}, boolean, default {@code false} + *
+ */ + static boolean IS_DRIFT_USE_NANOTIME = Boolean.getBoolean("rx2.scheduler.use-nanotime"); + + /** + * Returns the current clock time depending on state of {@link Scheduler#IS_DRIFT_USE_NANOTIME} in given {@code unit} + *

+ * By default {@link System#currentTimeMillis()} will be used as the clock. When the property is set + * {@link System#nanoTime()} will be used. + *

+ * @param unit the time unit + * @return the 'current time' in given unit + * @throws NullPointerException if {@code unit} is {@code null} + */ + static long computeNow(TimeUnit unit) { + if(!IS_DRIFT_USE_NANOTIME) { + return unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } + return unit.convert(System.nanoTime(), TimeUnit.NANOSECONDS); + } + + /** + * The tolerance for a clock drift in nanoseconds where the periodic scheduler will rebase. + *

+ * The associated system parameter, {@code rx2.scheduler.drift-tolerance}, expects its value in minutes. + */ + static final long CLOCK_DRIFT_TOLERANCE_NANOSECONDS; + static { + CLOCK_DRIFT_TOLERANCE_NANOSECONDS = TimeUnit.MINUTES.toNanos( + Long.getLong("rx2.scheduler.drift-tolerance", 15)); + } + + /** + * Returns the clock drift tolerance in nanoseconds. + *

Related system property: {@code rx2.scheduler.drift-tolerance} in minutes. + * @return the tolerance in nanoseconds + * @since 2.0 + */ + public static long clockDriftTolerance() { + return CLOCK_DRIFT_TOLERANCE_NANOSECONDS; + } + + /** + * Retrieves or creates a new {@link Worker} that represents sequential execution of actions. + *

+ * When work is completed, the {@code Worker} instance should be released + * by calling {@link Worker#dispose()} to avoid potential resource leaks in the + * underlying task-execution scheme. + *

+ * Work on a {@link Worker} is guaranteed to be sequential and non-overlapping. + * + * @return a Worker representing a serial queue of actions to be executed + */ + @NonNull + public abstract Worker createWorker(); + + /** + * Returns the 'current time' of the Scheduler in the specified time unit. + * @param unit the time unit + * @return the 'current time' + * @since 2.0 + */ + public long now(@NonNull TimeUnit unit) { + return computeNow(unit); + } + + /** + * Allows the Scheduler instance to start threads + * and accept tasks on them. + *

+ * Implementations should make sure the call is idempotent, thread-safe and + * should not throw any {@code RuntimeException} if it doesn't support this + * functionality. + * + * @since 2.0 + */ + public void start() { + + } + + /** + * Instructs the Scheduler instance to stop threads, + * stop accepting tasks on any outstanding {@link Worker} instances + * and clean up any associated resources with this Scheduler. + *

+ * Implementations should make sure the call is idempotent, thread-safe and + * should not throw any {@code RuntimeException} if it doesn't support this + * functionality. + * @since 2.0 + */ + public void shutdown() { + + } + + /** + * Schedules the given task on this Scheduler without any time delay. + * + *

+ * This method is safe to be called from multiple threads but there are no + * ordering or non-overlapping guarantees between tasks. + * + * @param run the task to execute + * + * @return the Disposable instance that let's one cancel this particular task. + * @since 2.0 + */ + @NonNull + public Disposable scheduleDirect(@NonNull Runnable run) { + return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS); + } + + /** + * Schedules the execution of the given task with the given time delay. + * + *

+ * This method is safe to be called from multiple threads but there are no + * ordering guarantees between tasks. + * + * @param run the task to schedule + * @param delay the delay amount, non-positive values indicate non-delayed scheduling + * @param unit the unit of measure of the delay amount + * @return the Disposable that let's one cancel this particular delayed task. + * @since 2.0 + */ + @NonNull + public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) { + final Worker w = createWorker(); + + final Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + + DisposeTask task = new DisposeTask(decoratedRun, w); + + w.schedule(task, delay, unit); + + return task; + } + + /** + * Schedules a periodic execution of the given task with the given initial time delay and repeat period. + * + *

+ * This method is safe to be called from multiple threads but there are no + * ordering guarantees between tasks. + * + *

+ * The periodic execution is at a fixed rate, that is, the first execution will be after the + * {@code initialDelay}, the second after {@code initialDelay + period}, the third after + * {@code initialDelay + 2 * period}, and so on. + * + * @param run the task to schedule + * @param initialDelay the initial delay amount, non-positive values indicate non-delayed scheduling + * @param period the period at which the task should be re-executed + * @param unit the unit of measure of the delay amount + * @return the Disposable that let's one cancel this particular delayed task. + * @since 2.0 + */ + @NonNull + public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initialDelay, long period, @NonNull TimeUnit unit) { + final Worker w = createWorker(); + + final Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + + PeriodicDirectTask periodicTask = new PeriodicDirectTask(decoratedRun, w); + + Disposable d = w.schedulePeriodically(periodicTask, initialDelay, period, unit); + if (d == EmptyDisposable.INSTANCE) { + return d; + } + + return periodicTask; + } + + /** + * Allows the use of operators for controlling the timing around when + * actions scheduled on workers are actually done. This makes it possible to + * layer additional behavior on this {@link Scheduler}. The only parameter + * is a function that flattens an {@link Flowable} of {@link Flowable} + * of {@link Completable}s into just one {@link Completable}. There must be + * a chain of operators connecting the returned value to the source + * {@link Flowable} otherwise any work scheduled on the returned + * {@link Scheduler} will not be executed. + *

+ * When {@link Scheduler#createWorker()} is invoked a {@link Flowable} of + * {@link Completable}s is onNext'd to the combinator to be flattened. If + * the inner {@link Flowable} is not immediately subscribed to an calls to + * {@link Worker#schedule} are buffered. Once the {@link Flowable} is + * subscribed to actions are then onNext'd as {@link Completable}s. + *

+ * Finally the actions scheduled on the parent {@link Scheduler} when the + * inner most {@link Completable}s are subscribed to. + *

+ * When the {@link Worker} is unsubscribed the {@link Completable} emits an + * onComplete and triggers any behavior in the flattening operator. The + * {@link Flowable} and all {@link Completable}s give to the flattening + * function never onError. + *

+ * Limit the amount concurrency two at a time without creating a new fix + * size thread pool: + * + *

+     * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
+     *  // use merge max concurrent to limit the number of concurrent
+     *  // callbacks two at a time
+     *  return Completable.merge(Flowable.merge(workers), 2);
+     * });
+     * 
+ *

+ * This is a slightly different way to limit the concurrency but it has some + * interesting benefits and drawbacks to the method above. It works by + * limited the number of concurrent {@link Worker}s rather than individual + * actions. Generally each {@link Flowable} uses its own {@link Worker}. + * This means that this will essentially limit the number of concurrent + * subscribes. The danger comes from using operators like + * {@link Flowable#zip(org.reactivestreams.Publisher, org.reactivestreams.Publisher, io.reactivex.functions.BiFunction)} where + * subscribing to the first {@link Flowable} could deadlock the + * subscription to the second. + * + *

+     * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
+     *  // use merge max concurrent to limit the number of concurrent
+     *  // Flowables two at a time
+     *  return Completable.merge(Flowable.merge(workers, 2));
+     * });
+     * 
+ * + * Slowing down the rate to no more than than 1 a second. This suffers from + * the same problem as the one above I could find an {@link Flowable} + * operator that limits the rate without dropping the values (aka leaky + * bucket algorithm). + * + *
+     * Scheduler slowScheduler = Schedulers.computation().when(workers -> {
+     *  // use concatenate to make each worker happen one at a time.
+     *  return Completable.concat(workers.map(actions -> {
+     *      // delay the starting of the next worker by 1 second.
+     *      return Completable.merge(actions.delaySubscription(1, TimeUnit.SECONDS));
+     *  }));
+     * });
+     * 
+ * + *

History: 2.0.1 - experimental + * @param a Scheduler and a Subscription + * @param combine the function that takes a two-level nested Flowable sequence of a Completable and returns + * the Completable that will be subscribed to and should trigger the execution of the scheduled Actions. + * @return the Scheduler with the customized execution behavior + * @since 2.1 + */ + @SuppressWarnings("unchecked") + @NonNull + public S when(@NonNull Function>, Completable> combine) { + return (S) new SchedulerWhen(combine, this); + } + + /** + * Represents an isolated, sequential worker of a parent Scheduler for executing {@code Runnable} tasks on + * an underlying task-execution scheme (such as custom Threads, event loop, {@link java.util.concurrent.Executor Executor} or Actor system). + *

+ * Disposing the {@link Worker} should cancel all outstanding work and allows resource cleanup. + *

+ * The default implementations of {@link #schedule(Runnable)} and {@link #schedulePeriodically(Runnable, long, long, TimeUnit)} + * delegate to the abstract {@link #schedule(Runnable, long, TimeUnit)} method. Its implementation is encouraged to + * track the individual {@code Runnable} tasks while they are waiting to be executed (with or without delay) so that + * {@link #dispose()} can prevent their execution or potentially interrupt them if they are currently running. + *

+ * The default implementation of the {@link #now(TimeUnit)} method returns current {@link System#currentTimeMillis()} + * value in the desired time unit, unless {@code rx2.scheduler.use-nanotime} (boolean) is set. When the property is set to + * {@code true}, the method uses {@link System#nanoTime()} as its basis instead. Custom {@code Worker} implementations can override this + * to provide specialized time accounting (such as virtual time to be advanced programmatically). + * Note that operators requiring a scheduler may rely on either of the {@code now()} calls provided by + * {@code Scheduler} or {@code Worker} respectively, therefore, it is recommended they represent a logically + * consistent source of the current time. + *

+ * The default implementation of the {@link #schedulePeriodically(Runnable, long, long, TimeUnit)} method uses + * the {@link #schedule(Runnable, long, TimeUnit)} for scheduling the {@code Runnable} task periodically. + * The algorithm calculates the next absolute time when the task should run again and schedules this execution + * based on the relative time between it and {@link #now(TimeUnit)}. However, drifts or changes in the + * system clock would affect this calculation either by scheduling subsequent runs too frequently or too far apart. + * Therefore, the default implementation uses the {@link #clockDriftTolerance()} value (set via + * {@code rx2.scheduler.drift-tolerance} in minutes) to detect a drift in {@link #now(TimeUnit)} and + * re-adjust the absolute/relative time calculation accordingly. + *

+ * If the {@code Worker} is disposed, the {@code schedule} methods + * should return the {@link io.reactivex.disposables.Disposables#disposed()} singleton instance indicating the disposed + * state to the caller. Since the {@link #dispose()} call can happen on any thread, the {@code schedule} implementations + * should make best effort to cancel tasks immediately after those tasks have been submitted to the + * underlying task-execution scheme if the dispose was detected after this submission. + *

+ * All methods on the {@code Worker} class should be thread safe. + */ + public abstract static class Worker implements Disposable { + /** + * Schedules a Runnable for execution without any time delay. + * + *

The default implementation delegates to {@link #schedule(Runnable, long, TimeUnit)}. + * + * @param run + * Runnable to schedule + * @return a Disposable to be able to unsubscribe the action (cancel it if not executed) + */ + @NonNull + public Disposable schedule(@NonNull Runnable run) { + return schedule(run, 0L, TimeUnit.NANOSECONDS); + } + + /** + * Schedules an Runnable for execution at some point in the future specified by a time delay + * relative to the current time. + *

+ * Note to implementors: non-positive {@code delayTime} should be regarded as non-delayed schedule, i.e., + * as if the {@link #schedule(Runnable)} was called. + * + * @param run + * the Runnable to schedule + * @param delay + * time to "wait" before executing the action; non-positive values indicate an non-delayed + * schedule + * @param unit + * the time unit of {@code delayTime} + * @return a Disposable to be able to unsubscribe the action (cancel it if not executed) + */ + @NonNull + public abstract Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit unit); + + /** + * Schedules a periodic execution of the given task with the given initial time delay and repeat period. + *

+ * The default implementation schedules and reschedules the {@code Runnable} task via the + * {@link #schedule(Runnable, long, TimeUnit)} + * method over and over and at a fixed rate, that is, the first execution will be after the + * {@code initialDelay}, the second after {@code initialDelay + period}, the third after + * {@code initialDelay + 2 * period}, and so on. + *

+ * Note to implementors: non-positive {@code initialTime} and {@code period} should be regarded as + * non-delayed scheduling of the first and any subsequent executions. + * In addition, a more specific {@code Worker} implementation should override this method + * if it can perform the periodic task execution with less overhead (such as by avoiding the + * creation of the wrapper and tracker objects upon each periodic invocation of the + * common {@link #schedule(Runnable, long, TimeUnit)} method). + * + * @param run + * the Runnable to execute periodically + * @param initialDelay + * time to wait before executing the action for the first time; non-positive values indicate + * an non-delayed schedule + * @param period + * the time interval to wait each time in between executing the action; non-positive values + * indicate no delay between repeated schedules + * @param unit + * the time unit of {@code period} + * @return a Disposable to be able to unsubscribe the action (cancel it if not executed) + */ + @NonNull + public Disposable schedulePeriodically(@NonNull Runnable run, final long initialDelay, final long period, @NonNull final TimeUnit unit) { + final SequentialDisposable first = new SequentialDisposable(); + + final SequentialDisposable sd = new SequentialDisposable(first); + + final Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + + final long periodInNanoseconds = unit.toNanos(period); + final long firstNowNanoseconds = now(TimeUnit.NANOSECONDS); + final long firstStartInNanoseconds = firstNowNanoseconds + unit.toNanos(initialDelay); + + Disposable d = schedule(new PeriodicTask(firstStartInNanoseconds, decoratedRun, firstNowNanoseconds, sd, + periodInNanoseconds), initialDelay, unit); + + if (d == EmptyDisposable.INSTANCE) { + return d; + } + first.replace(d); + + return sd; + } + + /** + * Returns the 'current time' of the Worker in the specified time unit. + * @param unit the time unit + * @return the 'current time' + * @since 2.0 + */ + public long now(@NonNull TimeUnit unit) { + return computeNow(unit); + } + + /** + * Holds state and logic to calculate when the next delayed invocation + * of this task has to happen (accounting for clock drifts). + */ + final class PeriodicTask implements Runnable, SchedulerRunnableIntrospection { + @NonNull + final Runnable decoratedRun; + @NonNull + final SequentialDisposable sd; + final long periodInNanoseconds; + long count; + long lastNowNanoseconds; + long startInNanoseconds; + + PeriodicTask(long firstStartInNanoseconds, @NonNull Runnable decoratedRun, + long firstNowNanoseconds, @NonNull SequentialDisposable sd, long periodInNanoseconds) { + this.decoratedRun = decoratedRun; + this.sd = sd; + this.periodInNanoseconds = periodInNanoseconds; + lastNowNanoseconds = firstNowNanoseconds; + startInNanoseconds = firstStartInNanoseconds; + } + + @Override + public void run() { + decoratedRun.run(); + + if (!sd.isDisposed()) { + + long nextTick; + + long nowNanoseconds = now(TimeUnit.NANOSECONDS); + // If the clock moved in a direction quite a bit, rebase the repetition period + if (nowNanoseconds + CLOCK_DRIFT_TOLERANCE_NANOSECONDS < lastNowNanoseconds + || nowNanoseconds >= lastNowNanoseconds + periodInNanoseconds + CLOCK_DRIFT_TOLERANCE_NANOSECONDS) { + nextTick = nowNanoseconds + periodInNanoseconds; + /* + * Shift the start point back by the drift as if the whole thing + * started count periods ago. + */ + startInNanoseconds = nextTick - (periodInNanoseconds * (++count)); + } else { + nextTick = startInNanoseconds + (++count * periodInNanoseconds); + } + lastNowNanoseconds = nowNanoseconds; + + long delay = nextTick - nowNanoseconds; + sd.replace(schedule(this, delay, TimeUnit.NANOSECONDS)); + } + } + + @Override + public Runnable getWrappedRunnable() { + return this.decoratedRun; + } + } + } + + static final class PeriodicDirectTask + implements Disposable, Runnable, SchedulerRunnableIntrospection { + + @NonNull + final Runnable run; + + @NonNull + final Worker worker; + + volatile boolean disposed; + + PeriodicDirectTask(@NonNull Runnable run, @NonNull Worker worker) { + this.run = run; + this.worker = worker; + } + + @Override + public void run() { + if (!disposed) { + try { + run.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + worker.dispose(); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + } + + @Override + public void dispose() { + disposed = true; + worker.dispose(); + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public Runnable getWrappedRunnable() { + return run; + } + } + + static final class DisposeTask implements Disposable, Runnable, SchedulerRunnableIntrospection { + + @NonNull + final Runnable decoratedRun; + + @NonNull + final Worker w; + + @Nullable + Thread runner; + + DisposeTask(@NonNull Runnable decoratedRun, @NonNull Worker w) { + this.decoratedRun = decoratedRun; + this.w = w; + } + + @Override + public void run() { + runner = Thread.currentThread(); + try { + decoratedRun.run(); + } finally { + dispose(); + runner = null; + } + } + + @Override + public void dispose() { + if (runner == Thread.currentThread() && w instanceof NewThreadWorker) { + ((NewThreadWorker)w).shutdown(); + } else { + w.dispose(); + } + } + + @Override + public boolean isDisposed() { + return w.isDisposed(); + } + + @Override + public Runnable getWrappedRunnable() { + return this.decoratedRun; + } + } +} diff --git a/src/main/java/io/reactivex/Single.java b/src/main/java/io/reactivex/Single.java new file mode 100755 index 0000000..15ef6b6 --- /dev/null +++ b/src/main/java/io/reactivex/Single.java @@ -0,0 +1,4182 @@ +/** + * 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; + +import java.util.NoSuchElementException; +import java.util.concurrent.*; + +import org.reactivestreams.Publisher; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.observers.*; +import io.reactivex.internal.operators.completable.*; +import io.reactivex.internal.operators.flowable.*; +import io.reactivex.internal.operators.maybe.*; +import io.reactivex.internal.operators.mixed.*; +import io.reactivex.internal.operators.observable.*; +import io.reactivex.internal.operators.single.*; +import io.reactivex.internal.util.*; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; + +/** + * The {@code Single} class implements the Reactive Pattern for a single value response. + *

+ * {@code Single} behaves similarly to {@link Observable} except that it can only emit either a single successful + * value or an error (there is no "onComplete" notification as there is for an {@link Observable}). + *

+ * The {@code Single} class implements the {@link SingleSource} base interface and the default consumer + * type it interacts with is the {@link SingleObserver} via the {@link #subscribe(SingleObserver)} method. + *

+ * The {@code Single} operates with the following sequential protocol: + *

+ *     onSubscribe (onSuccess | onError)?
+ * 
+ *

+ * Note that {@code onSuccess} and {@code onError} are mutually exclusive events; unlike {@code Observable}, + * {@code onSuccess} is never followed by {@code onError}. + *

+ * Like {@code Observable}, a running {@code Single} can be stopped through the {@link Disposable} instance + * provided to consumers through {@link SingleObserver#onSubscribe}. + *

+ * Like an {@code Observable}, a {@code Single} is lazy, can be either "hot" or "cold", synchronous or + * asynchronous. {@code Single} instances returned by the methods of this class are cold + * and there is a standard hot implementation in the form of a subject: + * {@link io.reactivex.subjects.SingleSubject SingleSubject}. + *

+ * The documentation for this class makes use of marble diagrams. The following legend explains these diagrams: + *

+ * + *

+ * See {@link Flowable} or {@link Observable} for the + * implementation of the Reactive Pattern for a stream or vector of values. + *

+ * For more information see the ReactiveX + * documentation. + *

+ * Example: + *


+ * Disposable d = Single.just("Hello World")
+ *    .delay(10, TimeUnit.SECONDS, Schedulers.io())
+ *    .subscribeWith(new DisposableSingleObserver<String>() {
+ *        @Override
+ *        public void onStart() {
+ *            System.out.println("Started");
+ *        }
+ *
+ *        @Override
+ *        public void onSuccess(String value) {
+ *            System.out.println("Success: " + value);
+ *        }
+ *
+ *        @Override
+ *        public void onError(Throwable error) {
+ *            error.printStackTrace();
+ *        }
+ *    });
+ * 
+ * Thread.sleep(5000);
+ * 
+ * d.dispose();
+ * 
+ *

+ * Note that by design, subscriptions via {@link #subscribe(SingleObserver)} can't be disposed + * from the outside (hence the + * {@code void} return of the {@link #subscribe(SingleObserver)} method) and it is the + * responsibility of the implementor of the {@code SingleObserver} to allow this to happen. + * RxJava supports such usage with the standard + * {@link io.reactivex.observers.DisposableSingleObserver DisposableSingleObserver} instance. + * For convenience, the {@link #subscribeWith(SingleObserver)} method is provided as well to + * allow working with a {@code SingleObserver} (or subclass) instance to be applied with in + * a fluent manner (such as in the example above). + * @param + * the type of the item emitted by the Single + * @since 2.0 + * @see io.reactivex.observers.DisposableSingleObserver + */ +public abstract class Single implements SingleSource { + + /** + * Runs multiple SingleSources and signals the events of the first one that signals (disposing + * the rest). + *

+ * + *

+ *
Scheduler:
+ *
{@code amb} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the Iterable sequence of sources. A subscription to each source will + * occur in the same order as in this Iterable. + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single amb(final Iterable> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new SingleAmb(null, sources)); + } + + /** + * Runs multiple SingleSources and signals the events of the first one that signals (disposing + * the rest). + *

+ * + *

+ *
Scheduler:
+ *
{@code ambArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the array of sources. A subscription to each source will + * occur in the same order as in this array. + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single ambArray(final SingleSource... sources) { + if (sources.length == 0) { + return error(SingleInternalHelper.emptyThrower()); + } + if (sources.length == 1) { + return wrap((SingleSource)sources[0]); + } + return RxJavaPlugins.onAssembly(new SingleAmb(sources, null)); + } + + /** + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided by + * an Iterable sequence. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the Iterable sequence of SingleSource instances + * @return the new Flowable instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.FULL) + public static Flowable concat(Iterable> sources) { + return concat(Flowable.fromIterable(sources)); + } + + /** + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided by + * an Observable sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the ObservableSource of SingleSource instances + * @return the new Observable instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Observable concat(ObservableSource> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new ObservableConcatMap(sources, SingleInternalHelper.toObservable(), 2, ErrorMode.IMMEDIATE)); + } + + /** + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided by + * a Publisher sequence. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer + * and the sources {@code Publisher} is expected to honor it as well.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the Publisher of SingleSource instances + * @return the new Flowable instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concat(Publisher> sources) { + return concat(sources, 2); + } + + /** + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided by + * a Publisher sequence and prefetched by the specified amount. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer + * and the sources {@code Publisher} is expected to honor it as well.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the Publisher of SingleSource instances + * @param prefetch the number of SingleSources to prefetch from the Publisher + * @return the new Flowable instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Flowable concat(Publisher> sources, int prefetch) { + ObjectHelper.requireNonNull(sources, "sources is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new FlowableConcatMapPublisher(sources, SingleInternalHelper.toFlowable(), prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Returns a Flowable that emits the items emitted by two Singles, one after the other. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common value type + * @param source1 + * a Single to be concatenated + * @param source2 + * a Single to be concatenated + * @return a Flowable that emits items emitted by the two source Singles, one after the other. + * @see ReactiveX operators documentation: Concat + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable concat( + SingleSource source1, SingleSource source2 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return concat(Flowable.fromArray(source1, source2)); + } + + /** + * Returns a Flowable that emits the items emitted by three Singles, one after the other. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common value type + * @param source1 + * a Single to be concatenated + * @param source2 + * a Single to be concatenated + * @param source3 + * a Single to be concatenated + * @return a Flowable that emits items emitted by the three source Singles, one after the other. + * @see ReactiveX operators documentation: Concat + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable concat( + SingleSource source1, SingleSource source2, + SingleSource source3 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return concat(Flowable.fromArray(source1, source2, source3)); + } + + /** + * Returns a Flowable that emits the items emitted by four Singles, one after the other. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the common value type + * @param source1 + * a Single to be concatenated + * @param source2 + * a Single to be concatenated + * @param source3 + * a Single to be concatenated + * @param source4 + * a Single to be concatenated + * @return a Flowable that emits items emitted by the four source Singles, one after the other. + * @see ReactiveX operators documentation: Concat + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable concat( + SingleSource source1, SingleSource source2, + SingleSource source3, SingleSource source4 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return concat(Flowable.fromArray(source1, source2, source3, source4)); + } + + /** + * Concatenate the single values, in a non-overlapping fashion, of the SingleSources provided in + * an array. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concatArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources the array of SingleSource instances + * @return the new Flowable instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Flowable concatArray(SingleSource... sources) { + return RxJavaPlugins.onAssembly(new FlowableConcatMap(Flowable.fromArray(sources), SingleInternalHelper.toFlowable(), 2, ErrorMode.BOUNDARY)); + } + + /** + * Concatenates a sequence of SingleSource eagerly into a single stream of values. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source SingleSources. The operator buffers the value emitted by these SingleSources and then drains them + * in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of Single that need to be eagerly concatenated + * @return the new Flowable instance with the specified concatenation behavior + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatArrayEager(SingleSource... sources) { + return Flowable.fromArray(sources).concatMapEager(SingleInternalHelper.toFlowable()); + } + + /** + * Concatenates a Publisher sequence of SingleSources eagerly into a single stream of values. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * emitted source Publishers as they are observed. The operator buffers the values emitted by these + * Publishers and then drains them in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream and the outer Publisher is + * expected to support backpressure. Violating this assumption, the operator will + * signal {@link io.reactivex.exceptions.MissingBackpressureException}.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of Publishers that need to be eagerly concatenated + * @return the new Publisher instance with the specified concatenation behavior + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatEager(Publisher> sources) { + return Flowable.fromPublisher(sources).concatMapEager(SingleInternalHelper.toFlowable()); + } + + /** + * Concatenates a sequence of SingleSources eagerly into a single stream of values. + *

+ * + *

+ * Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the + * source SingleSources. The operator buffers the values emitted by these SingleSources and then drains them + * in order, each one after the previous one completes. + *

+ *
Backpressure:
+ *
Backpressure is honored towards the downstream.
+ *
Scheduler:
+ *
This method does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param sources a sequence of SingleSource that need to be eagerly concatenated + * @return the new Flowable instance with the specified concatenation behavior + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable concatEager(Iterable> sources) { + return Flowable.fromIterable(sources).concatMapEager(SingleInternalHelper.toFlowable()); + } + + /** + * Provides an API (via a cold Single) that bridges the reactive world with the callback-style world. + *

+ * + *

+ * Example: + *


+     * Single.<Event>create(emitter -> {
+     *     Callback listener = new Callback() {
+     *         @Override
+     *         public void onEvent(Event e) {
+     *             emitter.onSuccess(e);
+     *         }
+     *
+     *         @Override
+     *         public void onFailure(Exception e) {
+     *             emitter.onError(e);
+     *         }
+     *     };
+     *
+     *     AutoCloseable c = api.someMethod(listener);
+     *
+     *     emitter.setCancellable(c::close);
+     *
+     * });
+     * 
+ *
+ *
Scheduler:
+ *
{@code create} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param source the emitter that is called when a SingleObserver subscribes to the returned {@code Single} + * @return the new Single instance + * @see SingleOnSubscribe + * @see Cancellable + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single create(SingleOnSubscribe source) { + ObjectHelper.requireNonNull(source, "source is null"); + return RxJavaPlugins.onAssembly(new SingleCreate(source)); + } + + /** + * Calls a {@link Callable} for each individual {@link SingleObserver} to return the actual {@link SingleSource} to + * be subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code defer} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param singleSupplier the {@code Callable} that is called for each individual {@code SingleObserver} and + * returns a SingleSource instance to subscribe to + * @return the new Single instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single defer(final Callable> singleSupplier) { + ObjectHelper.requireNonNull(singleSupplier, "singleSupplier is null"); + return RxJavaPlugins.onAssembly(new SingleDefer(singleSupplier)); + } + + /** + * Signals a Throwable returned by the callback function for each individual SingleObserver. + *

+ * + *

+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param errorSupplier the callable that is called for each individual SingleObserver and + * returns a Throwable instance to be emitted. + * @return the new Single instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single error(final Callable errorSupplier) { + ObjectHelper.requireNonNull(errorSupplier, "errorSupplier is null"); + return RxJavaPlugins.onAssembly(new SingleError(errorSupplier)); + } + + /** + * Returns a Single that invokes a subscriber's {@link SingleObserver#onError onError} method when the + * subscriber subscribes to it. + *

+ * + *

+ *
Scheduler:
+ *
{@code error} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param exception + * the particular Throwable to pass to {@link SingleObserver#onError onError} + * @param + * the type of the item (ostensibly) emitted by the Single + * @return a Single that invokes the subscriber's {@link SingleObserver#onError onError} method when + * the subscriber subscribes to it + * @see ReactiveX operators documentation: Throw + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single error(final Throwable exception) { + ObjectHelper.requireNonNull(exception, "exception is null"); + return error(Functions.justCallable(exception)); + } + + /** + * Returns a {@link Single} that invokes passed function and emits its result for each new SingleObserver that subscribes. + *

+ * Allows you to defer execution of passed function until SingleObserver subscribes to the {@link Single}. + * It makes passed function "lazy". + * Result of the function invocation will be emitted by the {@link Single}. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromCallable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Callable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link SingleObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Single} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + *
+ *
+ * + * @param callable + * function which execution should be deferred, it will be invoked when SingleObserver will subscribe to the {@link Single}. + * @param + * the type of the item emitted by the {@link Single}. + * @return a {@link Single} whose {@link SingleObserver}s' subscriptions trigger an invocation of the given function. + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single fromCallable(final Callable callable) { + ObjectHelper.requireNonNull(callable, "callable is null"); + return RxJavaPlugins.onAssembly(new SingleFromCallable(callable)); + } + + /** + * Converts a {@link Future} into a {@code Single}. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a Single that emits the return + * value of the {@link Future#get} method of that object, by passing the object into the {@code from} + * method. + *

+ * Important note: This Single is blocking; you cannot dispose it. + *

+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting {@code Single} + * @return a {@code Single} that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Single fromFuture(Future future) { + return toSingle(Flowable.fromFuture(future)); + } + + /** + * Converts a {@link Future} into a {@code Single}, with a timeout on the Future. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a {@code Single} that emits + * the return value of the {@link Future#get} method of that object, by passing the object into the + * {@code from} method. + *

+ * Important note: This {@code Single} is blocking; you cannot dispose it. + *

+ *
Scheduler:
+ *
{@code fromFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param future + * the source {@link Future} + * @param timeout + * the maximum time to wait before calling {@code get} + * @param unit + * the {@link TimeUnit} of the {@code timeout} argument + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting {@code Single} + * @return a {@code Single} that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Single fromFuture(Future future, long timeout, TimeUnit unit) { + return toSingle(Flowable.fromFuture(future, timeout, unit)); + } + + /** + * Converts a {@link Future} into a {@code Single}, with a timeout on the Future. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a {@code Single} that emits + * the return value of the {@link Future#get} method of that object, by passing the object into the + * {@code from} method. + *

+ * Important note: This {@code Single} is blocking; you cannot dispose it. + *

+ *
Scheduler:
+ *
You specify the {@link Scheduler} where the blocking wait will happen.
+ *
+ * + * @param future + * the source {@link Future} + * @param timeout + * the maximum time to wait before calling {@code get} + * @param unit + * the {@link TimeUnit} of the {@code timeout} argument + * @param scheduler + * the Scheduler to use for the blocking wait + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting {@code Single} + * @return a {@code Single} that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Single fromFuture(Future future, long timeout, TimeUnit unit, Scheduler scheduler) { + return toSingle(Flowable.fromFuture(future, timeout, unit, scheduler)); + } + + /** + * Converts a {@link Future}, operating on a specified {@link Scheduler}, into a {@code Single}. + *

+ * + *

+ * You can convert any object that supports the {@link Future} interface into a {@code Single} that emits + * the return value of the {@link Future#get} method of that object, by passing the object into the + * {@code from} method. + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param future + * the source {@link Future} + * @param scheduler + * the {@link Scheduler} to wait for the Future on. Use a Scheduler such as + * {@link Schedulers#io()} that can block and wait on the Future + * @param + * the type of object that the {@link Future} returns, and also the type of item to be emitted by + * the resulting {@code Single} + * @return a {@code Single} that emits the item from the source {@link Future} + * @see ReactiveX operators documentation: From + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Single fromFuture(Future future, Scheduler scheduler) { + return toSingle(Flowable.fromFuture(future, scheduler)); + } + + /** + * Wraps a specific Publisher into a Single and signals its single element or error. + *

If the source Publisher is empty, a NoSuchElementException is signalled. If + * the source has more than one element, an IndexOutOfBoundsException is signalled. + *

+ * The {@link Publisher} must follow the + * Reactive Streams specification. + * Violating the specification may result in undefined behavior. + *

+ * If possible, use {@link #create(SingleOnSubscribe)} to create a + * source-like {@code Single} instead. + *

+ * Note that even though {@link Publisher} appears to be a functional interface, it + * is not recommended to implement it through a lambda as the specification requires + * state management that is not achievable with a stateless lambda. + *

+ * + *

+ *
Backpressure:
+ *
The {@code publisher} is consumed in an unbounded fashion but will be cancelled + * if it produced more than one item.
+ *
Scheduler:
+ *
{@code fromPublisher} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param publisher the source Publisher instance, not null + * @return the new Single instance + * @see #create(SingleOnSubscribe) + */ + @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single fromPublisher(final Publisher publisher) { + ObjectHelper.requireNonNull(publisher, "publisher is null"); + return RxJavaPlugins.onAssembly(new SingleFromPublisher(publisher)); + } + + /** + * Wraps a specific ObservableSource into a Single and signals its single element or error. + *

If the ObservableSource is empty, a NoSuchElementException is signalled. + * If the source has more than one element, an IndexOutOfBoundsException is signalled. + *

+ * + *

+ *
Scheduler:
+ *
{@code fromObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param observableSource the source Observable, not null + * @param + * the type of the item emitted by the {@link Single}. + * @return the new Single instance + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single fromObservable(ObservableSource observableSource) { + ObjectHelper.requireNonNull(observableSource, "observableSource is null"); + return RxJavaPlugins.onAssembly(new ObservableSingleSingle(observableSource, null)); + } + + /** + * Returns a {@code Single} that emits a specified item. + *

+ * + *

+ * To convert any object into a {@code Single} that emits that object, pass that object into the + * {@code just} method. + *

+ *
Scheduler:
+ *
{@code just} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param item + * the item to emit + * @param + * the type of that item + * @return a {@code Single} that emits {@code item} + * @see ReactiveX operators documentation: Just + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public static Single just(final T item) { + ObjectHelper.requireNonNull(item, "item is null"); + return RxJavaPlugins.onAssembly(new SingleJust(item)); + } + + /** + * Merges an Iterable sequence of SingleSource instances into a single Flowable sequence, + * running all SingleSources at once. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Iterable)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + *
+ *
+ * @param the common and resulting value type + * @param sources the Iterable sequence of SingleSource sources + * @return the new Flowable instance + * @since 2.0 + * @see #mergeDelayError(Iterable) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable merge(Iterable> sources) { + return merge(Flowable.fromIterable(sources)); + } + + /** + * Merges a Flowable sequence of SingleSource instances into a single Flowable sequence, + * running all SingleSources at once. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(Publisher)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + *
+ *
+ * @param the common and resulting value type + * @param sources the Flowable sequence of SingleSource sources + * @return the new Flowable instance + * @see #mergeDelayError(Publisher) + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Flowable merge(Publisher> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new FlowableFlatMapPublisher(sources, SingleInternalHelper.toFlowable(), false, Integer.MAX_VALUE, Flowable.bufferSize())); + } + + /** + * Flattens a {@code Single} that emits a {@code Single} into a single {@code Single} that emits the item + * emitted by the nested {@code Single}, without any transformation. + *

+ * + *

+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
The resulting {@code Single} emits the outer source's or the inner {@code SingleSource}'s {@code Throwable} as is. + * Unlike the other {@code merge()} operators, this operator won't and can't produce a {@code CompositeException} because there is + * only one possibility for the outer or the inner {@code SingleSource} to emit an {@code onError} signal. + * Therefore, there is no need for a {@code mergeDelayError(SingleSource>)} operator. + *
+ *
+ * + * @param the value type of the sources and the output + * @param source + * a {@code Single} that emits a {@code Single} + * @return a {@code Single} that emits the item that is the result of flattening the {@code Single} emitted + * by {@code source} + * @see ReactiveX operators documentation: Merge + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Single merge(SingleSource> source) { + ObjectHelper.requireNonNull(source, "source is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMap, T>(source, (Function)Functions.identity())); + } + + /** + * Flattens two Singles into a single Flowable, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by + * using the {@code merge} method. + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(SingleSource, SingleSource)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common value type + * @param source1 + * a SingleSource to be merged + * @param source2 + * a SingleSource to be merged + * @return a Flowable that emits all of the items emitted by the source Singles + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(SingleSource, SingleSource) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable merge( + SingleSource source1, SingleSource source2 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return merge(Flowable.fromArray(source1, source2)); + } + + /** + * Flattens three Singles into a single Flowable, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using + * the {@code merge} method. + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(SingleSource, SingleSource, SingleSource)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common value type + * @param source1 + * a SingleSource to be merged + * @param source2 + * a SingleSource to be merged + * @param source3 + * a SingleSource to be merged + * @return a Flowable that emits all of the items emitted by the source Singles + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(SingleSource, SingleSource, SingleSource) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable merge( + SingleSource source1, SingleSource source2, + SingleSource source3 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return merge(Flowable.fromArray(source1, source2, source3)); + } + + /** + * Flattens four Singles into a single Flowable, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using + * the {@code merge} method. + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If any of the source {@code SingleSource}s signal a {@code Throwable} via {@code onError}, the resulting + * {@code Flowable} terminates with that {@code Throwable} and all other source {@code SingleSource}s are disposed. + * If more than one {@code SingleSource} signals an error, the resulting {@code Flowable} may terminate with the + * first one's error or, depending on the concurrency of the sources, may terminate with a + * {@code CompositeException} containing two or more of the various error signals. + * {@code Throwable}s that didn't make into the composite will be sent (individually) to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} method as {@code UndeliverableException} errors. Similarly, {@code Throwable}s + * signaled by source(s) after the returned {@code Flowable} has been cancelled or terminated with a + * (composite) error will be sent to the same global error handler. + * Use {@link #mergeDelayError(SingleSource, SingleSource, SingleSource, SingleSource)} to merge sources and terminate only when all source {@code SingleSource}s + * have completed or failed with an error. + *
+ *
+ * + * @param the common value type + * @param source1 + * a SingleSource to be merged + * @param source2 + * a SingleSource to be merged + * @param source3 + * a SingleSource to be merged + * @param source4 + * a SingleSource to be merged + * @return a Flowable that emits all of the items emitted by the source Singles + * @see ReactiveX operators documentation: Merge + * @see #mergeDelayError(SingleSource, SingleSource, SingleSource, SingleSource) + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable merge( + SingleSource source1, SingleSource source2, + SingleSource source3, SingleSource source4 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return merge(Flowable.fromArray(source1, source2, source3, source4)); + } + + /** + * Merges an Iterable sequence of SingleSource instances into a single Flowable sequence, + * running all SingleSources at once and delaying any error(s) until all sources succeed or fail. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.9 - experimental + * @param the common and resulting value type + * @param sources the Iterable sequence of SingleSource sources + * @return the new Flowable instance + * @see #merge(Iterable) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + public static Flowable mergeDelayError(Iterable> sources) { + return mergeDelayError(Flowable.fromIterable(sources)); + } + + /** + * Merges a Flowable sequence of SingleSource instances into a single Flowable sequence, + * running all SingleSources at once and delaying any error(s) until all sources succeed or fail. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.9 - experimental + * @param the common and resulting value type + * @param sources the Flowable sequence of SingleSource sources + * @return the new Flowable instance + * @see #merge(Publisher) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Flowable mergeDelayError(Publisher> sources) { + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new FlowableFlatMapPublisher(sources, SingleInternalHelper.toFlowable(), true, Integer.MAX_VALUE, Flowable.bufferSize())); + } + + /** + * Flattens two Singles into a single Flowable, without any transformation, delaying + * any error(s) until all sources succeed or fail. + *

+ * + *

+ * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by + * using the {@code mergeDelayError} method. + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.9 - experimental + * @param the common value type + * @param source1 + * a SingleSource to be merged + * @param source2 + * a SingleSource to be merged + * @return a Flowable that emits all of the items emitted by the source Singles + * @see ReactiveX operators documentation: Merge + * @see #merge(SingleSource, SingleSource) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable mergeDelayError( + SingleSource source1, SingleSource source2 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return mergeDelayError(Flowable.fromArray(source1, source2)); + } + + /** + * Flattens three Singles into a single Flowable, without any transformation, delaying + * any error(s) until all sources succeed or fail. + *

+ * + *

+ * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using + * the {@code mergeDelayError} method. + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.9 - experimental + * @param the common value type + * @param source1 + * a SingleSource to be merged + * @param source2 + * a SingleSource to be merged + * @param source3 + * a SingleSource to be merged + * @return a Flowable that emits all of the items emitted by the source Singles + * @see ReactiveX operators documentation: Merge + * @see #merge(SingleSource, SingleSource, SingleSource) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable mergeDelayError( + SingleSource source1, SingleSource source2, + SingleSource source3 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return mergeDelayError(Flowable.fromArray(source1, source2, source3)); + } + + /** + * Flattens four Singles into a single Flowable, without any transformation, delaying + * any error(s) until all sources succeed or fail. + *

+ * + *

+ * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using + * the {@code mergeDelayError} method. + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code mergeDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.9 - experimental + * @param the common value type + * @param source1 + * a SingleSource to be merged + * @param source2 + * a SingleSource to be merged + * @param source3 + * a SingleSource to be merged + * @param source4 + * a SingleSource to be merged + * @return a Flowable that emits all of the items emitted by the source Singles + * @see ReactiveX operators documentation: Merge + * @see #merge(SingleSource, SingleSource, SingleSource, SingleSource) + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Flowable mergeDelayError( + SingleSource source1, SingleSource source2, + SingleSource source3, SingleSource source4 + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return mergeDelayError(Flowable.fromArray(source1, source2, source3, source4)); + } + + /** + * Returns a singleton instance of a never-signalling Single (only calls onSubscribe). + *

+ * + *

+ *
Scheduler:
+ *
{@code never} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the target value type + * @return the singleton never instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single never() { + return RxJavaPlugins.onAssembly((Single) SingleNever.INSTANCE); + } + + /** + * Signals success with 0L value after the given delay for each SingleObserver. + *

+ * + *

+ *
Scheduler:
+ *
{@code timer} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * @param delay the delay amount + * @param unit the time unit of the delay + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public static Single timer(long delay, TimeUnit unit) { + return timer(delay, unit, Schedulers.computation()); + } + + /** + * Signals success with 0L value after the given delay for each SingleObserver. + *

+ * + *

+ *
Scheduler:
+ *
you specify the {@link Scheduler} to signal on.
+ *
+ * @param delay the delay amount + * @param unit the time unit of the delay + * @param scheduler the scheduler where the single 0L will be emitted + * @return the new Single instance + * @throws NullPointerException + * if unit is null, or + * if scheduler is null + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public static Single timer(final long delay, final TimeUnit unit, final Scheduler scheduler) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new SingleTimer(delay, unit, scheduler)); + } + + /** + * Compares two SingleSources and emits true if they emit the same value (compared via Object.equals). + *

+ * + *

+ *
Scheduler:
+ *
{@code equals} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the common value type + * @param first the first SingleSource instance + * @param second the second SingleSource instance + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single equals(final SingleSource first, final SingleSource second) { // NOPMD + ObjectHelper.requireNonNull(first, "first is null"); + ObjectHelper.requireNonNull(second, "second is null"); + return RxJavaPlugins.onAssembly(new SingleEquals(first, second)); + } + + /** + * Advanced use only: creates a Single instance without + * any safeguards by using a callback that is called with a SingleObserver. + *

+ * + *

+ *
Scheduler:
+ *
{@code unsafeCreate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param onSubscribe the function that is called with the subscribing SingleObserver + * @return the new Single instance + * @throws IllegalArgumentException if {@code source} is a subclass of {@code Single}; such + * instances don't need conversion and is possibly a port remnant from 1.x or one should use {@link #hide()} + * instead. + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single unsafeCreate(SingleSource onSubscribe) { + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + if (onSubscribe instanceof Single) { + throw new IllegalArgumentException("unsafeCreate(Single) should be upgraded"); + } + return RxJavaPlugins.onAssembly(new SingleFromUnsafeSource(onSubscribe)); + } + + /** + * Allows using and disposing a resource while running a SingleSource instance generated from + * that resource (similar to a try-with-resources). + *

+ * + *

+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the SingleSource generated + * @param the resource type + * @param resourceSupplier the Callable called for each SingleObserver to generate a resource Object + * @param singleFunction the function called with the returned resource + * Object from {@code resourceSupplier} and should return a SingleSource instance + * to be run by the operator + * @param disposer the consumer of the generated resource that is called exactly once for + * that particular resource when the generated SingleSource terminates + * (successfully or with an error) or gets disposed. + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public static Single using(Callable resourceSupplier, + Function> singleFunction, + Consumer disposer) { + return using(resourceSupplier, singleFunction, disposer, true); + } + + /** + * Allows using and disposing a resource while running a SingleSource instance generated from + * that resource (similar to a try-with-resources). + *

+ * + *

+ *
Scheduler:
+ *
{@code using} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type of the SingleSource generated + * @param the resource type + * @param resourceSupplier the Callable called for each SingleObserver to generate a resource Object + * @param singleFunction the function called with the returned resource + * Object from {@code resourceSupplier} and should return a SingleSource instance + * to be run by the operator + * @param disposer the consumer of the generated resource that is called exactly once for + * that particular resource when the generated SingleSource terminates + * (successfully or with an error) or gets disposed. + * @param eager + * if true, the disposer is called before the terminal event is signalled + * if false, the disposer is called after the terminal event is delivered to downstream + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single using( + final Callable resourceSupplier, + final Function> singleFunction, + final Consumer disposer, + final boolean eager) { + ObjectHelper.requireNonNull(resourceSupplier, "resourceSupplier is null"); + ObjectHelper.requireNonNull(singleFunction, "singleFunction is null"); + ObjectHelper.requireNonNull(disposer, "disposer is null"); + + return RxJavaPlugins.onAssembly(new SingleUsing(resourceSupplier, singleFunction, disposer, eager)); + } + + /** + * Wraps a SingleSource instance into a new Single instance if not already a Single + * instance. + *

+ * + *

+ *
Scheduler:
+ *
{@code wrap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the value type + * @param source the source to wrap + * @return the Single wrapper or the source cast to Single (if possible) + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single wrap(SingleSource source) { + ObjectHelper.requireNonNull(source, "source is null"); + if (source instanceof Single) { + return RxJavaPlugins.onAssembly((Single)source); + } + return RxJavaPlugins.onAssembly(new SingleFromUnsafeSource(source)); + } + + /** + * Waits until all SingleSource sources provided by the Iterable sequence signal a success + * value and calls a zipper function with an array of these values to return a result + * to be emitted to downstream. + *

+ * If the {@code Iterable} of {@link SingleSource}s is empty a {@link NoSuchElementException} error is signalled after subscription. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + * + *

+ * + *

+ * If any of the SingleSources signal an error, all other SingleSources get disposed and the + * error emitted to downstream immediately. + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the common value type + * @param the result value type + * @param sources the Iterable sequence of SingleSource instances. An empty sequence will result in an + * {@code onError} signal of {@link NoSuchElementException}. + * @param zipper the function that receives an array with values from each SingleSource + * and should return a value to be emitted to downstream + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single zip(final Iterable> sources, Function zipper) { + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.requireNonNull(sources, "sources is null"); + return RxJavaPlugins.onAssembly(new SingleZipIterable(sources, zipper)); + } + + /** + * Returns a Single that emits the results of a specified combiner function applied to two items emitted by + * two other Singles. + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first source Single's value type + * @param the second source Single's value type + * @param the result value type + * @param source1 + * the first source Single + * @param source2 + * a second source Single + * @param zipper + * a function that, when applied to the item emitted by each of the source Singles, results in an + * item that will be emitted by the resulting Single + * @return a Single that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single zip( + SingleSource source1, SingleSource source2, + BiFunction zipper + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2); + } + + /** + * Returns a Single that emits the results of a specified combiner function applied to three items emitted + * by three other Singles. + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first source Single's value type + * @param the second source Single's value type + * @param the third source Single's value type + * @param the result value type + * @param source1 + * the first source Single + * @param source2 + * a second source Single + * @param source3 + * a third source Single + * @param zipper + * a function that, when applied to the item emitted by each of the source Singles, results in an + * item that will be emitted by the resulting Single + * @return a Single that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single zip( + SingleSource source1, SingleSource source2, + SingleSource source3, + Function3 zipper + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3); + } + + /** + * Returns a Single that emits the results of a specified combiner function applied to four items + * emitted by four other Singles. + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first source Single's value type + * @param the second source Single's value type + * @param the third source Single's value type + * @param the fourth source Single's value type + * @param the result value type + * @param source1 + * the first source Single + * @param source2 + * a second source Single + * @param source3 + * a third source Single + * @param source4 + * a fourth source Single + * @param zipper + * a function that, when applied to the item emitted by each of the source Singles, results in an + * item that will be emitted by the resulting Single + * @return a Single that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single zip( + SingleSource source1, SingleSource source2, + SingleSource source3, SingleSource source4, + Function4 zipper + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4); + } + + /** + * Returns a Single that emits the results of a specified combiner function applied to five items + * emitted by five other Singles. + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first source Single's value type + * @param the second source Single's value type + * @param the third source Single's value type + * @param the fourth source Single's value type + * @param the fifth source Single's value type + * @param the result value type + * @param source1 + * the first source Single + * @param source2 + * a second source Single + * @param source3 + * a third source Single + * @param source4 + * a fourth source Single + * @param source5 + * a fifth source Single + * @param zipper + * a function that, when applied to the item emitted by each of the source Singles, results in an + * item that will be emitted by the resulting Single + * @return a Single that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single zip( + SingleSource source1, SingleSource source2, + SingleSource source3, SingleSource source4, + SingleSource source5, + Function5 zipper + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5); + } + + /** + * Returns a Single that emits the results of a specified combiner function applied to six items + * emitted by six other Singles. + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first source Single's value type + * @param the second source Single's value type + * @param the third source Single's value type + * @param the fourth source Single's value type + * @param the fifth source Single's value type + * @param the sixth source Single's value type + * @param the result value type + * @param source1 + * the first source Single + * @param source2 + * a second source Single + * @param source3 + * a third source Single + * @param source4 + * a fourth source Single + * @param source5 + * a fifth source Single + * @param source6 + * a sixth source Single + * @param zipper + * a function that, when applied to the item emitted by each of the source Singles, results in an + * item that will be emitted by the resulting Single + * @return a Single that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single zip( + SingleSource source1, SingleSource source2, + SingleSource source3, SingleSource source4, + SingleSource source5, SingleSource source6, + Function6 zipper + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5, source6); + } + + /** + * Returns a Single that emits the results of a specified combiner function applied to seven items + * emitted by seven other Singles. + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first source Single's value type + * @param the second source Single's value type + * @param the third source Single's value type + * @param the fourth source Single's value type + * @param the fifth source Single's value type + * @param the sixth source Single's value type + * @param the seventh source Single's value type + * @param the result value type + * @param source1 + * the first source Single + * @param source2 + * a second source Single + * @param source3 + * a third source Single + * @param source4 + * a fourth source Single + * @param source5 + * a fifth source Single + * @param source6 + * a sixth source Single + * @param source7 + * a seventh source Single + * @param zipper + * a function that, when applied to the item emitted by each of the source Singles, results in an + * item that will be emitted by the resulting Single + * @return a Single that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single zip( + SingleSource source1, SingleSource source2, + SingleSource source3, SingleSource source4, + SingleSource source5, SingleSource source6, + SingleSource source7, + Function7 zipper + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5, source6, source7); + } + + /** + * Returns a Single that emits the results of a specified combiner function applied to eight items + * emitted by eight other Singles. + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first source Single's value type + * @param the second source Single's value type + * @param the third source Single's value type + * @param the fourth source Single's value type + * @param the fifth source Single's value type + * @param the sixth source Single's value type + * @param the seventh source Single's value type + * @param the eighth source Single's value type + * @param the result value type + * @param source1 + * the first source Single + * @param source2 + * a second source Single + * @param source3 + * a third source Single + * @param source4 + * a fourth source Single + * @param source5 + * a fifth source Single + * @param source6 + * a sixth source Single + * @param source7 + * a seventh source Single + * @param source8 + * an eighth source Single + * @param zipper + * a function that, when applied to the item emitted by each of the source Singles, results in an + * item that will be emitted by the resulting Single + * @return a Single that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single zip( + SingleSource source1, SingleSource source2, + SingleSource source3, SingleSource source4, + SingleSource source5, SingleSource source6, + SingleSource source7, SingleSource source8, + Function8 zipper + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5, source6, source7, source8); + } + + /** + * Returns a Single that emits the results of a specified combiner function applied to nine items + * emitted by nine other Singles. + *

+ * + *

+ *
Scheduler:
+ *
{@code zip} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the first source Single's value type + * @param the second source Single's value type + * @param the third source Single's value type + * @param the fourth source Single's value type + * @param the fifth source Single's value type + * @param the sixth source Single's value type + * @param the seventh source Single's value type + * @param the eighth source Single's value type + * @param the ninth source Single's value type + * @param the result value type + * @param source1 + * the first source Single + * @param source2 + * a second source Single + * @param source3 + * a third source Single + * @param source4 + * a fourth source Single + * @param source5 + * a fifth source Single + * @param source6 + * a sixth source Single + * @param source7 + * a seventh source Single + * @param source8 + * an eighth source Single + * @param source9 + * a ninth source Single + * @param zipper + * a function that, when applied to the item emitted by each of the source Singles, results in an + * item that will be emitted by the resulting Single + * @return a Single that emits the zipped results + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public static Single zip( + SingleSource source1, SingleSource source2, + SingleSource source3, SingleSource source4, + SingleSource source5, SingleSource source6, + SingleSource source7, SingleSource source8, + SingleSource source9, + Function9 zipper + ) { + ObjectHelper.requireNonNull(source1, "source1 is null"); + ObjectHelper.requireNonNull(source2, "source2 is null"); + ObjectHelper.requireNonNull(source3, "source3 is null"); + ObjectHelper.requireNonNull(source4, "source4 is null"); + ObjectHelper.requireNonNull(source5, "source5 is null"); + ObjectHelper.requireNonNull(source6, "source6 is null"); + ObjectHelper.requireNonNull(source7, "source7 is null"); + ObjectHelper.requireNonNull(source8, "source8 is null"); + ObjectHelper.requireNonNull(source9, "source9 is null"); + return zipArray(Functions.toFunction(zipper), source1, source2, source3, source4, source5, source6, source7, source8, source9); + } + + /** + * Waits until all SingleSource sources provided via an array signal a success + * value and calls a zipper function with an array of these values to return a result + * to be emitted to downstream. + *

+ * If the array of {@link SingleSource}s is empty a {@link NoSuchElementException} error is signalled immediately. + *

+ * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the + * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a + * {@code Function} passed to the method would trigger a {@code ClassCastException}. + * + *

+ * + *

+ * If any of the SingleSources signal an error, all other SingleSources get disposed and the + * error emitted to downstream immediately. + *

+ *
Scheduler:
+ *
{@code zipArray} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the common value type + * @param the result value type + * @param sources the array of SingleSource instances. An empty sequence will result in an + * {@code onError} signal of {@link NoSuchElementException}. + * @param zipper the function that receives an array with values from each SingleSource + * and should return a value to be emitted to downstream + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public static Single zipArray(Function zipper, SingleSource... sources) { + ObjectHelper.requireNonNull(zipper, "zipper is null"); + ObjectHelper.requireNonNull(sources, "sources is null"); + if (sources.length == 0) { + return error(new NoSuchElementException()); + } + return RxJavaPlugins.onAssembly(new SingleZipArray(sources, zipper)); + } + + /** + * Signals the event of this or the other SingleSource whichever signals first. + *

+ * + *

+ *
Scheduler:
+ *
{@code ambWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param other the other SingleSource to race for the first emission of success or error + * @return the new Single instance. A subscription to this provided source will occur after subscribing + * to the current source. + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public final Single ambWith(SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return ambArray(this, other); + } + + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * + *

+ * This allows fluent conversion to any other type. + *

+ *
Scheduler:
+ *
{@code as} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.7 - experimental + * @param the resulting object type + * @param converter the function that receives the current Single instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final R as(@NonNull SingleConverter converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + + /** + * Hides the identity of the current Single, including the Disposable that is sent + * to the downstream via {@code onSubscribe()}. + *

+ * + *

+ *
Scheduler:
+ *
{@code hide} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single hide() { + return RxJavaPlugins.onAssembly(new SingleHide(this)); + } + + /** + * Transform a Single by applying a particular Transformer function to it. + *

+ * + *

+ * This method operates on the Single itself whereas {@link #lift} operates on the Single's SingleObservers. + *

+ * If the operator you are creating is designed to act on the individual item emitted by a Single, use + * {@link #lift}. If your operator is designed to transform the source Single as a whole (for instance, by + * applying a particular set of existing RxJava operators to it) use {@code compose}. + *

+ *
Scheduler:
+ *
{@code compose} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the value type of the single returned by the transformer function + * @param transformer the transformer function, not null + * @return the source Single, transformed by the transformer function + * @see RxJava wiki: Implementing Your Own Operators + */ + @SuppressWarnings("unchecked") + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single compose(SingleTransformer transformer) { + return wrap(((SingleTransformer) ObjectHelper.requireNonNull(transformer, "transformer is null")).apply(this)); + } + + /** + * Stores the success value or exception from the current Single and replays it to late SingleObservers. + *

+ * + * The returned Single subscribes to the current Single when the first SingleObserver subscribes. + *

+ *
Scheduler:
+ *
{@code cache} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single cache() { + return RxJavaPlugins.onAssembly(new SingleCache(this)); + } + + /** + * Casts the success value of the current Single into the target type or signals a + * ClassCastException if not compatible. + *

+ * + *

+ *
Scheduler:
+ *
{@code cast} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the target type + * @param clazz the type token to use for casting the success result from the current Single + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single cast(final Class clazz) { + ObjectHelper.requireNonNull(clazz, "clazz is null"); + return map(Functions.castFunction(clazz)); + } + + /** + * Returns a Flowable that emits the item emitted by the source Single, then the item emitted by the + * specified Single. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code concatWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * a Single to be concatenated after the current + * @return a Flowable that emits the item emitted by the source Single, followed by the item emitted by + * {@code t1} + * @see ReactiveX operators documentation: Concat + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable concatWith(SingleSource other) { + return concat(this, other); + } + + /** + * Delays the emission of the success signal from the current Single by the specified amount. + * An error signal will not be delayed. + *

+ * + *

+ *
Scheduler:
+ *
{@code delay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ * + * @param time the amount of time the success signal should be delayed for + * @param unit the time unit + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Single delay(long time, TimeUnit unit) { + return delay(time, unit, Schedulers.computation(), false); + } + + /** + * Delays the emission of the success or error signal from the current Single by the specified amount. + *

+ * + *

+ *
Scheduler:
+ *
{@code delay} operates by default on the {@code computation} {@link Scheduler}.
+ *
+ *

History: 2.1.5 - experimental + * @param time the amount of time the success or error signal should be delayed for + * @param unit the time unit + * @param delayError if true, both success and error signals are delayed. if false, only success signals are delayed. + * @return the new Single instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Single delay(long time, TimeUnit unit, boolean delayError) { + return delay(time, unit, Schedulers.computation(), delayError); + } + + /** + * Delays the emission of the success signal from the current Single by the specified amount. + * An error signal will not be delayed. + *

+ * + *

+ *
Scheduler:
+ *
you specify the {@link Scheduler} where the non-blocking wait and emission happens
+ *
+ * + * @param time the amount of time the success signal should be delayed for + * @param unit the time unit + * @param scheduler the target scheduler to use for the non-blocking wait and emission + * @return the new Single instance + * @throws NullPointerException + * if unit is null, or + * if scheduler is null + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Single delay(final long time, final TimeUnit unit, final Scheduler scheduler) { + return delay(time, unit, scheduler, false); + } + + /** + * Delays the emission of the success or error signal from the current Single by the specified amount. + *

+ * + *

+ *
Scheduler:
+ *
you specify the {@link Scheduler} where the non-blocking wait and emission happens
+ *
+ *

History: 2.1.5 - experimental + * @param time the amount of time the success or error signal should be delayed for + * @param unit the time unit + * @param scheduler the target scheduler to use for the non-blocking wait and emission + * @param delayError if true, both success and error signals are delayed. if false, only success signals are delayed. + * @return the new Single instance + * @throws NullPointerException + * if unit is null, or + * if scheduler is null + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Single delay(final long time, final TimeUnit unit, final Scheduler scheduler, boolean delayError) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new SingleDelay(this, time, unit, scheduler, delayError)); + } + + /** + * Delays the actual subscription to the current Single until the given other CompletableSource + * completes. + *

+ * + *

If the delaying source signals an error, that error is re-emitted and no subscription + * to the current Single happens. + *

+ *
Scheduler:
+ *
{@code delaySubscription} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param other the CompletableSource that has to complete before the subscription to the + * current Single happens + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single delaySubscription(CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new SingleDelayWithCompletable(this, other)); + } + + /** + * Delays the actual subscription to the current Single until the given other SingleSource + * signals success. + *

+ * + *

If the delaying source signals an error, that error is re-emitted and no subscription + * to the current Single happens. + *

+ *
Scheduler:
+ *
{@code delaySubscription} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the element type of the other source + * @param other the SingleSource that has to complete before the subscription to the + * current Single happens + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single delaySubscription(SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new SingleDelayWithSingle(this, other)); + } + + /** + * Delays the actual subscription to the current Single until the given other ObservableSource + * signals its first value or completes. + *

+ * + *

If the delaying source signals an error, that error is re-emitted and no subscription + * to the current Single happens. + *

+ *
Scheduler:
+ *
{@code delaySubscription} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the element type of the other source + * @param other the ObservableSource that has to signal a value or complete before the + * subscription to the current Single happens + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single delaySubscription(ObservableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new SingleDelayWithObservable(this, other)); + } + + /** + * Delays the actual subscription to the current Single until the given other Publisher + * signals its first value or completes. + *

+ * + *

If the delaying source signals an error, that error is re-emitted and no subscription + * to the current Single happens. + *

The other source is consumed in an unbounded manner (requesting Long.MAX_VALUE from it). + *

+ *
Backpressure:
+ *
The {@code other} publisher is consumed in an unbounded fashion but will be + * cancelled after the first item it produced.
+ *
Scheduler:
+ *
{@code delaySubscription} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the element type of the other source + * @param other the Publisher that has to signal a value or complete before the + * subscription to the current Single happens + * @return the new Single instance + * @since 2.0 + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single delaySubscription(Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new SingleDelayWithPublisher(this, other)); + } + + /** + * Delays the actual subscription to the current Single until the given time delay elapsed. + *

+ * + *

+ *
Scheduler:
+ *
{@code delaySubscription} does by default subscribe to the current Single + * on the {@code computation} {@link Scheduler} after the delay.
+ *
+ * @param time the time amount to wait with the subscription + * @param unit the time unit of the waiting + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Single delaySubscription(long time, TimeUnit unit) { + return delaySubscription(time, unit, Schedulers.computation()); + } + + /** + * Delays the actual subscription to the current Single until the given time delay elapsed. + *

+ * + *

+ *
Scheduler:
+ *
{@code delaySubscription} does by default subscribe to the current Single + * on the {@link Scheduler} you provided, after the delay.
+ *
+ * @param time the time amount to wait with the subscription + * @param unit the time unit of the waiting + * @param scheduler the scheduler to wait on and subscribe on to the current Single + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Single delaySubscription(long time, TimeUnit unit, Scheduler scheduler) { + return delaySubscription(Observable.timer(time, unit, scheduler)); + } + + /** + * Maps the {@link Notification} success value of this Single back into normal + * {@code onSuccess}, {@code onError} or {@code onComplete} signals as a + * {@link Maybe} source. + *

+ * + *

+ * The intended use of the {@code selector} function is to perform a + * type-safe identity mapping (see example) on a source that is already of type + * {@code Notification}. The Java language doesn't allow + * limiting instance methods to a certain generic argument shape, therefore, + * a function is used to ensure the conversion remains type safe. + *

+ *
Scheduler:
+ *
{@code dematerialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

+ * Example: + *


+     * Single.just(Notification.createOnNext(1))
+     * .dematerialize(notification -> notification)
+     * .test()
+     * .assertResult(1);
+     * 
+ * @param the result type + * @param selector the function called with the success item and should + * return a {@link Notification} instance. + * @return the new Maybe instance + * @since 2.2.4 - experimental + * @see #materialize() + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + @Experimental + public final Maybe dematerialize(Function> selector) { + ObjectHelper.requireNonNull(selector, "selector is null"); + return RxJavaPlugins.onAssembly(new SingleDematerialize(this, selector)); + } + + /** + * Calls the specified consumer with the success item after this item has been emitted to the downstream. + *

+ * + *

+ * Note that the {@code doAfterSuccess} action is shared between subscriptions and as such + * should be thread-safe. + *

+ *
Scheduler:
+ *
{@code doAfterSuccess} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.1 - experimental + * @param onAfterSuccess the Consumer that will be called after emitting an item from upstream to the downstream + * @return the new Single instance + * @since 2.1 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doAfterSuccess(Consumer onAfterSuccess) { + ObjectHelper.requireNonNull(onAfterSuccess, "onAfterSuccess is null"); + return RxJavaPlugins.onAssembly(new SingleDoAfterSuccess(this, onAfterSuccess)); + } + + /** + * Registers an {@link Action} to be called after this Single invokes either onSuccess or onError. + *

+ * + *

+ * Note that the {@code doAfterTerminate} action is shared between subscriptions and as such + * should be thread-safe.

+ * + *
+ *
Scheduler:
+ *
{@code doAfterTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + *

History: 2.0.6 - experimental + * @param onAfterTerminate + * an {@link Action} to be invoked when the source Single finishes + * @return a Single that emits the same items as the source Single, then invokes the + * {@link Action} + * @see ReactiveX operators documentation: Do + * @since 2.1 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doAfterTerminate(Action onAfterTerminate) { + ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"); + return RxJavaPlugins.onAssembly(new SingleDoAfterTerminate(this, onAfterTerminate)); + } + + /** + * Calls the specified action after this Single signals onSuccess or onError or gets disposed by + * the downstream. + *

In case of a race between a terminal event and a dispose call, the provided {@code onFinally} action + * is executed once per subscription. + *

Note that the {@code onFinally} action is shared between subscriptions and as such + * should be thread-safe. + *

+ * + *

+ *
+ *
Scheduler:
+ *
{@code doFinally} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.1 - experimental + * @param onFinally the action called when this Single terminates or gets disposed + * @return the new Single instance + * @since 2.1 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doFinally(Action onFinally) { + ObjectHelper.requireNonNull(onFinally, "onFinally is null"); + return RxJavaPlugins.onAssembly(new SingleDoFinally(this, onFinally)); + } + + /** + * Calls the shared consumer with the Disposable sent through the onSubscribe for each + * SingleObserver that subscribes to the current Single. + *

+ * + *

+ *
+ *
Scheduler:
+ *
{@code doOnSubscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onSubscribe the consumer called with the Disposable sent via onSubscribe + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doOnSubscribe(final Consumer onSubscribe) { + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + return RxJavaPlugins.onAssembly(new SingleDoOnSubscribe(this, onSubscribe)); + } + + /** + * Returns a Single instance that calls the given onTerminate callback + * just before this Single completes normally or with an exception. + *

+ * + *

+ * This differs from {@code doAfterTerminate} in that this happens before the {@code onSuccess} or + * {@code onError} notification. + *

+ *
Scheduler:
+ *
{@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onTerminate the action to invoke when the consumer calls {@code onSuccess} or {@code onError} + * @return the new Single instance + * @see ReactiveX operators documentation: Do + * @see #doOnTerminate(Action) + * @since 2.2.7 - experimental + */ + @Experimental + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doOnTerminate(final Action onTerminate) { + ObjectHelper.requireNonNull(onTerminate, "onTerminate is null"); + return RxJavaPlugins.onAssembly(new SingleDoOnTerminate(this, onTerminate)); + } + + /** + * Calls the shared consumer with the success value sent via onSuccess for each + * SingleObserver that subscribes to the current Single. + *

+ * + *

+ *
+ *
Scheduler:
+ *
{@code doOnSuccess} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onSuccess the consumer called with the success value of onSuccess + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doOnSuccess(final Consumer onSuccess) { + ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"); + return RxJavaPlugins.onAssembly(new SingleDoOnSuccess(this, onSuccess)); + } + + /** + * Calls the shared consumer with the error sent via onError or the value + * via onSuccess for each SingleObserver that subscribes to the current Single. + *

+ * + *

+ *
Scheduler:
+ *
{@code doOnEvent} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onEvent the consumer called with the success value of onEvent + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doOnEvent(final BiConsumer onEvent) { + ObjectHelper.requireNonNull(onEvent, "onEvent is null"); + return RxJavaPlugins.onAssembly(new SingleDoOnEvent(this, onEvent)); + } + + /** + * Calls the shared consumer with the error sent via onError for each + * SingleObserver that subscribes to the current Single. + *

+ * + *

+ *
+ *
Scheduler:
+ *
{@code doOnError} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onError the consumer called with the success value of onError + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doOnError(final Consumer onError) { + ObjectHelper.requireNonNull(onError, "onError is null"); + return RxJavaPlugins.onAssembly(new SingleDoOnError(this, onError)); + } + + /** + * Calls the shared {@code Action} if a SingleObserver subscribed to the current Single + * disposes the common Disposable it received via onSubscribe. + *

+ * + *

+ *
+ *
Scheduler:
+ *
{@code doOnDispose} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param onDispose the action called when the subscription is disposed + * @return the new Single instance + * @throws NullPointerException if onDispose is null + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single doOnDispose(final Action onDispose) { + ObjectHelper.requireNonNull(onDispose, "onDispose is null"); + return RxJavaPlugins.onAssembly(new SingleDoOnDispose(this, onDispose)); + } + + /** + * Filters the success item of the Single via a predicate function and emitting it if the predicate + * returns true, completing otherwise. + *

+ * + *

+ *
Scheduler:
+ *
{@code filter} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param predicate + * a function that evaluates the item emitted by the source Maybe, returning {@code true} + * if it passes the filter + * @return a Maybe that emit the item emitted by the source Maybe that the filter + * evaluates as {@code true} + * @see ReactiveX operators documentation: Filter + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe filter(Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate is null"); + return RxJavaPlugins.onAssembly(new MaybeFilterSingle(this, predicate)); + } + + /** + * Returns a Single that is based on applying a specified function to the item emitted by the source Single, + * where that function returns a SingleSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMap} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Single, returns a SingleSource + * @return the Single returned from {@code mapper} when applied to the item emitted by the source Single + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single flatMap(Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMap(this, mapper)); + } + + /** + * Returns a Maybe that is based on applying a specified function to the item emitted by the source Single, + * where that function returns a MaybeSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Single, returns a MaybeSource + * @return the Maybe returned from {@code mapper} when applied to the item emitted by the source Single + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Maybe flatMapMaybe(final Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMapMaybe(this, mapper)); + } + + /** + * Returns a Flowable that emits items based on applying a specified function to the item emitted by the + * source Single, where that function returns a Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer + * and the {@code Publisher} returned by the mapper function is expected to honor it as well.
+ *
Scheduler:
+ *
{@code flatMapPublisher} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Single, returns a + * Flowable + * @return the Flowable returned from {@code func} when applied to the item emitted by the source Single + * @see ReactiveX operators documentation: FlatMap + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flatMapPublisher(Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMapPublisher(this, mapper)); + } + + /** + * Maps the success value of the upstream {@link Single} into an {@link Iterable} and emits its items as a + * {@link Flowable} sequence. + *

+ * + *

+ *
Backpressure:
+ *
The operator honors backpressure from downstream.
+ *
Scheduler:
+ *
{@code flattenAsFlowable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting Iterable + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Single + * @return the new Flowable instance + * @see ReactiveX operators documentation: FlatMap + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable flattenAsFlowable(final Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMapIterableFlowable(this, mapper)); + } + + /** + * Maps the success value of the upstream {@link Single} into an {@link Iterable} and emits its items as an + * {@link Observable} sequence. + *

+ * + *

+ *
Scheduler:
+ *
{@code flattenAsObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of item emitted by the resulting Iterable + * @param mapper + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Single + * @return the new Observable instance + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flattenAsObservable(final Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMapIterableObservable(this, mapper)); + } + + /** + * Returns an Observable that is based on applying a specified function to the item emitted by the source Single, + * where that function returns an ObservableSource. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper + * a function that, when applied to the item emitted by the source Single, returns an ObservableSource + * @return the Observable returned from {@code func} when applied to the item emitted by the source Single + * @see ReactiveX operators documentation: FlatMap + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable flatMapObservable(Function> mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMapObservable(this, mapper)); + } + + /** + * Returns a {@link Completable} that completes based on applying a specified function to the item emitted by the + * source {@link Single}, where that function returns a {@link Completable}. + *

+ * + *

+ *
Scheduler:
+ *
{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param mapper + * a function that, when applied to the item emitted by the source Single, returns a + * Completable + * @return the Completable returned from {@code func} when applied to the item emitted by the source Single + * @see ReactiveX operators documentation: FlatMap + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable flatMapCompletable(final Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleFlatMapCompletable(this, mapper)); + } + + /** + * Waits in a blocking fashion until the current Single signals a success value (which is returned) or + * an exception (which is propagated). + *

+ * + *

+ *
Scheduler:
+ *
{@code blockingGet} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the source signals an error, the operator wraps a checked {@link Exception} + * into {@link RuntimeException} and throws that. Otherwise, {@code RuntimeException}s and + * {@link Error}s are rethrown as they are.
+ *
+ * @return the success value + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final T blockingGet() { + BlockingMultiObserver observer = new BlockingMultiObserver(); + subscribe(observer); + return observer.blockingGet(); + } + + /** + * This method requires advanced knowledge about building operators, please consider + * other standard composition methods first; + * Returns a {@code Single} which, when subscribed to, invokes the {@link SingleOperator#apply(SingleObserver) apply(SingleObserver)} method + * of the provided {@link SingleOperator} for each individual downstream {@link Single} and allows the + * insertion of a custom operator by accessing the downstream's {@link SingleObserver} during this subscription phase + * and providing a new {@code SingleObserver}, containing the custom operator's intended business logic, that will be + * used in the subscription process going further upstream. + *

+ * + *

+ * Generally, such a new {@code SingleObserver} will wrap the downstream's {@code SingleObserver} and forwards the + * {@code onSuccess} and {@code onError} events from the upstream directly or according to the + * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the + * flow control calls of {@code dispose} and {@code isDisposed} that would have traveled upstream and perform + * additional actions depending on the same business logic requirements. + *

+ * Example: + *


+     * // Step 1: Create the consumer type that will be returned by the SingleOperator.apply():
+     *
+     * public final class CustomSingleObserver<T> implements SingleObserver<T>, Disposable {
+     *
+     *     // The downstream's SingleObserver that will receive the onXXX events
+     *     final SingleObserver<? super String> downstream;
+     *
+     *     // The connection to the upstream source that will call this class' onXXX methods
+     *     Disposable upstream;
+     *
+     *     // The constructor takes the downstream subscriber and usually any other parameters
+     *     public CustomSingleObserver(SingleObserver<? super String> downstream) {
+     *         this.downstream = downstream;
+     *     }
+     *
+     *     // In the subscription phase, the upstream sends a Disposable to this class
+     *     // and subsequently this class has to send a Disposable to the downstream.
+     *     // Note that relaying the upstream's Disposable directly is not allowed in RxJava
+     *     @Override
+     *     public void onSubscribe(Disposable d) {
+     *         if (upstream != null) {
+     *             d.dispose();
+     *         } else {
+     *             upstream = d;
+     *             downstream.onSubscribe(this);
+     *         }
+     *     }
+     *
+     *     // The upstream calls this with the next item and the implementation's
+     *     // responsibility is to emit an item to the downstream based on the intended
+     *     // business logic, or if it can't do so for the particular item,
+     *     // request more from the upstream
+     *     @Override
+     *     public void onSuccess(T item) {
+     *         String str = item.toString();
+     *         if (str.length() < 2) {
+     *             downstream.onSuccess(str);
+     *         } else {
+     *             // Single is usually expected to produce one of the onXXX events
+     *             downstream.onError(new NoSuchElementException());
+     *         }
+     *     }
+     *
+     *     // Some operators may handle the upstream's error while others
+     *     // could just forward it to the downstream.
+     *     @Override
+     *     public void onError(Throwable throwable) {
+     *         downstream.onError(throwable);
+     *     }
+     *
+     *     // Some operators may use their own resources which should be cleaned up if
+     *     // the downstream disposes the flow before it completed. Operators without
+     *     // resources can simply forward the dispose to the upstream.
+     *     // In some cases, a disposed flag may be set by this method so that other parts
+     *     // of this class may detect the dispose and stop sending events
+     *     // to the downstream.
+     *     @Override
+     *     public void dispose() {
+     *         upstream.dispose();
+     *     }
+     *
+     *     // Some operators may simply forward the call to the upstream while others
+     *     // can return the disposed flag set in dispose().
+     *     @Override
+     *     public boolean isDisposed() {
+     *         return upstream.isDisposed();
+     *     }
+     * }
+     *
+     * // Step 2: Create a class that implements the SingleOperator interface and
+     * //         returns the custom consumer type from above in its apply() method.
+     * //         Such class may define additional parameters to be submitted to
+     * //         the custom consumer type.
+     *
+     * final class CustomSingleOperator<T> implements SingleOperator<String> {
+     *     @Override
+     *     public SingleObserver<? super String> apply(SingleObserver<? super T> upstream) {
+     *         return new CustomSingleObserver<T>(upstream);
+     *     }
+     * }
+     *
+     * // Step 3: Apply the custom operator via lift() in a flow by creating an instance of it
+     * //         or reusing an existing one.
+     *
+     * Single.just(5)
+     * .lift(new CustomSingleOperator<Integer>())
+     * .test()
+     * .assertResult("5");
+     *
+     * Single.just(15)
+     * .lift(new CustomSingleOperator<Integer>())
+     * .test()
+     * .assertFailure(NoSuchElementException.class);
+     * 
+ *

+ * Creating custom operators can be complicated and it is recommended one consults the + * RxJava wiki: Writing operators page about + * the tools, requirements, rules, considerations and pitfalls of implementing them. + *

+ * Note that implementing custom operators via this {@code lift()} method adds slightly more overhead by requiring + * an additional allocation and indirection per assembled flows. Instead, extending the abstract {@code Single} + * class and creating a {@link SingleTransformer} with it is recommended. + *

+ * Note also that it is not possible to stop the subscription phase in {@code lift()} as the {@code apply()} method + * requires a non-null {@code SingleObserver} instance to be returned, which is then unconditionally subscribed to + * the upstream {@code Single}. For example, if the operator decided there is no reason to subscribe to the + * upstream source because of some optimization possibility or a failure to prepare the operator, it still has to + * return a {@code SingleObserver} that should immediately dispose the upstream's {@code Disposable} in its + * {@code onSubscribe} method. Again, using a {@code SingleTransformer} and extending the {@code Single} is + * a better option as {@link #subscribeActual} can decide to not subscribe to its upstream after all. + *

+ *
Scheduler:
+ *
{@code lift} does not operate by default on a particular {@link Scheduler}, however, the + * {@link SingleOperator} may use a {@code Scheduler} to support its own asynchronous behavior.
+ *
+ * + * @param the output value type + * @param lift the {@link SingleOperator} that receives the downstream's {@code SingleObserver} and should return + * a {@code SingleObserver} with custom behavior to be used as the consumer for the current + * {@code Single}. + * @return the new Single instance + * @see RxJava wiki: Writing operators + * @see #compose(SingleTransformer) + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single lift(final SingleOperator lift) { + ObjectHelper.requireNonNull(lift, "lift is null"); + return RxJavaPlugins.onAssembly(new SingleLift(this, lift)); + } + + /** + * Returns a Single that applies a specified function to the item emitted by the source Single and + * emits the result of this function application. + *

+ * + *

+ *
Scheduler:
+ *
{@code map} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the result value type + * @param mapper + * a function to apply to the item emitted by the Single + * @return a Single that emits the item from the source Single, transformed by the specified function + * @see ReactiveX operators documentation: Map + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single map(Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new SingleMap(this, mapper)); + } + + /** + * Maps the signal types of this Single into a {@link Notification} of the same kind + * and emits it as a single success value to downstream. + *

+ * + *

+ *
Scheduler:
+ *
{@code materialize} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Single instance + * @since 2.2.4 - experimental + * @see #dematerialize(Function) + */ + @Experimental + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single> materialize() { + return RxJavaPlugins.onAssembly(new SingleMaterialize(this)); + } + + /** + * Signals true if the current Single signals a success value that is Object-equals with the value + * provided. + *

+ * + *

+ *
Scheduler:
+ *
{@code contains} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param value the value to compare against the success value of this Single + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single contains(Object value) { + return contains(value, ObjectHelper.equalsPredicate()); + } + + /** + * Signals true if the current Single signals a success value that is equal with + * the value provided by calling a bi-predicate. + *

+ * + *

+ *
Scheduler:
+ *
{@code contains} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param value the value to compare against the success value of this Single + * @param comparer the function that receives the success value of this Single, the value provided + * and should return true if they are considered equal + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single contains(final Object value, final BiPredicate comparer) { + ObjectHelper.requireNonNull(value, "value is null"); + ObjectHelper.requireNonNull(comparer, "comparer is null"); + return RxJavaPlugins.onAssembly(new SingleContains(this, value, comparer)); + } + + /** + * Flattens this and another Single into a single Flowable, without any transformation. + *

+ * + *

+ * You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using + * the {@code mergeWith} method. + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code mergeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * a SingleSource to be merged + * @return that emits all of the items emitted by the source Singles + * @see ReactiveX operators documentation: Merge + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable mergeWith(SingleSource other) { + return merge(this, other); + } + + /** + * Modifies a Single to emit its item (or notify of its error) on a specified {@link Scheduler}, + * asynchronously. + *

+ * + *

+ *
Scheduler:
+ *
you specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to notify subscribers on + * @return the source Single modified so that its subscribers are notified on the specified + * {@link Scheduler} + * @throws NullPointerException if scheduler is null + * @see ReactiveX operators documentation: ObserveOn + * @see RxJava Threading Examples + * @see #subscribeOn + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Single observeOn(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new SingleObserveOn(this, scheduler)); + } + + /** + * Instructs a Single to emit an item (returned by a specified function) rather than invoking + * {@link SingleObserver#onError onError} if it encounters an error. + *

+ * + *

+ * By default, when a Single encounters an error that prevents it from emitting the expected item to its + * subscriber, the Single invokes its subscriber's {@link SingleObserver#onError} method, and then quits + * without invoking any more of its subscriber's methods. The {@code onErrorReturn} method changes this + * behavior. If you pass a function ({@code resumeFunction}) to a Single's {@code onErrorReturn} method, if + * the original Single encounters an error, instead of invoking its subscriber's + * {@link SingleObserver#onError} method, it will instead emit the return value of {@code resumeFunction}. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorReturn} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param resumeFunction + * a function that returns an item that the new Single will emit if the source Single encounters + * an error + * @return the original Single with appropriately modified behavior + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single onErrorReturn(final Function resumeFunction) { + ObjectHelper.requireNonNull(resumeFunction, "resumeFunction is null"); + return RxJavaPlugins.onAssembly(new SingleOnErrorReturn(this, resumeFunction, null)); + } + + /** + * Signals the specified value as success in case the current Single signals an error. + *

+ * + *

+ *
Scheduler:
+ *
{@code onErrorReturnItem} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param value the value to signal if the current Single fails + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single onErrorReturnItem(final T value) { + ObjectHelper.requireNonNull(value, "value is null"); + return RxJavaPlugins.onAssembly(new SingleOnErrorReturn(this, null, value)); + } + + /** + * Instructs a Single to pass control to another Single rather than invoking + * {@link SingleObserver#onError(Throwable)} if it encounters an error. + *

+ * + *

+ * By default, when a Single encounters an error that prevents it from emitting the expected item to + * its {@link SingleObserver}, the Single invokes its SingleObserver's {@code onError} method, and then quits + * without invoking any more of its SingleObserver's methods. The {@code onErrorResumeNext} method changes this + * behavior. If you pass another Single ({@code resumeSingleInCaseOfError}) to a Single's + * {@code onErrorResumeNext} method, if the original Single encounters an error, instead of invoking its + * SingleObserver's {@code onError} method, it will instead relinquish control to {@code resumeSingleInCaseOfError} which + * will invoke the SingleObserver's {@link SingleObserver#onSuccess onSuccess} method if it is able to do so. In such a case, + * because no Single necessarily invokes {@code onError}, the SingleObserver may never know that an error + * happened. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param resumeSingleInCaseOfError a Single that will take control if source Single encounters an error. + * @return the original Single, with appropriately modified behavior. + * @see ReactiveX operators documentation: Catch + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single onErrorResumeNext(final Single resumeSingleInCaseOfError) { + ObjectHelper.requireNonNull(resumeSingleInCaseOfError, "resumeSingleInCaseOfError is null"); + return onErrorResumeNext(Functions.justFunction(resumeSingleInCaseOfError)); + } + + /** + * Instructs a Single to pass control to another Single rather than invoking + * {@link SingleObserver#onError(Throwable)} if it encounters an error. + *

+ * + *

+ * By default, when a Single encounters an error that prevents it from emitting the expected item to + * its {@link SingleObserver}, the Single invokes its SingleObserver's {@code onError} method, and then quits + * without invoking any more of its SingleObserver's methods. The {@code onErrorResumeNext} method changes this + * behavior. If you pass a function that will return another Single ({@code resumeFunctionInCaseOfError}) to a Single's + * {@code onErrorResumeNext} method, if the original Single encounters an error, instead of invoking its + * SingleObserver's {@code onError} method, it will instead relinquish control to {@code resumeSingleInCaseOfError} which + * will invoke the SingleObserver's {@link SingleObserver#onSuccess onSuccess} method if it is able to do so. In such a case, + * because no Single necessarily invokes {@code onError}, the SingleObserver may never know that an error + * happened. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. + *

+ *
Scheduler:
+ *
{@code onErrorResumeNext} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param resumeFunctionInCaseOfError a function that returns a Single that will take control if source Single encounters an error. + * @return the original Single, with appropriately modified behavior. + * @see ReactiveX operators documentation: Catch + * @since .20 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single onErrorResumeNext( + final Function> resumeFunctionInCaseOfError) { + ObjectHelper.requireNonNull(resumeFunctionInCaseOfError, "resumeFunctionInCaseOfError is null"); + return RxJavaPlugins.onAssembly(new SingleResumeNext(this, resumeFunctionInCaseOfError)); + } + + /** + * Nulls out references to the upstream producer and downstream SingleObserver if + * the sequence is terminated or downstream calls dispose(). + *

+ * + *

+ *
Scheduler:
+ *
{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.5 - experimental + * @return a Single which nulls out references to the upstream producer and downstream SingleObserver if + * the sequence is terminated or downstream calls dispose() + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single onTerminateDetach() { + return RxJavaPlugins.onAssembly(new SingleDetach(this)); + } + + /** + * Repeatedly re-subscribes to the current Single and emits each success value. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Flowable instance + * @since 2.0 + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeat() { + return toFlowable().repeat(); + } + + /** + * Re-subscribes to the current Single at most the given number of times and emits each success value. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code repeat} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param times the number of times to re-subscribe to the current Single + * @return the new Flowable instance + * @since 2.0 + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeat(long times) { + return toFlowable().repeat(times); + } + + /** + * Re-subscribes to the current Single if + * the Publisher returned by the handler function signals a value in response to a + * value signalled through the Flowable the handle receives. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer. + * The {@code Publisher} returned by the handler function is expected to honor backpressure as well.
+ *
Scheduler:
+ *
{@code repeatWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param handler the function that is called with a Flowable that signals a value when the Single + * signalled a success value and returns a Publisher that has to signal a value to + * trigger a resubscription to the current Single, otherwise the terminal signal of + * the Publisher will be the terminal signal of the sequence as well. + * @return the new Flowable instance + * @since 2.0 + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeatWhen(Function, ? extends Publisher> handler) { + return toFlowable().repeatWhen(handler); + } + + /** + * Re-subscribes to the current Single until the given BooleanSupplier returns true. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code repeatUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param stop the BooleanSupplier called after the current Single succeeds and if returns false, + * the Single is re-subscribed; otherwise the sequence completes. + * @return the new Flowable instance + * @since 2.0 + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Flowable repeatUntil(BooleanSupplier stop) { + return toFlowable().repeatUntil(stop); + } + + /** + * Repeatedly re-subscribes to the current Single indefinitely if it fails with an onError. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single retry() { + return toSingle(toFlowable().retry()); + } + + /** + * Repeatedly re-subscribe at most the specified times to the current Single + * if it fails with an onError. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param times the number of times to resubscribe if the current Single fails + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single retry(long times) { + return toSingle(toFlowable().retry(times)); + } + + /** + * Re-subscribe to the current Single if the given predicate returns true when the Single fails + * with an onError. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param predicate the predicate called with the resubscription count and the failure Throwable + * and should return true if a resubscription should happen + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single retry(BiPredicate predicate) { + return toSingle(toFlowable().retry(predicate)); + } + + /** + * Repeatedly re-subscribe at most times or until the predicate returns false, whichever happens first + * if it fails with an onError. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.1.8 - experimental + * @param times the number of times to resubscribe if the current Single fails + * @param predicate the predicate called with the failure Throwable + * and should return true if a resubscription should happen + * @return the new Single instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single retry(long times, Predicate predicate) { + return toSingle(toFlowable().retry(times, predicate)); + } + + /** + * Re-subscribe to the current Single if the given predicate returns true when the Single fails + * with an onError. + *

+ * + *

+ *
Scheduler:
+ *
{@code retry} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param predicate the predicate called with the failure Throwable + * and should return true if a resubscription should happen + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single retry(Predicate predicate) { + return toSingle(toFlowable().retry(predicate)); + } + + /** + * Re-subscribes to the current Single if and when the Publisher returned by the handler + * function signals a value. + *

+ * + *

+ * If the Publisher signals an onComplete, the resulting Single will signal a NoSuchElementException. + *

+ * Note that the inner {@code Publisher} returned by the handler function should signal + * either {@code onNext}, {@code onError} or {@code onComplete} in response to the received + * {@code Throwable} to indicate the operator should retry or terminate. If the upstream to + * the operator is asynchronous, signalling onNext followed by onComplete immediately may + * result in the sequence to be completed immediately. Similarly, if this inner + * {@code Publisher} signals {@code onError} or {@code onComplete} while the upstream is + * active, the sequence is terminated with the same signal immediately. + *

+ * The following example demonstrates how to retry an asynchronous source with a delay: + *


+     * Single.timer(1, TimeUnit.SECONDS)
+     *     .doOnSubscribe(s -> System.out.println("subscribing"))
+     *     .map(v -> { throw new RuntimeException(); })
+     *     .retryWhen(errors -> {
+     *         AtomicInteger counter = new AtomicInteger();
+     *         return errors
+     *                   .takeWhile(e -> counter.getAndIncrement() != 3)
+     *                   .flatMap(e -> {
+     *                       System.out.println("delay retry by " + counter.get() + " second(s)");
+     *                       return Flowable.timer(counter.get(), TimeUnit.SECONDS);
+     *                   });
+     *     })
+     *     .blockingGet();
+     * 
+ *
+ *
Scheduler:
+ *
{@code retryWhen} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param handler the function that receives a Flowable of the error the Single emits and should + * return a Publisher that should signal a normal value (in response to the + * throwable the Flowable emits) to trigger a resubscription or signal an error to + * be the output of the resulting Single + * @return the new Single instance + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single retryWhen(Function, ? extends Publisher> handler) { + return toSingle(toFlowable().retryWhen(handler)); + } + + /** + * Subscribes to a Single but ignore its emission or notification. + *

+ * + *

+ * If the Single emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Disposable} reference can request the {@link Single} stop work. + * @see ReactiveX operators documentation: Subscribe + */ + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe() { + return subscribe(Functions.emptyConsumer(), Functions.ON_ERROR_MISSING); + } + + /** + * Subscribes to a Single and provides a composite callback to handle the item it emits + * or any error notification it issues. + *

+ * + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onCallback + * the callback that receives either the success value or the failure Throwable + * (whichever is not null) + * @return a {@link Disposable} reference can request the {@link Single} stop work. + * @see ReactiveX operators documentation: Subscribe + * @throws NullPointerException + * if {@code onCallback} is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(final BiConsumer onCallback) { + ObjectHelper.requireNonNull(onCallback, "onCallback is null"); + + BiConsumerSingleObserver observer = new BiConsumerSingleObserver(onCallback); + subscribe(observer); + return observer; + } + + /** + * Subscribes to a Single and provides a callback to handle the item it emits. + *

+ * + *

+ * If the Single emits an error, it is wrapped into an + * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} + * and routed to the RxJavaPlugins.onError handler. + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onSuccess + * the {@code Consumer} you have designed to accept the emission from the Single + * @return a {@link Disposable} reference can request the {@link Single} stop work. + * @throws NullPointerException + * if {@code onSuccess} is null + * @see ReactiveX operators documentation: Subscribe + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(Consumer onSuccess) { + return subscribe(onSuccess, Functions.ON_ERROR_MISSING); + } + + /** + * Subscribes to a Single and provides callbacks to handle the item it emits or any error notification it + * issues. + *

+ * + *

+ *
Scheduler:
+ *
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param onSuccess + * the {@code Consumer} you have designed to accept the emission from the Single + * @param onError + * the {@code Consumer} you have designed to accept any error notification from the + * Single + * @return a {@link Disposable} reference can request the {@link Single} stop work. + * @see ReactiveX operators documentation: Subscribe + * @throws NullPointerException + * if {@code onSuccess} is null, or + * if {@code onError} is null + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Disposable subscribe(final Consumer onSuccess, final Consumer onError) { + ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + + ConsumerSingleObserver observer = new ConsumerSingleObserver(onSuccess, onError); + subscribe(observer); + return observer; + } + + @SchedulerSupport(SchedulerSupport.NONE) + @Override + public final void subscribe(SingleObserver observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); + + observer = RxJavaPlugins.onSubscribe(this, observer); + + ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null SingleObserver. Please check the handler provided to RxJavaPlugins.setOnSingleSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins"); + + try { + subscribeActual(observer); + } catch (NullPointerException ex) { + throw ex; + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + NullPointerException npe = new NullPointerException("subscribeActual failed"); + npe.initCause(ex); + throw npe; + } + } + + /** + * Implement this method in subclasses to handle the incoming {@link SingleObserver}s. + *

There is no need to call any of the plugin hooks on the current {@code Single} instance or + * the {@code SingleObserver}; all hooks and basic safeguards have been + * applied by {@link #subscribe(SingleObserver)} before this method gets called. + * @param observer the SingleObserver to handle, not null + */ + protected abstract void subscribeActual(@NonNull SingleObserver observer); + + /** + * Subscribes a given SingleObserver (subclass) to this Single and returns the given + * SingleObserver as is. + *

+ * + *

Usage example: + *


+     * Single<Integer> source = Single.just(1);
+     * CompositeDisposable composite = new CompositeDisposable();
+     *
+     * DisposableSingleObserver<Integer> ds = new DisposableSingleObserver<>() {
+     *     // ...
+     * };
+     *
+     * composite.add(source.subscribeWith(ds));
+     * 
+ *
+ *
Scheduler:
+ *
{@code subscribeWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the type of the SingleObserver to use and return + * @param observer the SingleObserver (subclass) to use and return, not null + * @return the input {@code observer} + * @throws NullPointerException if {@code observer} is null + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final > E subscribeWith(E observer) { + subscribe(observer); + return observer; + } + + /** + * Asynchronously subscribes subscribers to this Single on the specified {@link Scheduler}. + *

+ * + *

+ *
Scheduler:
+ *
You specify which {@link Scheduler} this operator will use.
+ *
+ * + * @param scheduler + * the {@link Scheduler} to perform subscription actions on + * @return the source Single modified so that its subscriptions happen on the specified {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + * @see RxJava Threading Examples + * @see #observeOn + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Single subscribeOn(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new SingleSubscribeOn(this, scheduler)); + } + + /** + * Returns a Single that emits the item emitted by the source Single until a Completable terminates. Upon + * termination of {@code other}, this will emit a {@link CancellationException} rather than go to + * {@link SingleObserver#onSuccess(Object)}. + *

+ * + *

+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the Completable whose termination will cause {@code takeUntil} to emit the item from the source + * Single + * @return a Single that emits the item emitted by the source Single until such time as {@code other} terminates. + * @see ReactiveX operators documentation: TakeUntil + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single takeUntil(final CompletableSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return takeUntil(new CompletableToFlowable(other)); + } + + /** + * Returns a Single that emits the item emitted by the source Single until a Publisher emits an item. Upon + * emission of an item from {@code other}, this will emit a {@link CancellationException} rather than go to + * {@link SingleObserver#onSuccess(Object)}. + *

+ * + *

+ *
Backpressure:
+ *
The {@code other} publisher is consumed in an unbounded fashion but will be + * cancelled after the first item it produced.
+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the Publisher whose first emitted item will cause {@code takeUntil} to emit the item from the source + * Single + * @param + * the type of items emitted by {@code other} + * @return a Single that emits the item emitted by the source Single until such time as {@code other} emits + * its first item + * @see ReactiveX operators documentation: TakeUntil + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single takeUntil(final Publisher other) { + ObjectHelper.requireNonNull(other, "other is null"); + return RxJavaPlugins.onAssembly(new SingleTakeUntil(this, other)); + } + + /** + * Returns a Single that emits the item emitted by the source Single until a second Single emits an item. Upon + * emission of an item from {@code other}, this will emit a {@link CancellationException} rather than go to + * {@link SingleObserver#onSuccess(Object)}. + *

+ * + *

+ *
Scheduler:
+ *
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param other + * the Single whose emitted item will cause {@code takeUntil} to emit the item from the source Single + * @param + * the type of item emitted by {@code other} + * @return a Single that emits the item emitted by the source Single until such time as {@code other} emits its item + * @see ReactiveX operators documentation: TakeUntil + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.NONE) + public final Single takeUntil(final SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return takeUntil(new SingleToFlowable(other)); + } + + /** + * Signals a TimeoutException if the current Single doesn't signal a success value within the + * specified timeout window. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeout} signals the TimeoutException on the {@code computation} {@link Scheduler}.
+ *
+ * @param timeout the timeout amount + * @param unit the time unit + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Single timeout(long timeout, TimeUnit unit) { + return timeout0(timeout, unit, Schedulers.computation(), null); + } + + /** + * Signals a TimeoutException if the current Single doesn't signal a success value within the + * specified timeout window. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeout} signals the TimeoutException on the {@link Scheduler} you specify.
+ *
+ * @param timeout the timeout amount + * @param unit the time unit + * @param scheduler the target scheduler where the timeout is awaited and the TimeoutException + * signalled + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Single timeout(long timeout, TimeUnit unit, Scheduler scheduler) { + return timeout0(timeout, unit, scheduler, null); + } + + /** + * Runs the current Single and if it doesn't signal within the specified timeout window, it is + * disposed and the other SingleSource subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeout} subscribes to the other SingleSource on the {@link Scheduler} you specify.
+ *
+ * @param timeout the timeout amount + * @param unit the time unit + * @param scheduler the scheduler where the timeout is awaited and the subscription to other happens + * @param other the other SingleSource that gets subscribed to if the current Single times out + * @return the new Single instance + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Single timeout(long timeout, TimeUnit unit, Scheduler scheduler, SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(timeout, unit, scheduler, other); + } + + /** + * Runs the current Single and if it doesn't signal within the specified timeout window, it is + * disposed and the other SingleSource subscribed to. + *

+ * + *

+ *
Scheduler:
+ *
{@code timeout} subscribes to the other SingleSource on + * the {@code computation} {@link Scheduler}.
+ *
+ * @param timeout the timeout amount + * @param unit the time unit + * @param other the other SingleSource that gets subscribed to if the current Single times out + * @return the new Single instance + * @throws NullPointerException + * if other is null, or + * if unit is null, or + * if scheduler is null + * @since 2.0 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Single timeout(long timeout, TimeUnit unit, SingleSource other) { + ObjectHelper.requireNonNull(other, "other is null"); + return timeout0(timeout, unit, Schedulers.computation(), other); + } + + private Single timeout0(final long timeout, final TimeUnit unit, final Scheduler scheduler, final SingleSource other) { + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new SingleTimeout(this, timeout, unit, scheduler, other)); + } + + /** + * Calls the specified converter function with the current Single instance + * during assembly time and returns its result. + *

+ * + *

+ *
Scheduler:
+ *
{@code to} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the result type + * @param convert the function that is called with the current Single instance during + * assembly time that should return some value to be the result + * + * @return the value returned by the convert function + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final R to(Function, R> convert) { + try { + return ObjectHelper.requireNonNull(convert, "convert is null").apply(this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Returns a {@link Completable} that discards result of the {@link Single} + * and calls {@code onComplete} when this source {@link Single} calls + * {@code onSuccess}. Error terminal event is propagated. + *

+ * + *

+ *
Scheduler:
+ *
{@code toCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Completable} that calls {@code onComplete} on it's subscriber when the source {@link Single} + * calls {@code onSuccess}. + * @since 2.0 + * @deprecated see {@link #ignoreElement()} instead, will be removed in 3.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @Deprecated + public final Completable toCompletable() { + return RxJavaPlugins.onAssembly(new CompletableFromSingle(this)); + } + + /** + * Returns a {@link Completable} that ignores the success value of this {@link Single} + * and calls {@code onComplete} instead on the returned {@code Completable}. + *

+ * + *

+ *
Scheduler:
+ *
{@code ignoreElement} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Completable} that calls {@code onComplete} on it's observer when the source {@link Single} + * calls {@code onSuccess}. + * @since 2.1.13 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Completable ignoreElement() { + return RxJavaPlugins.onAssembly(new CompletableFromSingle(this)); + } + + /** + * Converts this Single into a {@link Flowable}. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer.
+ *
Scheduler:
+ *
{@code toFlowable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Flowable} that emits a single item T or an error. + */ + @BackpressureSupport(BackpressureKind.FULL) + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public final Flowable toFlowable() { + if (this instanceof FuseToFlowable) { + return ((FuseToFlowable)this).fuseToFlowable(); + } + return RxJavaPlugins.onAssembly(new SingleToFlowable(this)); + } + + /** + * Returns a {@link Future} representing the single value emitted by this {@code Single}. + *

+ * + *

+ *
Scheduler:
+ *
{@code toFuture} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Future} that expects a single item to be emitted by this {@code Single} + * @see ReactiveX documentation: To + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Future toFuture() { + return subscribeWith(new FutureSingleObserver()); + } + + /** + * Converts this Single into a {@link Maybe}. + *

+ * + *

+ *
Scheduler:
+ *
{@code toMaybe} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return a {@link Maybe} that emits a single item T or an error. + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public final Maybe toMaybe() { + if (this instanceof FuseToMaybe) { + return ((FuseToMaybe)this).fuseToMaybe(); + } + return RxJavaPlugins.onAssembly(new MaybeFromSingle(this)); + } + /** + * Converts this Single into an {@link Observable}. + *

+ * + *

+ *
Scheduler:
+ *
{@code toObservable} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return an {@link Observable} that emits a single item T or an error. + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @SuppressWarnings("unchecked") + public final Observable toObservable() { + if (this instanceof FuseToObservable) { + return ((FuseToObservable)this).fuseToObservable(); + } + return RxJavaPlugins.onAssembly(new SingleToObservable(this)); + } + + /** + * Returns a Single which makes sure when a SingleObserver disposes the Disposable, + * that call is propagated up on the specified scheduler. + *

+ * + *

+ *
Scheduler:
+ *
{@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.
+ *
+ *

History: 2.0.9 - experimental + * @param scheduler the target scheduler where to execute the disposal + * @return the new Single instance + * @throws NullPointerException if scheduler is null + * @since 2.2 + */ + @CheckReturnValue + @NonNull + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Single unsubscribeOn(final Scheduler scheduler) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new SingleUnsubscribeOn(this, scheduler)); + } + + /** + * Returns a Single that emits the result of applying a specified function to the pair of items emitted by + * the source Single and another specified Single. + *

+ * + *

+ *
Scheduler:
+ *
{@code zipWith} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param + * the type of items emitted by the {@code other} Single + * @param + * the type of items emitted by the resulting Single + * @param other + * the other SingleSource + * @param zipper + * a function that combines the pairs of items from the two SingleSources to generate the items to + * be emitted by the resulting Single + * @return a Single that pairs up values from the source Single and the {@code other} SingleSource + * and emits the results of {@code zipFunction} applied to these pairs + * @see ReactiveX operators documentation: Zip + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Single zipWith(SingleSource other, BiFunction zipper) { + return zip(this, other, zipper); + } + + // ------------------------------------------------------------------------- + // Fluent test support, super handy and reduces test preparation boilerplate + // ------------------------------------------------------------------------- + /** + * Creates a TestObserver and subscribes + * it to this Single. + *

+ * + *

+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new TestObserver instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final TestObserver test() { + TestObserver to = new TestObserver(); + subscribe(to); + return to; + } + + /** + * Creates a TestObserver optionally in cancelled state, then subscribes it to this Single. + *

+ * + *

+ *
Scheduler:
+ *
{@code test} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param cancelled if true, the TestObserver will be cancelled before subscribing to this + * Single. + * @return the new TestObserver instance + * @since 2.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final TestObserver test(boolean cancelled) { + TestObserver to = new TestObserver(); + + if (cancelled) { + to.cancel(); + } + + subscribe(to); + return to; + } + + private static Single toSingle(Flowable source) { + return RxJavaPlugins.onAssembly(new FlowableSingleSingle(source, null)); + } +} diff --git a/src/main/java/io/reactivex/SingleConverter.java b/src/main/java/io/reactivex/SingleConverter.java new file mode 100755 index 0000000..1e3944f --- /dev/null +++ b/src/main/java/io/reactivex/SingleConverter.java @@ -0,0 +1,35 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link Single#as} operator to turn a Single into another + * value fluently. + *

History: 2.1.7 - experimental + * @param the upstream type + * @param the output type + * @since 2.2 + */ +public interface SingleConverter { + /** + * Applies a function to the upstream Single and returns a converted value of type {@code R}. + * + * @param upstream the upstream Single instance + * @return the converted value + */ + @NonNull + R apply(@NonNull Single upstream); +} diff --git a/src/main/java/io/reactivex/SingleEmitter.java b/src/main/java/io/reactivex/SingleEmitter.java new file mode 100755 index 0000000..9c1ded1 --- /dev/null +++ b/src/main/java/io/reactivex/SingleEmitter.java @@ -0,0 +1,101 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Cancellable; + +/** + * Abstraction over an RxJava {@link SingleObserver} that allows associating + * a resource with it. + *

+ * All methods are safe to call from multiple threads, but note that there is no guarantee + * whose terminal event will win and get delivered to the downstream. + *

+ * Calling {@link #onSuccess(Object)} multiple times has no effect. + * Calling {@link #onError(Throwable)} multiple times or after {@code onSuccess} will route the + * exception into the global error handler via {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + *

+ * The emitter allows the registration of a single resource, in the form of a {@link Disposable} + * or {@link Cancellable} via {@link #setDisposable(Disposable)} or {@link #setCancellable(Cancellable)} + * respectively. The emitter implementations will dispose/cancel this instance when the + * downstream cancels the flow or after the event generator logic calls {@link #onSuccess(Object)}, + * {@link #onError(Throwable)}, or when {@link #tryOnError(Throwable)} succeeds. + *

+ * Only one {@code Disposable} or {@code Cancellable} object can be associated with the emitter at + * a time. Calling either {@code set} method will dispose/cancel any previous object. If there + * is a need for handling multiple resources, one can create a {@link io.reactivex.disposables.CompositeDisposable} + * and associate that with the emitter instead. + *

+ * The {@link Cancellable} is logically equivalent to {@code Disposable} but allows using cleanup logic that can + * throw a checked exception (such as many {@code close()} methods on Java IO components). Since + * the release of resources happens after the terminal events have been delivered or the sequence gets + * cancelled, exceptions throw within {@code Cancellable} are routed to the global error handler via + * {@link io.reactivex.plugins.RxJavaPlugins#onError(Throwable)}. + * + * @param the value type to emit + */ +public interface SingleEmitter { + + /** + * Signal a success value. + * @param t the value, not null + */ + void onSuccess(@NonNull T t); + + /** + * Signal an exception. + * @param t the exception, not null + */ + void onError(@NonNull Throwable t); + + /** + * Sets a Disposable on this emitter; any previous Disposable + * or Cancellable will be disposed/cancelled. + * @param d the disposable, null is allowed + */ + void setDisposable(@Nullable Disposable d); + + /** + * Sets a Cancellable on this emitter; any previous {@link Disposable} + * or {@link Cancellable} will be disposed/cancelled. + * @param c the cancellable resource, null is allowed + */ + void setCancellable(@Nullable Cancellable c); + + /** + * Returns true if the downstream disposed the sequence or the + * emitter was terminated via {@link #onSuccess(Object)}, {@link #onError(Throwable)}, + * or a successful {@link #tryOnError(Throwable)}. + *

This method is thread-safe. + * @return true if the downstream disposed the sequence or the emitter was terminated + */ + boolean isDisposed(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

+ * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); +} diff --git a/src/main/java/io/reactivex/SingleObserver.java b/src/main/java/io/reactivex/SingleObserver.java new file mode 100755 index 0000000..6fefe16 --- /dev/null +++ b/src/main/java/io/reactivex/SingleObserver.java @@ -0,0 +1,85 @@ +/** + * 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; + +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; + +/** + * Provides a mechanism for receiving push-based notification of a single value or an error. + *

+ * When a {@code SingleObserver} is subscribed to a {@link SingleSource} through the {@link SingleSource#subscribe(SingleObserver)} method, + * the {@code SingleSource} calls {@link #onSubscribe(Disposable)} with a {@link Disposable} that allows + * disposing the sequence at any time. A well-behaved + * {@code SingleSource} will call a {@code SingleObserver}'s {@link #onSuccess(Object)} method exactly once or the {@code SingleObserver}'s + * {@link #onError} method exactly once as they are considered mutually exclusive terminal signals. + *

+ * Calling the {@code SingleObserver}'s method must happen in a serialized fashion, that is, they must not + * be invoked concurrently by multiple threads in an overlapping fashion and the invocation pattern must + * adhere to the following protocol: + *

    onSubscribe (onSuccess | onError)?
+ *

+ * Subscribing a {@code SingleObserver} to multiple {@code SingleSource}s is not recommended. If such reuse + * happens, it is the duty of the {@code SingleObserver} implementation to be ready to receive multiple calls to + * its methods and ensure proper concurrent behavior of its business logic. + *

+ * Calling {@link #onSubscribe(Disposable)}, {@link #onSuccess(Object)} or {@link #onError(Throwable)} with a + * {@code null} argument is forbidden. + *

+ * The implementations of the {@code onXXX} methods should avoid throwing runtime exceptions other than the following cases: + *

    + *
  • If the argument is {@code null}, the methods can throw a {@code NullPointerException}. + * Note though that RxJava prevents {@code null}s to enter into the flow and thus there is generally no + * need to check for nulls in flows assembled from standard sources and intermediate operators. + *
  • + *
  • If there is a fatal error (such as {@code VirtualMachineError}).
  • + *
+ * @see ReactiveX documentation: Observable + * @param + * the type of item the SingleObserver expects to observe + * @since 2.0 + */ +public interface SingleObserver { + + /** + * Provides the SingleObserver with the means of cancelling (disposing) the + * connection (channel) with the Single in both + * synchronous (from within {@code onSubscribe(Disposable)} itself) and asynchronous manner. + * @param d the Disposable instance whose {@link Disposable#dispose()} can + * be called anytime to cancel the connection + * @since 2.0 + */ + void onSubscribe(@NonNull Disposable d); + + /** + * Notifies the SingleObserver with a single item and that the {@link Single} has finished sending + * push-based notifications. + *

+ * The {@link Single} will not call this method if it calls {@link #onError}. + * + * @param t + * the item emitted by the Single + */ + void onSuccess(@NonNull T t); + + /** + * Notifies the SingleObserver that the {@link Single} has experienced an error condition. + *

+ * If the {@link Single} calls this method, it will not thereafter call {@link #onSuccess}. + * + * @param e + * the exception encountered by the Single + */ + void onError(@NonNull Throwable e); +} diff --git a/src/main/java/io/reactivex/SingleOnSubscribe.java b/src/main/java/io/reactivex/SingleOnSubscribe.java new file mode 100755 index 0000000..aa12a0d --- /dev/null +++ b/src/main/java/io/reactivex/SingleOnSubscribe.java @@ -0,0 +1,33 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * A functional interface that has a {@code subscribe()} method that receives + * an instance of a {@link SingleEmitter} instance that allows pushing + * an event in a cancellation-safe manner. + * + * @param the value type pushed + */ +public interface SingleOnSubscribe { + + /** + * Called for each SingleObserver that subscribes. + * @param emitter the safe emitter instance, never null + * @throws Exception on error + */ + void subscribe(@NonNull SingleEmitter emitter) throws Exception; +} + diff --git a/src/main/java/io/reactivex/SingleOperator.java b/src/main/java/io/reactivex/SingleOperator.java new file mode 100755 index 0000000..92ccfe7 --- /dev/null +++ b/src/main/java/io/reactivex/SingleOperator.java @@ -0,0 +1,33 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Interface to map/wrap a downstream observer to an upstream observer. + * + * @param the value type of the downstream + * @param the value type of the upstream + */ +public interface SingleOperator { + /** + * Applies a function to the child SingleObserver and returns a new parent SingleObserver. + * @param observer the child SingleObserver instance + * @return the parent SingleObserver instance + * @throws Exception on failure + */ + @NonNull + SingleObserver apply(@NonNull SingleObserver observer) throws Exception; +} diff --git a/src/main/java/io/reactivex/SingleSource.java b/src/main/java/io/reactivex/SingleSource.java new file mode 100755 index 0000000..befdd58 --- /dev/null +++ b/src/main/java/io/reactivex/SingleSource.java @@ -0,0 +1,35 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Represents a basic {@link Single} source base interface, + * consumable via an {@link SingleObserver}. + *

+ * This class also serves the base type for custom operators wrapped into + * Single via {@link Single#create(SingleOnSubscribe)}. + * + * @param the element type + * @since 2.0 + */ +public interface SingleSource { + + /** + * Subscribes the given SingleObserver to this SingleSource instance. + * @param observer the SingleObserver, not null + * @throws NullPointerException if {@code observer} is null + */ + void subscribe(@NonNull SingleObserver observer); +} diff --git a/src/main/java/io/reactivex/SingleTransformer.java b/src/main/java/io/reactivex/SingleTransformer.java new file mode 100755 index 0000000..bb0a7c1 --- /dev/null +++ b/src/main/java/io/reactivex/SingleTransformer.java @@ -0,0 +1,33 @@ +/** + * 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; + +import io.reactivex.annotations.*; + +/** + * Interface to compose Singles. + * + * @param the upstream value type + * @param the downstream value type + */ +public interface SingleTransformer { + /** + * Applies a function to the upstream Single and returns a SingleSource with + * optionally different element type. + * @param upstream the upstream Single instance + * @return the transformed SingleSource instance + */ + @NonNull + SingleSource apply(@NonNull Single upstream); +} diff --git a/src/main/java/io/reactivex/annotations/BackpressureKind.java b/src/main/java/io/reactivex/annotations/BackpressureKind.java new file mode 100755 index 0000000..6f04d81 --- /dev/null +++ b/src/main/java/io/reactivex/annotations/BackpressureKind.java @@ -0,0 +1,48 @@ +/** + * 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.annotations; + +/** + * Enumeration for various kinds of backpressure support. + * @since 2.0 + */ +public enum BackpressureKind { + /** + * The backpressure-related requests pass through this operator without change. + */ + PASS_THROUGH, + /** + * The operator fully supports backpressure and may coordinate downstream requests + * with upstream requests through batching, arbitration or by other means. + */ + FULL, + /** + * The operator performs special backpressure management; see the associated javadoc. + */ + SPECIAL, + /** + * The operator requests Long.MAX_VALUE from upstream but respects the backpressure + * of the downstream. + */ + UNBOUNDED_IN, + /** + * The operator will emit a MissingBackpressureException if the downstream didn't request + * enough or in time. + */ + ERROR, + /** + * The operator ignores all kinds of backpressure and may overflow the downstream. + */ + NONE +} diff --git a/src/main/java/io/reactivex/annotations/BackpressureSupport.java b/src/main/java/io/reactivex/annotations/BackpressureSupport.java new file mode 100755 index 0000000..17b3351 --- /dev/null +++ b/src/main/java/io/reactivex/annotations/BackpressureSupport.java @@ -0,0 +1,31 @@ +/** + * 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.annotations; + +import java.lang.annotation.*; + +/** + * Indicates the backpressure support kind of the associated operator or class. + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface BackpressureSupport { + /** + * The backpressure supported by this method or class. + * @return backpressure supported by this method or class. + */ + BackpressureKind value(); +} diff --git a/src/main/java/io/reactivex/annotations/Beta.java b/src/main/java/io/reactivex/annotations/Beta.java new file mode 100755 index 0000000..dea9484 --- /dev/null +++ b/src/main/java/io/reactivex/annotations/Beta.java @@ -0,0 +1,22 @@ +/** + * 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.annotations; + +/** + * Indicates the feature is in beta state: it will be most likely stay but + * the signature may change between versions without warning. + */ +public @interface Beta { + +} diff --git a/src/main/java/io/reactivex/annotations/CheckReturnValue.java b/src/main/java/io/reactivex/annotations/CheckReturnValue.java new file mode 100755 index 0000000..005f079 --- /dev/null +++ b/src/main/java/io/reactivex/annotations/CheckReturnValue.java @@ -0,0 +1,32 @@ +/** + * 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.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks methods whose return values should be checked. + *

History: 2.0.2 - experimental + * @since 2.1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target(ElementType.METHOD) +public @interface CheckReturnValue { + +} diff --git a/src/main/java/io/reactivex/annotations/Experimental.java b/src/main/java/io/reactivex/annotations/Experimental.java new file mode 100755 index 0000000..b5b27b1 --- /dev/null +++ b/src/main/java/io/reactivex/annotations/Experimental.java @@ -0,0 +1,22 @@ +/** + * 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.annotations; + +/** + * Indicates the feature is in experimental state: its existence, signature or behavior + * might change without warning from one release to the next. + */ +public @interface Experimental { + +} diff --git a/src/main/java/io/reactivex/annotations/NonNull.java b/src/main/java/io/reactivex/annotations/NonNull.java new file mode 100755 index 0000000..adfe9ff --- /dev/null +++ b/src/main/java/io/reactivex/annotations/NonNull.java @@ -0,0 +1,33 @@ +/** + * 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.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Indicates that a field/parameter/variable/return type is never null. + */ +@Documented +@Target(value = {FIELD, METHOD, PARAMETER, LOCAL_VARIABLE}) +@Retention(value = CLASS) +public @interface NonNull { } + diff --git a/src/main/java/io/reactivex/annotations/Nullable.java b/src/main/java/io/reactivex/annotations/Nullable.java new file mode 100755 index 0000000..69e0349 --- /dev/null +++ b/src/main/java/io/reactivex/annotations/Nullable.java @@ -0,0 +1,33 @@ +/** + * 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.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Indicates that a field/parameter/variable/return type may be null. + */ +@Documented +@Target(value = {FIELD, METHOD, PARAMETER, LOCAL_VARIABLE}) +@Retention(value = CLASS) +public @interface Nullable { } + diff --git a/src/main/java/io/reactivex/annotations/SchedulerSupport.java b/src/main/java/io/reactivex/annotations/SchedulerSupport.java new file mode 100755 index 0000000..09acaa6 --- /dev/null +++ b/src/main/java/io/reactivex/annotations/SchedulerSupport.java @@ -0,0 +1,76 @@ +/** + * 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.annotations; + +import java.lang.annotation.*; + +import io.reactivex.schedulers.Schedulers; + +/** + * Indicates what kind of scheduler the class or method uses. + *

+ * Constants are provided for instances from {@link Schedulers} as well as values for + * {@linkplain #NONE not using a scheduler} and {@linkplain #CUSTOM a manually-specified scheduler}. + * Libraries providing their own values should namespace them with their base package name followed + * by a colon ({@code :}) and then a human-readable name (e.g., {@code com.example:ui-thread}). + * @since 2.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE}) +public @interface SchedulerSupport { + /** + * A special value indicating the operator/class doesn't use schedulers. + */ + String NONE = "none"; + /** + * A special value indicating the operator/class requires a scheduler to be manually specified. + */ + String CUSTOM = "custom"; + + // Built-in schedulers: + /** + * The operator/class runs on RxJava's {@linkplain Schedulers#computation() computation + * scheduler} or takes timing information from it. + */ + String COMPUTATION = "io.reactivex:computation"; + /** + * The operator/class runs on RxJava's {@linkplain Schedulers#io() I/O scheduler} or takes + * timing information from it. + */ + String IO = "io.reactivex:io"; + /** + * The operator/class runs on RxJava's {@linkplain Schedulers#newThread() new thread scheduler} + * or takes timing information from it. + */ + String NEW_THREAD = "io.reactivex:new-thread"; + /** + * The operator/class runs on RxJava's {@linkplain Schedulers#trampoline() trampoline scheduler} + * or takes timing information from it. + */ + String TRAMPOLINE = "io.reactivex:trampoline"; + /** + * The operator/class runs on RxJava's {@linkplain Schedulers#single() single scheduler} + * or takes timing information from it. + *

History: 2.0.8 - experimental + * @since 2.2 + */ + String SINGLE = "io.reactivex:single"; + + /** + * The kind of scheduler the class or method uses. + * @return the name of the scheduler the class or method uses + */ + String value(); +} diff --git a/src/main/java/io/reactivex/annotations/package-info.java b/src/main/java/io/reactivex/annotations/package-info.java new file mode 100755 index 0000000..6883df9 --- /dev/null +++ b/src/main/java/io/reactivex/annotations/package-info.java @@ -0,0 +1,20 @@ +/** + * 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. + */ + +/** + * Annotations for indicating experimental and beta operators, classes, methods, types or fields. + */ +package io.reactivex.annotations; diff --git a/src/main/java/io/reactivex/disposables/ActionDisposable.java b/src/main/java/io/reactivex/disposables/ActionDisposable.java new file mode 100755 index 0000000..f553f8b --- /dev/null +++ b/src/main/java/io/reactivex/disposables/ActionDisposable.java @@ -0,0 +1,38 @@ +/** + * 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 io.reactivex.annotations.NonNull; +import io.reactivex.functions.Action; +import io.reactivex.internal.util.ExceptionHelper; + +/** + * A Disposable container that manages an Action instance. + */ +final class ActionDisposable extends ReferenceDisposable { + + private static final long serialVersionUID = -8219729196779211169L; + + ActionDisposable(Action value) { + super(value); + } + + @Override + protected void onDisposed(@NonNull Action value) { + try { + value.run(); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } +} diff --git a/src/main/java/io/reactivex/disposables/CompositeDisposable.java b/src/main/java/io/reactivex/disposables/CompositeDisposable.java new file mode 100755 index 0000000..f7a1bf4 --- /dev/null +++ b/src/main/java/io/reactivex/disposables/CompositeDisposable.java @@ -0,0 +1,257 @@ +/** + * 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 resources; + + volatile boolean disposed; + + /** + * Creates an empty CompositeDisposable. + */ + public CompositeDisposable() { + } + + /** + * Creates a CompositeDisposables with the given array of initial elements. + * @param disposables the array of Disposables to start with + * @throws NullPointerException if {@code disposables} or any of its array items is null + */ + public CompositeDisposable(@NonNull Disposable... disposables) { + ObjectHelper.requireNonNull(disposables, "disposables is null"); + this.resources = new OpenHashSet(disposables.length + 1); + for (Disposable d : disposables) { + ObjectHelper.requireNonNull(d, "A Disposable in the disposables array is null"); + this.resources.add(d); + } + } + + /** + * Creates a CompositeDisposables with the given Iterable sequence of initial elements. + * @param disposables the Iterable sequence of Disposables to start with + * @throws NullPointerException if {@code disposables} or any of its items is null + */ + public CompositeDisposable(@NonNull Iterable disposables) { + ObjectHelper.requireNonNull(disposables, "disposables is null"); + this.resources = new OpenHashSet(); + for (Disposable d : disposables) { + ObjectHelper.requireNonNull(d, "A Disposable item in the disposables sequence is null"); + this.resources.add(d); + } + } + + @Override + public void dispose() { + if (disposed) { + return; + } + OpenHashSet set; + synchronized (this) { + if (disposed) { + return; + } + disposed = true; + set = resources; + resources = null; + } + + dispose(set); + } + + @Override + public boolean isDisposed() { + return disposed; + } + + /** + * Adds a disposable to this container or disposes it if the + * container has been disposed. + * @param disposable the disposable to add, not null + * @return true if successful, false if this container has been disposed + * @throws NullPointerException if {@code disposable} is null + */ + @Override + public boolean add(@NonNull Disposable disposable) { + ObjectHelper.requireNonNull(disposable, "disposable is null"); + if (!disposed) { + synchronized (this) { + if (!disposed) { + OpenHashSet set = resources; + if (set == null) { + set = new OpenHashSet(); + resources = set; + } + set.add(disposable); + return true; + } + } + } + disposable.dispose(); + return false; + } + + /** + * Atomically adds the given array of Disposables to the container or + * disposes them all if the container has been disposed. + * @param disposables the array of Disposables + * @return true if the operation was successful, false if the container has been disposed + * @throws NullPointerException if {@code disposables} or any of its array items is null + */ + public boolean addAll(@NonNull Disposable... disposables) { + ObjectHelper.requireNonNull(disposables, "disposables is null"); + if (!disposed) { + synchronized (this) { + if (!disposed) { + OpenHashSet set = resources; + if (set == null) { + set = new OpenHashSet(disposables.length + 1); + resources = set; + } + for (Disposable d : disposables) { + ObjectHelper.requireNonNull(d, "A Disposable in the disposables array is null"); + set.add(d); + } + return true; + } + } + } + for (Disposable d : disposables) { + d.dispose(); + } + return false; + } + + /** + * Removes and disposes the given disposable if it is part of this + * container. + * @param disposable the disposable to remove and dispose, not null + * @return true if the operation was successful + */ + @Override + public boolean remove(@NonNull Disposable disposable) { + if (delete(disposable)) { + disposable.dispose(); + return true; + } + return false; + } + + /** + * Removes (but does not dispose) the given disposable if it is part of this + * container. + * @param disposable the disposable to remove, not null + * @return true if the operation was successful + * @throws NullPointerException if {@code disposable} is null + */ + @Override + public boolean delete(@NonNull Disposable disposable) { + ObjectHelper.requireNonNull(disposable, "disposables is null"); + if (disposed) { + return false; + } + synchronized (this) { + if (disposed) { + return false; + } + + OpenHashSet set = resources; + if (set == null || !set.remove(disposable)) { + return false; + } + } + return true; + } + + /** + * Atomically clears the container, then disposes all the previously contained Disposables. + */ + public void clear() { + if (disposed) { + return; + } + OpenHashSet 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 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 set) { + if (set == null) { + return; + } + List 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(); + } + errors.add(ex); + } + } + } + if (errors != null) { + if (errors.size() == 1) { + throw ExceptionHelper.wrapOrThrow(errors.get(0)); + } + throw new CompositeException(errors); + } + } +} diff --git a/src/main/java/io/reactivex/disposables/Disposable.java b/src/main/java/io/reactivex/disposables/Disposable.java new file mode 100755 index 0000000..974d039 --- /dev/null +++ b/src/main/java/io/reactivex/disposables/Disposable.java @@ -0,0 +1,29 @@ +/** + * 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; + +/** + * Represents a disposable resource. + */ +public interface Disposable { + /** + * Dispose the resource, the operation should be idempotent. + */ + void dispose(); + + /** + * Returns true if this resource has been disposed. + * @return true if this resource has been disposed + */ + boolean isDisposed(); +} diff --git a/src/main/java/io/reactivex/disposables/Disposables.java b/src/main/java/io/reactivex/disposables/Disposables.java new file mode 100755 index 0000000..7fbac0f --- /dev/null +++ b/src/main/java/io/reactivex/disposables/Disposables.java @@ -0,0 +1,113 @@ +/** + * 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.concurrent.Future; + +import io.reactivex.annotations.NonNull; +import io.reactivex.functions.Action; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.*; +import org.reactivestreams.Subscription; + +/** + * Utility class to help create disposables by wrapping + * other types. + * @since 2.0 + */ +public final class Disposables { + /** Utility class. */ + private Disposables() { + throw new IllegalStateException("No instances!"); + } + + /** + * Construct a Disposable by wrapping a Runnable that is + * executed exactly once when the Disposable is disposed. + * @param run the Runnable to wrap + * @return the new Disposable instance + */ + @NonNull + public static Disposable fromRunnable(@NonNull Runnable run) { + ObjectHelper.requireNonNull(run, "run is null"); + return new RunnableDisposable(run); + } + + /** + * Construct a Disposable by wrapping a Action that is + * executed exactly once when the Disposable is disposed. + * @param run the Action to wrap + * @return the new Disposable instance + */ + @NonNull + public static Disposable fromAction(@NonNull Action run) { + ObjectHelper.requireNonNull(run, "run is null"); + return new ActionDisposable(run); + } + + /** + * Construct a Disposable by wrapping a Future that is + * cancelled exactly once when the Disposable is disposed. + * @param future the Future to wrap + * @return the new Disposable instance + */ + @NonNull + public static Disposable fromFuture(@NonNull Future future) { + ObjectHelper.requireNonNull(future, "future is null"); + return fromFuture(future, true); + } + + /** + * Construct a Disposable by wrapping a Future that is + * cancelled exactly once when the Disposable is disposed. + * @param future the Future to wrap + * @param allowInterrupt if true, the future cancel happens via Future.cancel(true) + * @return the new Disposable instance + */ + @NonNull + public static Disposable fromFuture(@NonNull Future future, boolean allowInterrupt) { + ObjectHelper.requireNonNull(future, "future is null"); + return new FutureDisposable(future, allowInterrupt); + } + + /** + * Construct a Disposable by wrapping a Subscription that is + * cancelled exactly once when the Disposable is disposed. + * @param subscription the Runnable to wrap + * @return the new Disposable instance + */ + @NonNull + public static Disposable fromSubscription(@NonNull Subscription subscription) { + ObjectHelper.requireNonNull(subscription, "subscription is null"); + return new SubscriptionDisposable(subscription); + } + + /** + * Returns a new, non-disposed Disposable instance. + * @return a new, non-disposed Disposable instance + */ + @NonNull + public static Disposable empty() { + return fromRunnable(Functions.EMPTY_RUNNABLE); + } + + /** + * Returns a disposed Disposable instance. + * @return a disposed Disposable instance + */ + @NonNull + public static Disposable disposed() { + return EmptyDisposable.INSTANCE; + } +} diff --git a/src/main/java/io/reactivex/disposables/FutureDisposable.java b/src/main/java/io/reactivex/disposables/FutureDisposable.java new file mode 100755 index 0000000..0b3a677 --- /dev/null +++ b/src/main/java/io/reactivex/disposables/FutureDisposable.java @@ -0,0 +1,45 @@ +/** + * 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.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A Disposable container that cancels a Future instance. + */ +final class FutureDisposable extends AtomicReference> implements Disposable { + + private static final long serialVersionUID = 6545242830671168775L; + + private final boolean allowInterrupt; + + FutureDisposable(Future run, boolean allowInterrupt) { + super(run); + this.allowInterrupt = allowInterrupt; + } + + @Override + public boolean isDisposed() { + Future f = get(); + return f == null || f.isDone(); + } + + @Override + public void dispose() { + Future f = getAndSet(null); + if (f != null) { + f.cancel(allowInterrupt); + } + } +} diff --git a/src/main/java/io/reactivex/disposables/ReferenceDisposable.java b/src/main/java/io/reactivex/disposables/ReferenceDisposable.java new file mode 100755 index 0000000..100b184 --- /dev/null +++ b/src/main/java/io/reactivex/disposables/ReferenceDisposable.java @@ -0,0 +1,52 @@ +/** + * 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.concurrent.atomic.AtomicReference; + +import io.reactivex.annotations.NonNull; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Base class for Disposable containers that manage some other type that + * has to be run when the container is disposed. + * + * @param the type contained + */ +abstract class ReferenceDisposable extends AtomicReference implements Disposable { + + private static final long serialVersionUID = 6537757548749041217L; + + ReferenceDisposable(T value) { + super(ObjectHelper.requireNonNull(value, "value is null")); + } + + protected abstract void onDisposed(@NonNull T value); + + @Override + public final void dispose() { + T value = get(); + if (value != null) { + value = getAndSet(null); + if (value != null) { + onDisposed(value); + } + } + } + + @Override + public final boolean isDisposed() { + return get() == null; + } +} diff --git a/src/main/java/io/reactivex/disposables/RunnableDisposable.java b/src/main/java/io/reactivex/disposables/RunnableDisposable.java new file mode 100755 index 0000000..a70df60 --- /dev/null +++ b/src/main/java/io/reactivex/disposables/RunnableDisposable.java @@ -0,0 +1,37 @@ +/** + * 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 io.reactivex.annotations.NonNull; + +/** + * A disposable container that manages a Runnable instance. + */ +final class RunnableDisposable extends ReferenceDisposable { + + private static final long serialVersionUID = -8219729196779211169L; + + RunnableDisposable(Runnable value) { + super(value); + } + + @Override + protected void onDisposed(@NonNull Runnable value) { + value.run(); + } + + @Override + public String toString() { + return "RunnableDisposable(disposed=" + isDisposed() + ", " + get() + ")"; + } +} diff --git a/src/main/java/io/reactivex/disposables/SerialDisposable.java b/src/main/java/io/reactivex/disposables/SerialDisposable.java new file mode 100755 index 0000000..d506392 --- /dev/null +++ b/src/main/java/io/reactivex/disposables/SerialDisposable.java @@ -0,0 +1,88 @@ +/** + * 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.concurrent.atomic.AtomicReference; + +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * A Disposable container that allows atomically updating/replacing the contained + * Disposable with another Disposable, disposing the old one when updating plus + * handling the disposition when the container itself is disposed. + */ +public final class SerialDisposable implements Disposable { + final AtomicReference resource; + + /** + * Constructs an empty SerialDisposable. + */ + public SerialDisposable() { + this.resource = new AtomicReference(); + } + + /** + * Constructs a SerialDisposable with the given initial Disposable instance. + * @param initialDisposable the initial Disposable instance to use, null allowed + */ + public SerialDisposable(@Nullable Disposable initialDisposable) { + this.resource = new AtomicReference(initialDisposable); + } + + /** + * Atomically: set the next disposable on this container and dispose the previous + * one (if any) or dispose next if the container has been disposed. + * @param next the Disposable to set, may be null + * @return true if the operation succeeded, false if the container has been disposed + * @see #replace(Disposable) + */ + public boolean set(@Nullable Disposable next) { + return DisposableHelper.set(resource, next); + } + + /** + * Atomically: set the next disposable on this container but don't dispose the previous + * one (if any) or dispose next if the container has been disposed. + * @param next the Disposable to set, may be null + * @return true if the operation succeeded, false if the container has been disposed + * @see #set(Disposable) + */ + public boolean replace(@Nullable Disposable next) { + return DisposableHelper.replace(resource, next); + } + + /** + * Returns the currently contained Disposable or null if this container is empty. + * @return the current Disposable, may be null + */ + @Nullable + public Disposable get() { + Disposable d = resource.get(); + if (d == DisposableHelper.DISPOSED) { + return Disposables.disposed(); + } + return d; + } + + @Override + public void dispose() { + DisposableHelper.dispose(resource); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(resource.get()); + } +} diff --git a/src/main/java/io/reactivex/disposables/SubscriptionDisposable.java b/src/main/java/io/reactivex/disposables/SubscriptionDisposable.java new file mode 100755 index 0000000..ebf8934 --- /dev/null +++ b/src/main/java/io/reactivex/disposables/SubscriptionDisposable.java @@ -0,0 +1,33 @@ +/** + * 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 io.reactivex.annotations.NonNull; +import org.reactivestreams.Subscription; + +/** + * A Disposable container that handles a {@link Subscription}. + */ +final class SubscriptionDisposable extends ReferenceDisposable { + + private static final long serialVersionUID = -707001650852963139L; + + SubscriptionDisposable(Subscription value) { + super(value); + } + + @Override + protected void onDisposed(@NonNull Subscription value) { + value.cancel(); + } +} diff --git a/src/main/java/io/reactivex/disposables/package-info.java b/src/main/java/io/reactivex/disposables/package-info.java new file mode 100755 index 0000000..8d162a7 --- /dev/null +++ b/src/main/java/io/reactivex/disposables/package-info.java @@ -0,0 +1,22 @@ +/** + * 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. + */ + +/** + * Default implementations for Disposable-based resource management + * (Disposable container types) and utility classes to construct + * Disposables from callbacks and other types. + */ +package io.reactivex.disposables; diff --git a/src/main/java/io/reactivex/exceptions/CompositeException.java b/src/main/java/io/reactivex/exceptions/CompositeException.java new file mode 100755 index 0000000..4915688 --- /dev/null +++ b/src/main/java/io/reactivex/exceptions/CompositeException.java @@ -0,0 +1,294 @@ +/** + * 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.exceptions; + +import java.io.*; +import java.util.*; + +import io.reactivex.annotations.NonNull; + +/** + * Represents an exception that is a composite of one or more other exceptions. A {@code CompositeException} + * does not modify the structure of any exception it wraps, but at print-time it iterates through the list of + * Throwables contained in the composite in order to print them all. + * + * Its invariant is to contain an immutable, ordered (by insertion order), unique list of non-composite + * exceptions. You can retrieve individual exceptions in this list with {@link #getExceptions()}. + * + * The {@link #printStackTrace()} implementation handles the StackTrace in a customized way instead of using + * {@code getCause()} so that it can avoid circular references. + * + * If you invoke {@link #getCause()}, it will lazily create the causal chain but will stop if it finds any + * Throwable in the chain that it has already seen. + */ +public final class CompositeException extends RuntimeException { + + private static final long serialVersionUID = 3026362227162912146L; + + private final List exceptions; + private final String message; + private Throwable cause; + + /** + * Constructs a CompositeException with the given array of Throwables as the + * list of suppressed exceptions. + * @param exceptions the Throwables to have as initially suppressed exceptions + * + * @throws IllegalArgumentException if exceptions is empty. + */ + public CompositeException(@NonNull Throwable... exceptions) { + this(exceptions == null ? + Collections.singletonList(new NullPointerException("exceptions was null")) : Arrays.asList(exceptions)); + } + + /** + * Constructs a CompositeException with the given array of Throwables as the + * list of suppressed exceptions. + * @param errors the Throwables to have as initially suppressed exceptions + * + * @throws IllegalArgumentException if errors is empty. + */ + public CompositeException(@NonNull Iterable errors) { + Set deDupedExceptions = new LinkedHashSet(); + List localExceptions = new ArrayList(); + if (errors != null) { + for (Throwable ex : errors) { + if (ex instanceof CompositeException) { + deDupedExceptions.addAll(((CompositeException) ex).getExceptions()); + } else + if (ex != null) { + deDupedExceptions.add(ex); + } else { + deDupedExceptions.add(new NullPointerException("Throwable was null!")); + } + } + } else { + deDupedExceptions.add(new NullPointerException("errors was null")); + } + if (deDupedExceptions.isEmpty()) { + throw new IllegalArgumentException("errors is empty"); + } + localExceptions.addAll(deDupedExceptions); + this.exceptions = Collections.unmodifiableList(localExceptions); + this.message = exceptions.size() + " exceptions occurred. "; + } + + /** + * Retrieves the list of exceptions that make up the {@code CompositeException}. + * + * @return the exceptions that make up the {@code CompositeException}, as a {@link List} of {@link Throwable}s + */ + @NonNull + public List getExceptions() { + return exceptions; + } + + @Override + @NonNull + public String getMessage() { + return message; + } + + @Override + @NonNull + public synchronized Throwable getCause() { // NOPMD + if (cause == null) { + // we lazily generate this causal chain if this is called + CompositeExceptionCausalChain localCause = new CompositeExceptionCausalChain(); + Set seenCauses = new HashSet(); + + Throwable chain = localCause; + for (Throwable e : exceptions) { + if (seenCauses.contains(e)) { + // already seen this outer Throwable so skip + continue; + } + seenCauses.add(e); + + List listOfCauses = getListOfCauses(e); + // check if any of them have been seen before + for (Throwable child : listOfCauses) { + if (seenCauses.contains(child)) { + // already seen this outer Throwable so skip + e = new RuntimeException("Duplicate found in causal chain so cropping to prevent loop ..."); + continue; + } + seenCauses.add(child); + } + + // we now have 'e' as the last in the chain + try { + chain.initCause(e); + } catch (Throwable t) { // NOPMD + // ignore + // the JavaDocs say that some Throwables (depending on how they're made) will never + // let me call initCause without blowing up even if it returns null + } + chain = getRootCause(chain); + } + cause = localCause; + } + return cause; + } + + /** + * All of the following {@code printStackTrace} functionality is derived from JDK {@link Throwable} + * {@code printStackTrace}. In particular, the {@code PrintStreamOrWriter} abstraction is copied wholesale. + * + * Changes from the official JDK implementation:

    + *
  • no infinite loop detection
  • + *
  • smaller critical section holding {@link PrintStream} lock
  • + *
  • explicit knowledge about the exceptions {@link List} that this loops through
  • + *
+ */ + @Override + public void printStackTrace() { + printStackTrace(System.err); + } + + @Override + public void printStackTrace(PrintStream s) { + printStackTrace(new WrappedPrintStream(s)); + } + + @Override + public void printStackTrace(PrintWriter s) { + printStackTrace(new WrappedPrintWriter(s)); + } + + /** + * Special handling for printing out a {@code CompositeException}. + * Loops through all inner exceptions and prints them out. + * + * @param s + * stream to print to + */ + private void printStackTrace(PrintStreamOrWriter s) { + StringBuilder b = new StringBuilder(128); + b.append(this).append('\n'); + for (StackTraceElement myStackElement : getStackTrace()) { + b.append("\tat ").append(myStackElement).append('\n'); + } + int i = 1; + for (Throwable ex : exceptions) { + b.append(" ComposedException ").append(i).append(" :\n"); + appendStackTrace(b, ex, "\t"); + i++; + } + s.println(b.toString()); + } + + private void appendStackTrace(StringBuilder b, Throwable ex, String prefix) { + b.append(prefix).append(ex).append('\n'); + for (StackTraceElement stackElement : ex.getStackTrace()) { + b.append("\t\tat ").append(stackElement).append('\n'); + } + if (ex.getCause() != null) { + b.append("\tCaused by: "); + appendStackTrace(b, ex.getCause(), ""); + } + } + + abstract static class PrintStreamOrWriter { + /** Prints the specified string as a line on this StreamOrWriter. */ + abstract void println(Object o); + } + + /** + * Same abstraction and implementation as in JDK to allow PrintStream and PrintWriter to share implementation. + */ + static final class WrappedPrintStream extends PrintStreamOrWriter { + private final PrintStream printStream; + + WrappedPrintStream(PrintStream printStream) { + this.printStream = printStream; + } + + @Override + void println(Object o) { + printStream.println(o); + } + } + + static final class WrappedPrintWriter extends PrintStreamOrWriter { + private final PrintWriter printWriter; + + WrappedPrintWriter(PrintWriter printWriter) { + this.printWriter = printWriter; + } + + @Override + void println(Object o) { + printWriter.println(o); + } + } + + static final class CompositeExceptionCausalChain extends RuntimeException { + private static final long serialVersionUID = 3875212506787802066L; + /* package-private */static final String MESSAGE = "Chain of Causes for CompositeException In Order Received =>"; + + @Override + public String getMessage() { + return MESSAGE; + } + } + + private List getListOfCauses(Throwable ex) { + List list = new ArrayList(); + Throwable root = ex.getCause(); + if (root == null || root == ex) { + return list; + } else { + while (true) { + list.add(root); + Throwable cause = root.getCause(); + if (cause == null || cause == root) { + return list; + } else { + root = cause; + } + } + } + } + + /** + * Returns the number of suppressed exceptions. + * @return the number of suppressed exceptions + */ + public int size() { + return exceptions.size(); + } + + /** + * Returns the root cause of {@code e}. If {@code e.getCause()} returns {@code null} or {@code e}, just return {@code e} itself. + * + * @param e the {@link Throwable} {@code e}. + * @return The root cause of {@code e}. If {@code e.getCause()} returns {@code null} or {@code e}, just return {@code e} itself. + */ + /*private */Throwable getRootCause(Throwable e) { + Throwable root = e.getCause(); + if (root == null || e == root) { + return e; + } + while (true) { + Throwable cause = root.getCause(); + if (cause == null || cause == root) { + return root; + } + root = cause; + } + } +} diff --git a/src/main/java/io/reactivex/exceptions/Exceptions.java b/src/main/java/io/reactivex/exceptions/Exceptions.java new file mode 100755 index 0000000..42c2aa3 --- /dev/null +++ b/src/main/java/io/reactivex/exceptions/Exceptions.java @@ -0,0 +1,76 @@ +/** + * 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.exceptions; + +import io.reactivex.annotations.*; +import io.reactivex.internal.util.ExceptionHelper; + +/** + * Utility class to help propagate checked exceptions and rethrow exceptions + * designated as fatal. + */ +public final class Exceptions { + + /** Utility class. */ + private Exceptions() { + throw new IllegalStateException("No instances!"); + } + /** + * Convenience method to throw a {@code RuntimeException} and {@code Error} directly + * or wrap any other exception type into a {@code RuntimeException}. + * @param t the exception to throw directly or wrapped + * @return because {@code propagate} itself throws an exception or error, this is a sort of phantom return + * value; {@code propagate} does not actually return anything + */ + @NonNull + public static RuntimeException propagate(@NonNull Throwable t) { + /* + * The return type of RuntimeException is a trick for code to be like this: + * + * throw Exceptions.propagate(e); + * + * Even though nothing will return and throw via that 'throw', it allows the code to look like it + * so it's easy to read and understand that it will always result in a throw. + */ + throw ExceptionHelper.wrapOrThrow(t); + } + + /** + * Throws a particular {@code Throwable} only if it belongs to a set of "fatal" error varieties. These + * varieties are as follows: + *
    + *
  • {@code VirtualMachineError}
  • + *
  • {@code ThreadDeath}
  • + *
  • {@code LinkageError}
  • + *
+ * This can be useful if you are writing an operator that calls user-supplied code, and you want to + * notify subscribers of errors encountered in that code by calling their {@code onError} methods, but only + * if the errors are not so catastrophic that such a call would be futile, in which case you simply want to + * rethrow the error. + * + * @param t + * the {@code Throwable} to test and perhaps throw + * @see RxJava: StackOverflowError is swallowed (Issue #748) + */ + public static void throwIfFatal(@NonNull Throwable t) { + // values here derived from https://github.com/ReactiveX/RxJava/issues/748#issuecomment-32471495 + if (t instanceof VirtualMachineError) { + throw (VirtualMachineError) t; + } else if (t instanceof ThreadDeath) { + throw (ThreadDeath) t; + } else if (t instanceof LinkageError) { + throw (LinkageError) t; + } + } +} diff --git a/src/main/java/io/reactivex/exceptions/MissingBackpressureException.java b/src/main/java/io/reactivex/exceptions/MissingBackpressureException.java new file mode 100755 index 0000000..3bd0586 --- /dev/null +++ b/src/main/java/io/reactivex/exceptions/MissingBackpressureException.java @@ -0,0 +1,38 @@ +/** + * 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.exceptions; + +/** + * Indicates that an operator attempted to emit a value but the downstream wasn't ready for it. + */ +public final class MissingBackpressureException extends RuntimeException { + + private static final long serialVersionUID = 8517344746016032542L; + + /** + * Constructs a MissingBackpressureException without message or cause. + */ + public MissingBackpressureException() { + // no message + } + + /** + * Constructs a MissingBackpressureException with the given message but no cause. + * @param message the error message + */ + public MissingBackpressureException(String message) { + super(message); + } + +} diff --git a/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java b/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java new file mode 100755 index 0000000..1cfe421 --- /dev/null +++ b/src/main/java/io/reactivex/exceptions/OnErrorNotImplementedException.java @@ -0,0 +1,53 @@ +/** + * 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.exceptions; + +import io.reactivex.annotations.*; + +/** + * Represents an exception used to signal to the {@code RxJavaPlugins.onError()} that a + * callback-based subscribe() method on a base reactive type didn't specify + * an onError handler. + *

History: 2.0.6 - experimental; 2.1 - beta + * @since 2.2 + */ +public final class OnErrorNotImplementedException extends RuntimeException { + + private static final long serialVersionUID = -6298857009889503852L; + + /** + * Customizes the {@code Throwable} with a custom message and wraps it before it + * is signalled to the {@code RxJavaPlugins.onError()} handler as {@code OnErrorNotImplementedException}. + * + * @param message + * the message to assign to the {@code Throwable} to signal + * @param e + * the {@code Throwable} to signal; if null, a NullPointerException is constructed + */ + public OnErrorNotImplementedException(String message, @NonNull Throwable e) { + super(message, e != null ? e : new NullPointerException()); + } + + /** + * Wraps the {@code Throwable} before it + * is signalled to the {@code RxJavaPlugins.onError()} + * handler as {@code OnErrorNotImplementedException}. + * + * @param e + * the {@code Throwable} to signal; if null, a NullPointerException is constructed + */ + public OnErrorNotImplementedException(@NonNull Throwable e) { + this("The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | " + e, e); + } +} \ No newline at end of file diff --git a/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java b/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java new file mode 100755 index 0000000..ff36ce1 --- /dev/null +++ b/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java @@ -0,0 +1,33 @@ +/** + * 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.exceptions; + +/** + * Explicitly named exception to indicate a Reactive Streams + * protocol violation. + *

History: 2.0.6 - experimental; 2.1 - beta + * @since 2.2 + */ +public final class ProtocolViolationException extends IllegalStateException { + + private static final long serialVersionUID = 1644750035281290266L; + + /** + * Creates an instance with the given message. + * @param message the message + */ + public ProtocolViolationException(String message) { + super(message); + } +} diff --git a/src/main/java/io/reactivex/exceptions/UndeliverableException.java b/src/main/java/io/reactivex/exceptions/UndeliverableException.java new file mode 100755 index 0000000..d3923e0 --- /dev/null +++ b/src/main/java/io/reactivex/exceptions/UndeliverableException.java @@ -0,0 +1,33 @@ +/** + * 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.exceptions; + +/** + * Wrapper for Throwable errors that are sent to `RxJavaPlugins.onError`. + *

History: 2.0.6 - experimental; 2.1 - beta + * @since 2.2 + */ +public final class UndeliverableException extends IllegalStateException { + + private static final long serialVersionUID = 1644750035281290266L; + + /** + * Construct an instance by wrapping the given, non-null + * cause Throwable. + * @param cause the cause, not null + */ + public UndeliverableException(Throwable cause) { + super("The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | " + cause, cause); + } +} diff --git a/src/main/java/io/reactivex/exceptions/package-info.java b/src/main/java/io/reactivex/exceptions/package-info.java new file mode 100755 index 0000000..1a6c6d6 --- /dev/null +++ b/src/main/java/io/reactivex/exceptions/package-info.java @@ -0,0 +1,21 @@ +/** + * 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. + */ + +/** + * Exception handling utilities, safe subscriber exception classes, + * lifecycle exception classes. + */ +package io.reactivex.exceptions; diff --git a/src/main/java/io/reactivex/flowables/ConnectableFlowable.java b/src/main/java/io/reactivex/flowables/ConnectableFlowable.java new file mode 100755 index 0000000..d655bbc --- /dev/null +++ b/src/main/java/io/reactivex/flowables/ConnectableFlowable.java @@ -0,0 +1,322 @@ +/** + * 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.flowables; + +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.operators.flowable.*; +import io.reactivex.internal.util.ConnectConsumer; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; + +/** + * A {@code ConnectableFlowable} resembles an ordinary {@link Flowable}, except that it does not begin + * emitting items when it is subscribed to, but only when its {@link #connect} method is called. In this way you + * can wait for all intended {@link Subscriber}s to {@link Flowable#subscribe} to the {@code Flowable} + * before the {@code Flowable} begins emitting items. + *

+ * + * + * @see RxJava Wiki: + * Connectable Observable Operators + * @param + * the type of items emitted by the {@code ConnectableFlowable} + */ +public abstract class ConnectableFlowable extends Flowable { + + /** + * Instructs the {@code ConnectableFlowable} to begin emitting the items from its underlying + * {@link Flowable} to its {@link Subscriber}s. + * + * @param connection + * the action that receives the connection subscription before the subscription to source happens + * allowing the caller to synchronously disconnect a synchronous source + * @see ReactiveX documentation: Connect + */ + public abstract void connect(@NonNull Consumer connection); + + /** + * Instructs the {@code ConnectableFlowable} to begin emitting the items from its underlying + * {@link Flowable} to its {@link Subscriber}s. + *

+ * To disconnect from a synchronous source, use the {@link #connect(Consumer)} method. + * + * @return the subscription representing the connection + * @see ReactiveX documentation: Connect + */ + public final Disposable connect() { + ConnectConsumer cc = new ConnectConsumer(); + connect(cc); + return cc.disposable; + } + + /** + * Apply a workaround for a race condition with the regular publish().refCount() + * so that racing subscribers and refCount won't hang. + * + * @return the ConnectableFlowable to work with + * @since 2.2.10 + */ + private ConnectableFlowable onRefCount() { + if (this instanceof FlowablePublishClassic) { + @SuppressWarnings("unchecked") + FlowablePublishClassic fp = (FlowablePublishClassic) this; + return RxJavaPlugins.onAssembly( + new FlowablePublishAlt(fp.publishSource(), fp.publishBufferSize()) + ); + } + return this; + } + + /** + * Returns a {@code Flowable} that stays connected to this {@code ConnectableFlowable} as long as there + * is at least one subscription to this {@code ConnectableFlowable}. + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.
+ *
Scheduler:
+ *
This {@code refCount} overload does not operate on any particular {@link Scheduler}.
+ *
+ * @return a {@link Flowable} + * @see ReactiveX documentation: RefCount + * @see #refCount(int) + * @see #refCount(long, TimeUnit) + * @see #refCount(int, long, TimeUnit) + */ + @NonNull + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public Flowable refCount() { + return RxJavaPlugins.onAssembly(new FlowableRefCount(onRefCount())); + } + + /** + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches the specified count and disconnect if all subscribers have unsubscribed. + *
+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.
+ *
Scheduler:
+ *
This {@code refCount} overload does not operate on any particular {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable refCount(int subscriberCount) { + return refCount(subscriberCount, 0, TimeUnit.NANOSECONDS, Schedulers.trampoline()); + } + + /** + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches 1 and disconnect after the specified + * timeout if all subscribers have unsubscribed. + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.
+ *
Scheduler:
+ *
This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @return the new Flowable instance + * @see #refCount(long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable refCount(long timeout, TimeUnit unit) { + return refCount(1, timeout, unit, Schedulers.computation()); + } + + /** + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches 1 and disconnect after the specified + * timeout if all subscribers have unsubscribed. + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.
+ *
Scheduler:
+ *
This {@code refCount} overload operates on the specified {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @param scheduler the target scheduler to wait on before disconnecting + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable refCount(long timeout, TimeUnit unit, Scheduler scheduler) { + return refCount(1, timeout, unit, scheduler); + } + + /** + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches the specified count and disconnect after the specified + * timeout if all subscribers have unsubscribed. + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.
+ *
Scheduler:
+ *
This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @return the new Flowable instance + * @see #refCount(int, long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable refCount(int subscriberCount, long timeout, TimeUnit unit) { + return refCount(subscriberCount, timeout, unit, Schedulers.computation()); + } + + /** + * Connects to the upstream {@code ConnectableFlowable} if the number of subscribed + * subscriber reaches the specified count and disconnect after the specified + * timeout if all subscribers have unsubscribed. + *

+ *
Backpressure:
+ *
The operator itself doesn't interfere with backpressure which is determined by the upstream + * {@code ConnectableFlowable}'s backpressure behavior.
+ *
Scheduler:
+ *
This {@code refCount} overload operates on the specified {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @param scheduler the target scheduler to wait on before disconnecting + * @return the new Flowable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + public final Flowable refCount(int subscriberCount, long timeout, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.verifyPositive(subscriberCount, "subscriberCount"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new FlowableRefCount(onRefCount(), subscriberCount, timeout, unit, scheduler)); + } + + /** + * Returns a Flowable that automatically connects (at most once) to this ConnectableFlowable + * when the first Subscriber subscribes. + *

+ * + *

+ * The connection happens after the first subscription and happens at most once + * during the lifetime of the returned Flowable. If this ConnectableFlowable + * terminates, the connection is never renewed, no matter how Subscribers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Subscriber}s have cancelled their {@code Subscription}s. + *

+ * This overload does not allow disconnecting the connection established via + * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload + * to gain access to the {@code Disposable} representing the only connection. + * + * @return a Flowable that automatically connects to this ConnectableFlowable + * when the first Subscriber subscribes + * @see #refCount() + * @see #autoConnect(int, Consumer) + */ + @NonNull + public Flowable autoConnect() { + return autoConnect(1); + } + /** + * Returns a Flowable that automatically connects (at most once) to this ConnectableFlowable + * when the specified number of Subscribers subscribe to it. + *

+ * + *

+ * The connection happens after the given number of subscriptions and happens at most once + * during the lifetime of the returned Flowable. If this ConnectableFlowable + * terminates, the connection is never renewed, no matter how Subscribers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Subscriber}s have cancelled their {@code Subscription}s. + *

+ * This overload does not allow disconnecting the connection established via + * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload + * to gain access to the {@code Disposable} representing the only connection. + * + * @param numberOfSubscribers the number of subscribers to await before calling connect + * on the ConnectableFlowable. A non-positive value indicates + * an immediate connection. + * @return a Flowable that automatically connects to this ConnectableFlowable + * when the specified number of Subscribers subscribe to it + */ + @NonNull + public Flowable autoConnect(int numberOfSubscribers) { + return autoConnect(numberOfSubscribers, Functions.emptyConsumer()); + } + + /** + * Returns a Flowable that automatically connects (at most once) to this ConnectableFlowable + * when the specified number of Subscribers subscribe to it and calls the + * specified callback with the Subscription associated with the established connection. + *

+ * + *

+ * The connection happens after the given number of subscriptions and happens at most once + * during the lifetime of the returned Flowable. If this ConnectableFlowable + * terminates, the connection is never renewed, no matter how Subscribers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Subscriber}s have cancelled their {@code Subscription}s. + * + * @param numberOfSubscribers the number of subscribers to await before calling connect + * on the ConnectableFlowable. A non-positive value indicates + * an immediate connection. + * @param connection the callback Consumer that will receive the Subscription representing the + * established connection + * @return a Flowable that automatically connects to this ConnectableFlowable + * when the specified number of Subscribers subscribe to it and calls the + * specified callback with the Subscription associated with the established connection + */ + @NonNull + public Flowable autoConnect(int numberOfSubscribers, @NonNull Consumer connection) { + if (numberOfSubscribers <= 0) { + this.connect(connection); + return RxJavaPlugins.onAssembly(this); + } + return RxJavaPlugins.onAssembly(new FlowableAutoConnect(this, numberOfSubscribers, connection)); + } +} diff --git a/src/main/java/io/reactivex/flowables/GroupedFlowable.java b/src/main/java/io/reactivex/flowables/GroupedFlowable.java new file mode 100755 index 0000000..d640f4e --- /dev/null +++ b/src/main/java/io/reactivex/flowables/GroupedFlowable.java @@ -0,0 +1,54 @@ +/** + * 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.flowables; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; + +/** + * A {@link Flowable} that has been grouped by key, the value of which can be obtained with {@link #getKey()}. + *

+ * Note: A {@link GroupedFlowable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedFlowable}s that do not concern you. Instead, you can signal to them that they + * may discard their buffers by applying an operator like {@link Flowable#take take}{@code (0)} to them. + * + * @param + * the type of the key + * @param + * the type of the items emitted by the {@code GroupedFlowable} + * @see Flowable#groupBy(io.reactivex.functions.Function) + * @see ReactiveX documentation: GroupBy + */ +public abstract class GroupedFlowable extends Flowable { + + final K key; + + /** + * Constructs a GroupedFlowable with the given key. + * @param key the key + */ + protected GroupedFlowable(@Nullable K key) { + this.key = key; + } + + /** + * Returns the key that identifies the group of items emitted by this {@code GroupedFlowable}. + * + * @return the key that the items emitted by this {@code GroupedFlowable} were grouped by + */ + @Nullable + public K getKey() { + return key; + } +} diff --git a/src/main/java/io/reactivex/flowables/package-info.java b/src/main/java/io/reactivex/flowables/package-info.java new file mode 100755 index 0000000..66f3f48 --- /dev/null +++ b/src/main/java/io/reactivex/flowables/package-info.java @@ -0,0 +1,22 @@ +/** + * 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. + */ + +/** + * Classes supporting the Flowable base reactive class: + * {@link io.reactivex.flowables.ConnectableFlowable} and + * {@link io.reactivex.flowables.GroupedFlowable}. + */ +package io.reactivex.flowables; diff --git a/src/main/java/io/reactivex/functions/Action.java b/src/main/java/io/reactivex/functions/Action.java new file mode 100755 index 0000000..826b845 --- /dev/null +++ b/src/main/java/io/reactivex/functions/Action.java @@ -0,0 +1,25 @@ +/** + * 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.functions; + +/** + * A functional interface similar to Runnable but allows throwing a checked exception. + */ +public interface Action { + /** + * Runs the action and optionally throws a checked exception. + * @throws Exception if the implementation wishes to throw a checked exception + */ + void run() throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/BiConsumer.java b/src/main/java/io/reactivex/functions/BiConsumer.java new file mode 100755 index 0000000..6b147ae --- /dev/null +++ b/src/main/java/io/reactivex/functions/BiConsumer.java @@ -0,0 +1,30 @@ +/** + * 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.functions; + +/** + * A functional interface (callback) that accepts two values (of possibly different types). + * @param the first value type + * @param the second value type + */ +public interface BiConsumer { + + /** + * Performs an operation on the given values. + * @param t1 the first value + * @param t2 the second value + * @throws Exception on error + */ + void accept(T1 t1, T2 t2) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/BiFunction.java b/src/main/java/io/reactivex/functions/BiFunction.java new file mode 100755 index 0000000..f67a03b --- /dev/null +++ b/src/main/java/io/reactivex/functions/BiFunction.java @@ -0,0 +1,35 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that computes a value based on multiple input values. + * @param the first value type + * @param the second value type + * @param the result type + */ +public interface BiFunction { + + /** + * Calculate a value based on the input values. + * @param t1 the first value + * @param t2 the second value + * @return the result value + * @throws Exception on error + */ + @NonNull + R apply(@NonNull T1 t1, @NonNull T2 t2) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/BiPredicate.java b/src/main/java/io/reactivex/functions/BiPredicate.java new file mode 100755 index 0000000..390e164 --- /dev/null +++ b/src/main/java/io/reactivex/functions/BiPredicate.java @@ -0,0 +1,33 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that returns true or false for the given input values. + * @param the first value + * @param the second value + */ +public interface BiPredicate { + + /** + * Test the given input values and return a boolean. + * @param t1 the first value + * @param t2 the second value + * @return the boolean result + * @throws Exception on error + */ + boolean test(@NonNull T1 t1, @NonNull T2 t2) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/BooleanSupplier.java b/src/main/java/io/reactivex/functions/BooleanSupplier.java new file mode 100755 index 0000000..be928f9 --- /dev/null +++ b/src/main/java/io/reactivex/functions/BooleanSupplier.java @@ -0,0 +1,26 @@ +/** + * 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.functions; + +/** + * A functional interface (callback) that returns a boolean value. + */ +public interface BooleanSupplier { + /** + * Returns a boolean value. + * @return a boolean value + * @throws Exception on error + */ + boolean getAsBoolean() throws Exception; // NOPMD +} diff --git a/src/main/java/io/reactivex/functions/Cancellable.java b/src/main/java/io/reactivex/functions/Cancellable.java new file mode 100755 index 0000000..b4d5ae5 --- /dev/null +++ b/src/main/java/io/reactivex/functions/Cancellable.java @@ -0,0 +1,27 @@ +/** + * 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.functions; + +/** + * A functional interface that has a single cancel method + * that can throw. + */ +public interface Cancellable { + + /** + * Cancel the action or free a resource. + * @throws Exception on error + */ + void cancel() throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Consumer.java b/src/main/java/io/reactivex/functions/Consumer.java new file mode 100755 index 0000000..ff10bbb --- /dev/null +++ b/src/main/java/io/reactivex/functions/Consumer.java @@ -0,0 +1,27 @@ +/** + * 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.functions; + +/** + * A functional interface (callback) that accepts a single value. + * @param the value type + */ +public interface Consumer { + /** + * Consume the given value. + * @param t the value + * @throws Exception on error + */ + void accept(T t) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Function.java b/src/main/java/io/reactivex/functions/Function.java new file mode 100755 index 0000000..7c6d095 --- /dev/null +++ b/src/main/java/io/reactivex/functions/Function.java @@ -0,0 +1,33 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface that takes a value and returns another value, possibly with a + * different type and allows throwing a checked exception. + * + * @param the input value type + * @param the output value type + */ +public interface Function { + /** + * Apply some calculation to the input value and return some other value. + * @param t the input value + * @return the output value + * @throws Exception on error + */ + R apply(@NonNull T t) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Function3.java b/src/main/java/io/reactivex/functions/Function3.java new file mode 100755 index 0000000..f3b49d8 --- /dev/null +++ b/src/main/java/io/reactivex/functions/Function3.java @@ -0,0 +1,36 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that computes a value based on multiple input values. + * @param the first value type + * @param the second value type + * @param the third value type + * @param the result type + */ +public interface Function3 { + /** + * Calculate a value based on the input values. + * @param t1 the first value + * @param t2 the second value + * @param t3 the third value + * @return the result value + * @throws Exception on error + */ + @NonNull + R apply(@NonNull T1 t1, @NonNull T2 t2, @NonNull T3 t3) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Function4.java b/src/main/java/io/reactivex/functions/Function4.java new file mode 100755 index 0000000..70e921d --- /dev/null +++ b/src/main/java/io/reactivex/functions/Function4.java @@ -0,0 +1,38 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that computes a value based on multiple input values. + * @param the first value type + * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the result type + */ +public interface Function4 { + /** + * Calculate a value based on the input values. + * @param t1 the first value + * @param t2 the second value + * @param t3 the third value + * @param t4 the fourth value + * @return the result value + * @throws Exception on error + */ + @NonNull + R apply(@NonNull T1 t1, @NonNull T2 t2, @NonNull T3 t3, @NonNull T4 t4) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Function5.java b/src/main/java/io/reactivex/functions/Function5.java new file mode 100755 index 0000000..32ccfe1 --- /dev/null +++ b/src/main/java/io/reactivex/functions/Function5.java @@ -0,0 +1,40 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that computes a value based on multiple input values. + * @param the first value type + * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type + * @param the result type + */ +public interface Function5 { + /** + * Calculate a value based on the input values. + * @param t1 the first value + * @param t2 the second value + * @param t3 the third value + * @param t4 the fourth value + * @param t5 the fifth value + * @return the result value + * @throws Exception on error + */ + @NonNull + R apply(@NonNull T1 t1, @NonNull T2 t2, @NonNull T3 t3, @NonNull T4 t4, @NonNull T5 t5) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Function6.java b/src/main/java/io/reactivex/functions/Function6.java new file mode 100755 index 0000000..c011545 --- /dev/null +++ b/src/main/java/io/reactivex/functions/Function6.java @@ -0,0 +1,42 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that computes a value based on multiple input values. + * @param the first value type + * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type + * @param the sixth value type + * @param the result type + */ +public interface Function6 { + /** + * Calculate a value based on the input values. + * @param t1 the first value + * @param t2 the second value + * @param t3 the third value + * @param t4 the fourth value + * @param t5 the fifth value + * @param t6 the sixth value + * @return the result value + * @throws Exception on error + */ + @NonNull + R apply(@NonNull T1 t1, @NonNull T2 t2, @NonNull T3 t3, @NonNull T4 t4, @NonNull T5 t5, @NonNull T6 t6) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Function7.java b/src/main/java/io/reactivex/functions/Function7.java new file mode 100755 index 0000000..bb8dee9 --- /dev/null +++ b/src/main/java/io/reactivex/functions/Function7.java @@ -0,0 +1,44 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that computes a value based on multiple input values. + * @param the first value type + * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type + * @param the sixth value type + * @param the seventh value type + * @param the result type + */ +public interface Function7 { + /** + * Calculate a value based on the input values. + * @param t1 the first value + * @param t2 the second value + * @param t3 the third value + * @param t4 the fourth value + * @param t5 the fifth value + * @param t6 the sixth value + * @param t7 the seventh value + * @return the result value + * @throws Exception on error + */ + @NonNull + R apply(@NonNull T1 t1, @NonNull T2 t2, @NonNull T3 t3, @NonNull T4 t4, @NonNull T5 t5, @NonNull T6 t6, @NonNull T7 t7) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Function8.java b/src/main/java/io/reactivex/functions/Function8.java new file mode 100755 index 0000000..8b8785e --- /dev/null +++ b/src/main/java/io/reactivex/functions/Function8.java @@ -0,0 +1,46 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that computes a value based on multiple input values. + * @param the first value type + * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type + * @param the sixth value type + * @param the seventh value type + * @param the eighth value type + * @param the result type + */ +public interface Function8 { + /** + * Calculate a value based on the input values. + * @param t1 the first value + * @param t2 the second value + * @param t3 the third value + * @param t4 the fourth value + * @param t5 the fifth value + * @param t6 the sixth value + * @param t7 the seventh value + * @param t8 the eighth value + * @return the result value + * @throws Exception on error + */ + @NonNull + R apply(@NonNull T1 t1, @NonNull T2 t2, @NonNull T3 t3, @NonNull T4 t4, @NonNull T5 t5, @NonNull T6 t6, @NonNull T7 t7, @NonNull T8 t8) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Function9.java b/src/main/java/io/reactivex/functions/Function9.java new file mode 100755 index 0000000..11e7926 --- /dev/null +++ b/src/main/java/io/reactivex/functions/Function9.java @@ -0,0 +1,48 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that computes a value based on multiple input values. + * @param the first value type + * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type + * @param the sixth value type + * @param the seventh value type + * @param the eighth value type + * @param the ninth value type + * @param the result type + */ +public interface Function9 { + /** + * Calculate a value based on the input values. + * @param t1 the first value + * @param t2 the second value + * @param t3 the third value + * @param t4 the fourth value + * @param t5 the fifth value + * @param t6 the sixth value + * @param t7 the seventh value + * @param t8 the eighth value + * @param t9 the ninth value + * @return the result value + * @throws Exception on error + */ + @NonNull + R apply(@NonNull T1 t1, @NonNull T2 t2, @NonNull T3 t3, @NonNull T4 t4, @NonNull T5 t5, @NonNull T6 t6, @NonNull T7 t7, @NonNull T8 t8, @NonNull T9 t9) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/IntFunction.java b/src/main/java/io/reactivex/functions/IntFunction.java new file mode 100755 index 0000000..aef60e1 --- /dev/null +++ b/src/main/java/io/reactivex/functions/IntFunction.java @@ -0,0 +1,30 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that takes a primitive value and return value of type T. + * @param the returned value type + */ +public interface IntFunction { + /** + * Calculates a value based on a primitive integer input. + * @param i the input value + * @return the result Object + * @throws Exception on error + */ + @NonNull + T apply(int i) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/LongConsumer.java b/src/main/java/io/reactivex/functions/LongConsumer.java new file mode 100755 index 0000000..c233f85 --- /dev/null +++ b/src/main/java/io/reactivex/functions/LongConsumer.java @@ -0,0 +1,25 @@ +/** + * 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.functions; + +/** + * A functional interface (callback) that consumes a primitive long value. + */ +public interface LongConsumer { + /** + * Consume a primitive long input. + * @param t the primitive long value + * @throws Exception on error + */ + void accept(long t) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/Predicate.java b/src/main/java/io/reactivex/functions/Predicate.java new file mode 100755 index 0000000..17bc3cf --- /dev/null +++ b/src/main/java/io/reactivex/functions/Predicate.java @@ -0,0 +1,30 @@ +/** + * 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.functions; + +import io.reactivex.annotations.NonNull; + +/** + * A functional interface (callback) that returns true or false for the given input value. + * @param the first value + */ +public interface Predicate { + /** + * Test the given input value and return a boolean. + * @param t the value + * @return the boolean result + * @throws Exception on error + */ + boolean test(@NonNull T t) throws Exception; +} diff --git a/src/main/java/io/reactivex/functions/package-info.java b/src/main/java/io/reactivex/functions/package-info.java new file mode 100755 index 0000000..3e91a5f --- /dev/null +++ b/src/main/java/io/reactivex/functions/package-info.java @@ -0,0 +1,21 @@ +/** + * 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. + */ + +/** + * Functional interfaces of functions and actions of arity 0 to 9 and related + * utility classes. + */ +package io.reactivex.functions; diff --git a/src/main/java/io/reactivex/internal/disposables/ArrayCompositeDisposable.java b/src/main/java/io/reactivex/internal/disposables/ArrayCompositeDisposable.java new file mode 100755 index 0000000..24af60d --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/ArrayCompositeDisposable.java @@ -0,0 +1,96 @@ +/** + * 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.internal.disposables; + +import java.util.concurrent.atomic.AtomicReferenceArray; + +import io.reactivex.disposables.Disposable; + +/** + * A composite disposable with a fixed number of slots. + * + *

Note that since the implementation leaks the methods of AtomicReferenceArray, one must be + * careful to only call setResource, replaceResource and dispose on it. All other methods may lead to undefined behavior + * and should be used by internal means only. + */ +public final class ArrayCompositeDisposable extends AtomicReferenceArray implements Disposable { + + private static final long serialVersionUID = 2746389416410565408L; + + public ArrayCompositeDisposable(int capacity) { + super(capacity); + } + + /** + * Sets the resource at the specified index and disposes the old resource. + * @param index the index of the resource to set + * @param resource the new resource + * @return true if the resource has ben set, false if the composite has been disposed + */ + public boolean setResource(int index, Disposable resource) { + for (;;) { + Disposable o = get(index); + if (o == DisposableHelper.DISPOSED) { + resource.dispose(); + return false; + } + if (compareAndSet(index, o, resource)) { + if (o != null) { + o.dispose(); + } + return true; + } + } + } + + /** + * Replaces the resource at the specified index and returns the old resource. + * @param index the index of the resource to replace + * @param resource the new resource + * @return the old resource, can be null + */ + public Disposable replaceResource(int index, Disposable resource) { + for (;;) { + Disposable o = get(index); + if (o == DisposableHelper.DISPOSED) { + resource.dispose(); + return null; + } + if (compareAndSet(index, o, resource)) { + return o; + } + } + } + + @Override + public void dispose() { + if (get(0) != DisposableHelper.DISPOSED) { + int s = length(); + for (int i = 0; i < s; i++) { + Disposable o = get(i); + if (o != DisposableHelper.DISPOSED) { + o = getAndSet(i, DisposableHelper.DISPOSED); + if (o != DisposableHelper.DISPOSED && o != null) { + o.dispose(); + } + } + } + } + } + + @Override + public boolean isDisposed() { + return get(0) == DisposableHelper.DISPOSED; + } +} diff --git a/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java b/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java new file mode 100755 index 0000000..446dd6c --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/CancellableDisposable.java @@ -0,0 +1,56 @@ +/** + * 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.internal.disposables; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Cancellable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * A disposable container that wraps a Cancellable instance. + *

+ * Watch out for the AtomicReference API leak! + */ +public final class CancellableDisposable extends AtomicReference +implements Disposable { + + private static final long serialVersionUID = 5718521705281392066L; + + public CancellableDisposable(Cancellable cancellable) { + super(cancellable); + } + + @Override + public boolean isDisposed() { + return get() == null; + } + + @Override + public void dispose() { + if (get() != null) { + Cancellable c = getAndSet(null); + if (c != null) { + try { + c.cancel(); + } catch (Exception ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/disposables/DisposableContainer.java b/src/main/java/io/reactivex/internal/disposables/DisposableContainer.java new file mode 100755 index 0000000..e720fef --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/DisposableContainer.java @@ -0,0 +1,47 @@ +/** + * 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.internal.disposables; + +import io.reactivex.disposables.Disposable; + +/** + * Common interface to add and remove disposables from a container. + * @since 2.0 + */ +public interface DisposableContainer { + + /** + * Adds a disposable to this container or disposes it if the + * container has been disposed. + * @param d the disposable to add, not null + * @return true if successful, false if this container has been disposed + */ + boolean add(Disposable d); + + /** + * Removes and disposes the given disposable if it is part of this + * container. + * @param d the disposable to remove and dispose, not null + * @return true if the operation was successful + */ + boolean remove(Disposable d); + + /** + * Removes (but does not dispose) the given disposable if it is part of this + * container. + * @param d the disposable to remove, not null + * @return true if the operation was successful + */ + boolean delete(Disposable d); +} diff --git a/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java b/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java new file mode 100755 index 0000000..46f13bd --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/DisposableHelper.java @@ -0,0 +1,185 @@ +/** + * 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.internal.disposables; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.ProtocolViolationException; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Utility methods for working with Disposables atomically. + */ +public enum DisposableHelper implements Disposable { + /** + * The singleton instance representing a terminal, disposed state, don't leak it. + */ + DISPOSED + ; + + /** + * Checks if the given Disposable is the common {@link #DISPOSED} enum value. + * @param d the disposable to check + * @return true if d is {@link #DISPOSED} + */ + public static boolean isDisposed(Disposable d) { + return d == DISPOSED; + } + + /** + * Atomically sets the field and disposes the old contents. + * @param field the target field + * @param d the new Disposable to set + * @return true if successful, false if the field contains the {@link #DISPOSED} instance. + */ + public static boolean set(AtomicReference field, Disposable d) { + for (;;) { + Disposable current = field.get(); + if (current == DISPOSED) { + if (d != null) { + d.dispose(); + } + return false; + } + if (field.compareAndSet(current, d)) { + if (current != null) { + current.dispose(); + } + return true; + } + } + } + + /** + * Atomically sets the field to the given non-null Disposable and returns true + * or returns false if the field is non-null. + * If the target field contains the common DISPOSED instance, the supplied disposable + * is disposed. If the field contains other non-null Disposable, an IllegalStateException + * is signalled to the RxJavaPlugins.onError hook. + * + * @param field the target field + * @param d the disposable to set, not null + * @return true if the operation succeeded, false + */ + public static boolean setOnce(AtomicReference field, Disposable d) { + ObjectHelper.requireNonNull(d, "d is null"); + if (!field.compareAndSet(null, d)) { + d.dispose(); + if (field.get() != DISPOSED) { + reportDisposableSet(); + } + return false; + } + return true; + } + + /** + * Atomically replaces the Disposable in the field with the given new Disposable + * but does not dispose the old one. + * @param field the target field to change + * @param d the new disposable, null allowed + * @return true if the operation succeeded, false if the target field contained + * the common DISPOSED instance and the given disposable (if not null) is disposed. + */ + public static boolean replace(AtomicReference field, Disposable d) { + for (;;) { + Disposable current = field.get(); + if (current == DISPOSED) { + if (d != null) { + d.dispose(); + } + return false; + } + if (field.compareAndSet(current, d)) { + return true; + } + } + } + + /** + * Atomically disposes the Disposable in the field if not already disposed. + * @param field the target field + * @return true if the current thread managed to dispose the Disposable + */ + public static boolean dispose(AtomicReference field) { + Disposable current = field.get(); + Disposable d = DISPOSED; + if (current != d) { + current = field.getAndSet(d); + if (current != d) { + if (current != null) { + current.dispose(); + } + return true; + } + } + return false; + } + + /** + * Verifies that current is null, next is not null, otherwise signals errors + * to the RxJavaPlugins and returns false. + * @param current the current Disposable, expected to be null + * @param next the next Disposable, expected to be non-null + * @return true if the validation succeeded + */ + public static boolean validate(Disposable current, Disposable next) { + if (next == null) { + RxJavaPlugins.onError(new NullPointerException("next is null")); + return false; + } + if (current != null) { + next.dispose(); + reportDisposableSet(); + return false; + } + return true; + } + + /** + * Reports that the disposable is already set to the RxJavaPlugins error handler. + */ + public static void reportDisposableSet() { + RxJavaPlugins.onError(new ProtocolViolationException("Disposable already set!")); + } + + /** + * Atomically tries to set the given Disposable on the field if it is null or disposes it if + * the field contains {@link #DISPOSED}. + * @param field the target field + * @param d the disposable to set + * @return true if successful, false otherwise + */ + public static boolean trySet(AtomicReference field, Disposable d) { + if (!field.compareAndSet(null, d)) { + if (field.get() == DISPOSED) { + d.dispose(); + } + return false; + } + return true; + } + + @Override + public void dispose() { + // deliberately no-op + } + + @Override + public boolean isDisposed() { + return true; + } +} diff --git a/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java b/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java new file mode 100755 index 0000000..4e90491 --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/EmptyDisposable.java @@ -0,0 +1,117 @@ +/** + * 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.internal.disposables; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.fuseable.QueueDisposable; + +/** + * Represents a stateless empty Disposable that reports being always + * empty and disposed. + *

It is also async-fuseable but empty all the time. + *

Since EmptyDisposable implements QueueDisposable and is empty, + * don't use it in tests and then signal onNext with it; + * use Disposables.empty() instead. + */ +public enum EmptyDisposable implements QueueDisposable { + /** + * Since EmptyDisposable implements QueueDisposable and is empty, + * don't use it in tests and then signal onNext with it; + * use Disposables.empty() instead. + */ + INSTANCE, + /** + * An empty disposable that returns false for isDisposed. + */ + NEVER + ; + + @Override + public void dispose() { + // no-op + } + + @Override + public boolean isDisposed() { + return this == INSTANCE; + } + + public static void complete(Observer observer) { + observer.onSubscribe(INSTANCE); + observer.onComplete(); + } + + public static void complete(MaybeObserver observer) { + observer.onSubscribe(INSTANCE); + observer.onComplete(); + } + + public static void error(Throwable e, Observer observer) { + observer.onSubscribe(INSTANCE); + observer.onError(e); + } + + public static void complete(CompletableObserver observer) { + observer.onSubscribe(INSTANCE); + observer.onComplete(); + } + + public static void error(Throwable e, CompletableObserver observer) { + observer.onSubscribe(INSTANCE); + observer.onError(e); + } + + public static void error(Throwable e, SingleObserver observer) { + observer.onSubscribe(INSTANCE); + observer.onError(e); + } + + public static void error(Throwable e, MaybeObserver observer) { + observer.onSubscribe(INSTANCE); + observer.onError(e); + } + + @Override + public boolean offer(Object value) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public boolean offer(Object v1, Object v2) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Nullable + @Override + public Object poll() throws Exception { + return null; // always empty + } + + @Override + public boolean isEmpty() { + return true; // always empty + } + + @Override + public void clear() { + // nothing to do + } + + @Override + public int requestFusion(int mode) { + return mode & ASYNC; + } + +} diff --git a/src/main/java/io/reactivex/internal/disposables/ListCompositeDisposable.java b/src/main/java/io/reactivex/internal/disposables/ListCompositeDisposable.java new file mode 100755 index 0000000..a9e539b --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/ListCompositeDisposable.java @@ -0,0 +1,187 @@ +/** + * 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.internal.disposables; + +import java.util.*; + +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.ExceptionHelper; + +/** + * A disposable container that can hold onto multiple other disposables. + */ +public final class ListCompositeDisposable implements Disposable, DisposableContainer { + + List resources; + + volatile boolean disposed; + + public ListCompositeDisposable() { + } + + public ListCompositeDisposable(Disposable... resources) { + ObjectHelper.requireNonNull(resources, "resources is null"); + this.resources = new LinkedList(); + for (Disposable d : resources) { + ObjectHelper.requireNonNull(d, "Disposable item is null"); + this.resources.add(d); + } + } + + public ListCompositeDisposable(Iterable resources) { + ObjectHelper.requireNonNull(resources, "resources is null"); + this.resources = new LinkedList(); + for (Disposable d : resources) { + ObjectHelper.requireNonNull(d, "Disposable item is null"); + this.resources.add(d); + } + } + + @Override + public void dispose() { + if (disposed) { + return; + } + List set; + synchronized (this) { + if (disposed) { + return; + } + disposed = true; + set = resources; + resources = null; + } + + dispose(set); + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public boolean add(Disposable d) { + ObjectHelper.requireNonNull(d, "d is null"); + if (!disposed) { + synchronized (this) { + if (!disposed) { + List set = resources; + if (set == null) { + set = new LinkedList(); + resources = set; + } + set.add(d); + return true; + } + } + } + d.dispose(); + return false; + } + + public boolean addAll(Disposable... ds) { + ObjectHelper.requireNonNull(ds, "ds is null"); + if (!disposed) { + synchronized (this) { + if (!disposed) { + List set = resources; + if (set == null) { + set = new LinkedList(); + 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(Disposable d) { + if (delete(d)) { + d.dispose(); + return true; + } + return false; + } + + @Override + public boolean delete(Disposable d) { + ObjectHelper.requireNonNull(d, "Disposable item is null"); + if (disposed) { + return false; + } + synchronized (this) { + if (disposed) { + return false; + } + + List set = resources; + if (set == null || !set.remove(d)) { + return false; + } + } + return true; + } + + public void clear() { + if (disposed) { + return; + } + List set; + synchronized (this) { + if (disposed) { + return; + } + + set = resources; + resources = null; + } + + dispose(set); + } + + void dispose(List set) { + if (set == null) { + return; + } + List errors = null; + for (Disposable o : set) { + try { + o.dispose(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (errors == null) { + errors = new ArrayList(); + } + errors.add(ex); + } + } + if (errors != null) { + if (errors.size() == 1) { + throw ExceptionHelper.wrapOrThrow(errors.get(0)); + } + throw new CompositeException(errors); + } + } +} diff --git a/src/main/java/io/reactivex/internal/disposables/ResettableConnectable.java b/src/main/java/io/reactivex/internal/disposables/ResettableConnectable.java new file mode 100755 index 0000000..a111080 --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/ResettableConnectable.java @@ -0,0 +1,54 @@ +/** + * 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.internal.disposables; + +import io.reactivex.annotations.Experimental; +import io.reactivex.disposables.Disposable; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.observables.ConnectableObservable; + +/** + * Interface allowing conditional resetting of connections in {@link ConnectableObservable}s + * and {@link ConnectableFlowable}s. + * @since 2.2.2 - experimental + */ +@Experimental +public interface ResettableConnectable { + + /** + * Reset the connectable source only if the given {@link Disposable} {@code connection} instance + * is still representing a connection established by a previous {@code connect()} connection. + *

+ * For example, an immediately previous connection should reset the connectable source: + *


+     * Disposable d = connectable.connect();
+     * 
+     * ((ResettableConnectable)connectable).resetIf(d);
+     * 
+ * However, if the connection indicator {@code Disposable} is from a much earlier connection, + * it should not affect the current connection: + *

+     * Disposable d1 = connectable.connect();
+     * d.dispose();
+     *
+     * Disposable d2 = connectable.connect();
+     *
+     * ((ResettableConnectable)connectable).resetIf(d);
+     * 
+     * assertFalse(d2.isDisposed());
+     * 
+ * @param connection the disposable received from a previous {@code connect()} call. + */ + void resetIf(Disposable connection); +} diff --git a/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java b/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java new file mode 100755 index 0000000..458194a --- /dev/null +++ b/src/main/java/io/reactivex/internal/disposables/SequentialDisposable.java @@ -0,0 +1,79 @@ +/** + * 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.internal.disposables; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.disposables.Disposable; + +/** + * A Disposable container that allows updating/replacing a Disposable + * atomically and with respect of disposing the container itself. + *

+ * The class extends AtomicReference directly so watch out for the API leak! + * @since 2.0 + */ +public final class SequentialDisposable +extends AtomicReference +implements Disposable { + + private static final long serialVersionUID = -754898800686245608L; + + /** + * Constructs an empty SequentialDisposable. + */ + public SequentialDisposable() { + // nothing to do + } + + /** + * Construct a SequentialDisposable with the initial Disposable provided. + * @param initial the initial disposable, null allowed + */ + public SequentialDisposable(Disposable initial) { + lazySet(initial); + } + + /** + * Atomically: set the next disposable on this container and dispose the previous + * one (if any) or dispose next if the container has been disposed. + * @param next the Disposable to set, may be null + * @return true if the operation succeeded, false if the container has been disposed + * @see #replace(Disposable) + */ + public boolean update(Disposable next) { + return DisposableHelper.set(this, next); + } + + /** + * Atomically: set the next disposable on this container but don't dispose the previous + * one (if any) or dispose next if the container has been disposed. + * @param next the Disposable to set, may be null + * @return true if the operation succeeded, false if the container has been disposed + * @see #update(Disposable) + */ + public boolean replace(Disposable next) { + return DisposableHelper.replace(this, next); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } +} diff --git a/src/main/java/io/reactivex/internal/functions/Functions.java b/src/main/java/io/reactivex/internal/functions/Functions.java new file mode 100755 index 0000000..6aee33f --- /dev/null +++ b/src/main/java/io/reactivex/internal/functions/Functions.java @@ -0,0 +1,767 @@ +/** + * 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.internal.functions; + +import java.util.*; +import java.util.concurrent.*; + +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.exceptions.OnErrorNotImplementedException; +import io.reactivex.functions.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Timed; + +/** + * Utility methods to convert the BiFunction, Function3..Function9 instances to Function of Object array. + */ +public final class Functions { + + /** Utility class. */ + private Functions() { + throw new IllegalStateException("No instances!"); + } + + public static Function toFunction(final BiFunction f) { + ObjectHelper.requireNonNull(f, "f is null"); + return new Array2Func(f); + } + + public static Function toFunction(final Function3 f) { + ObjectHelper.requireNonNull(f, "f is null"); + return new Array3Func(f); + } + + public static Function toFunction(final Function4 f) { + ObjectHelper.requireNonNull(f, "f is null"); + return new Array4Func(f); + } + + public static Function toFunction(final Function5 f) { + ObjectHelper.requireNonNull(f, "f is null"); + return new Array5Func(f); + } + + public static Function toFunction( + final Function6 f) { + ObjectHelper.requireNonNull(f, "f is null"); + return new Array6Func(f); + } + + public static Function toFunction( + final Function7 f) { + ObjectHelper.requireNonNull(f, "f is null"); + return new Array7Func(f); + } + + public static Function toFunction( + final Function8 f) { + ObjectHelper.requireNonNull(f, "f is null"); + return new Array8Func(f); + } + + public static Function toFunction( + final Function9 f) { + ObjectHelper.requireNonNull(f, "f is null"); + return new Array9Func(f); + } + + /** A singleton identity function. */ + static final Function IDENTITY = new Identity(); + + /** + * Returns an identity function that simply returns its argument. + * @param the input and output value type + * @return the identity function + */ + @SuppressWarnings("unchecked") + public static Function identity() { + return (Function)IDENTITY; + } + + public static final Runnable EMPTY_RUNNABLE = new EmptyRunnable(); + + public static final Action EMPTY_ACTION = new EmptyAction(); + + static final Consumer EMPTY_CONSUMER = new EmptyConsumer(); + + /** + * Returns an empty consumer that does nothing. + * @param the consumed value type, the value is ignored + * @return an empty consumer that does nothing. + */ + @SuppressWarnings("unchecked") + public static Consumer emptyConsumer() { + return (Consumer)EMPTY_CONSUMER; + } + + public static final Consumer ERROR_CONSUMER = new ErrorConsumer(); + + /** + * Wraps the consumed Throwable into an OnErrorNotImplementedException and + * signals it to the plugin error handler. + */ + public static final Consumer ON_ERROR_MISSING = new OnErrorMissingConsumer(); + + public static final LongConsumer EMPTY_LONG_CONSUMER = new EmptyLongConsumer(); + + static final Predicate ALWAYS_TRUE = new TruePredicate(); + + static final Predicate ALWAYS_FALSE = new FalsePredicate(); + + static final Callable NULL_SUPPLIER = new NullCallable(); + + static final Comparator NATURAL_COMPARATOR = new NaturalObjectComparator(); + + @SuppressWarnings("unchecked") + public static Predicate alwaysTrue() { + return (Predicate)ALWAYS_TRUE; + } + + @SuppressWarnings("unchecked") + public static Predicate alwaysFalse() { + return (Predicate)ALWAYS_FALSE; + } + + @SuppressWarnings("unchecked") + public static Callable nullSupplier() { + return (Callable)NULL_SUPPLIER; + } + + /** + * Returns a natural order comparator which casts the parameters to Comparable. + * @param the value type + * @return a natural order comparator which casts the parameters to Comparable + */ + @SuppressWarnings("unchecked") + public static Comparator naturalOrder() { + return (Comparator)NATURAL_COMPARATOR; + } + + static final class FutureAction implements Action { + final Future future; + + FutureAction(Future future) { + this.future = future; + } + + @Override + public void run() throws Exception { + future.get(); + } + } + + /** + * Wraps the blocking get call of the Future into an Action. + * @param future the future to call get() on, not null + * @return the new Action instance + */ + public static Action futureAction(Future future) { + return new FutureAction(future); + } + + static final class JustValue implements Callable, Function { + final U value; + + JustValue(U value) { + this.value = value; + } + + @Override + public U call() throws Exception { + return value; + } + + @Override + public U apply(T t) throws Exception { + return value; + } + } + + /** + * Returns a Callable that returns the given value. + * @param the value type + * @param value the value to return + * @return the new Callable instance + */ + public static Callable justCallable(T value) { + return new JustValue(value); + } + + /** + * Returns a Function that ignores its parameter and returns the given value. + * @param the function's input type + * @param the value and return type of the function + * @param value the value to return + * @return the new Function instance + */ + public static Function justFunction(U value) { + return new JustValue(value); + } + + static final class CastToClass implements Function { + final Class clazz; + + CastToClass(Class clazz) { + this.clazz = clazz; + } + + @Override + public U apply(T t) throws Exception { + return clazz.cast(t); + } + } + + /** + * Returns a function that cast the incoming values via a Class object. + * @param the input value type + * @param the output and target type + * @param target the target class + * @return the new Function instance + */ + public static Function castFunction(Class target) { + return new CastToClass(target); + } + + static final class ArrayListCapacityCallable implements Callable> { + final int capacity; + + ArrayListCapacityCallable(int capacity) { + this.capacity = capacity; + } + + @Override + public List call() throws Exception { + return new ArrayList(capacity); + } + } + + public static Callable> createArrayList(int capacity) { + return new ArrayListCapacityCallable(capacity); + } + + static final class EqualsPredicate implements Predicate { + final T value; + + EqualsPredicate(T value) { + this.value = value; + } + + @Override + public boolean test(T t) throws Exception { + return ObjectHelper.equals(t, value); + } + } + + public static Predicate equalsWith(T value) { + return new EqualsPredicate(value); + } + + enum HashSetCallable implements Callable> { + INSTANCE; + @Override + public Set call() throws Exception { + return new HashSet(); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Callable> createHashSet() { + return (Callable)HashSetCallable.INSTANCE; + } + + static final class NotificationOnNext implements Consumer { + final Consumer> onNotification; + + NotificationOnNext(Consumer> onNotification) { + this.onNotification = onNotification; + } + + @Override + public void accept(T v) throws Exception { + onNotification.accept(Notification.createOnNext(v)); + } + } + + static final class NotificationOnError implements Consumer { + final Consumer> onNotification; + + NotificationOnError(Consumer> onNotification) { + this.onNotification = onNotification; + } + + @Override + public void accept(Throwable v) throws Exception { + onNotification.accept(Notification.createOnError(v)); + } + } + + static final class NotificationOnComplete implements Action { + final Consumer> onNotification; + + NotificationOnComplete(Consumer> onNotification) { + this.onNotification = onNotification; + } + + @Override + public void run() throws Exception { + onNotification.accept(Notification.createOnComplete()); + } + } + + public static Consumer notificationOnNext(Consumer> onNotification) { + return new NotificationOnNext(onNotification); + } + + public static Consumer notificationOnError(Consumer> onNotification) { + return new NotificationOnError(onNotification); + } + + public static Action notificationOnComplete(Consumer> onNotification) { + return new NotificationOnComplete(onNotification); + } + + static final class ActionConsumer implements Consumer { + final Action action; + + ActionConsumer(Action action) { + this.action = action; + } + + @Override + public void accept(T t) throws Exception { + action.run(); + } + } + + public static Consumer actionConsumer(Action action) { + return new ActionConsumer(action); + } + + static final class ClassFilter implements Predicate { + final Class clazz; + + ClassFilter(Class clazz) { + this.clazz = clazz; + } + + @Override + public boolean test(T t) throws Exception { + return clazz.isInstance(t); + } + } + + public static Predicate isInstanceOf(Class clazz) { + return new ClassFilter(clazz); + } + + static final class BooleanSupplierPredicateReverse implements Predicate { + final BooleanSupplier supplier; + + BooleanSupplierPredicateReverse(BooleanSupplier supplier) { + this.supplier = supplier; + } + + @Override + public boolean test(T t) throws Exception { + return !supplier.getAsBoolean(); + } + } + + public static Predicate predicateReverseFor(BooleanSupplier supplier) { + return new BooleanSupplierPredicateReverse(supplier); + } + + static final class TimestampFunction implements Function> { + final TimeUnit unit; + + final Scheduler scheduler; + + TimestampFunction(TimeUnit unit, Scheduler scheduler) { + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public Timed apply(T t) throws Exception { + return new Timed(t, scheduler.now(unit), unit); + } + } + + public static Function> timestampWith(TimeUnit unit, Scheduler scheduler) { + return new TimestampFunction(unit, scheduler); + } + + static final class ToMapKeySelector implements BiConsumer, T> { + private final Function keySelector; + + ToMapKeySelector(Function keySelector) { + this.keySelector = keySelector; + } + + @Override + public void accept(Map m, T t) throws Exception { + K key = keySelector.apply(t); + m.put(key, t); + } + } + + public static BiConsumer, T> toMapKeySelector(final Function keySelector) { + return new ToMapKeySelector(keySelector); + } + + static final class ToMapKeyValueSelector implements BiConsumer, T> { + private final Function valueSelector; + private final Function keySelector; + + ToMapKeyValueSelector(Function valueSelector, + Function keySelector) { + this.valueSelector = valueSelector; + this.keySelector = keySelector; + } + + @Override + public void accept(Map m, T t) throws Exception { + K key = keySelector.apply(t); + V value = valueSelector.apply(t); + m.put(key, value); + } + } + + public static BiConsumer, T> toMapKeyValueSelector(final Function keySelector, final Function valueSelector) { + return new ToMapKeyValueSelector(valueSelector, keySelector); + } + + static final class ToMultimapKeyValueSelector implements BiConsumer>, T> { + private final Function> collectionFactory; + private final Function valueSelector; + private final Function keySelector; + + ToMultimapKeyValueSelector(Function> collectionFactory, + Function valueSelector, Function keySelector) { + this.collectionFactory = collectionFactory; + this.valueSelector = valueSelector; + this.keySelector = keySelector; + } + + @SuppressWarnings("unchecked") + @Override + public void accept(Map> m, T t) throws Exception { + K key = keySelector.apply(t); + + Collection coll = m.get(key); + if (coll == null) { + coll = (Collection)collectionFactory.apply(key); + m.put(key, coll); + } + + V value = valueSelector.apply(t); + + coll.add(value); + } + } + + public static BiConsumer>, T> toMultimapKeyValueSelector( + final Function keySelector, final Function valueSelector, + final Function> collectionFactory) { + return new ToMultimapKeyValueSelector(collectionFactory, valueSelector, keySelector); + } + + enum NaturalComparator implements Comparator { + INSTANCE; + + @SuppressWarnings("unchecked") + @Override + public int compare(Object o1, Object o2) { + return ((Comparable)o1).compareTo(o2); + } + } + + @SuppressWarnings("unchecked") + public static Comparator naturalComparator() { + return (Comparator)NaturalComparator.INSTANCE; + } + + static final class ListSorter implements Function, List> { + final Comparator comparator; + + ListSorter(Comparator comparator) { + this.comparator = comparator; + } + + @Override + public List apply(List v) { + Collections.sort(v, comparator); + return v; + } + } + + public static Function, List> listSorter(final Comparator comparator) { + return new ListSorter(comparator); + } + + public static final Consumer REQUEST_MAX = new MaxRequestSubscription(); + + static final class Array2Func implements Function { + final BiFunction f; + + Array2Func(BiFunction f) { + this.f = f; + } + + @SuppressWarnings("unchecked") + @Override + public R apply(Object[] a) throws Exception { + if (a.length != 2) { + throw new IllegalArgumentException("Array of size 2 expected but got " + a.length); + } + return f.apply((T1)a[0], (T2)a[1]); + } + } + + static final class Array3Func implements Function { + final Function3 f; + + Array3Func(Function3 f) { + this.f = f; + } + + @SuppressWarnings("unchecked") + @Override + public R apply(Object[] a) throws Exception { + if (a.length != 3) { + throw new IllegalArgumentException("Array of size 3 expected but got " + a.length); + } + return f.apply((T1)a[0], (T2)a[1], (T3)a[2]); + } + } + + static final class Array4Func implements Function { + final Function4 f; + + Array4Func(Function4 f) { + this.f = f; + } + + @SuppressWarnings("unchecked") + @Override + public R apply(Object[] a) throws Exception { + if (a.length != 4) { + throw new IllegalArgumentException("Array of size 4 expected but got " + a.length); + } + return f.apply((T1)a[0], (T2)a[1], (T3)a[2], (T4)a[3]); + } + } + + static final class Array5Func implements Function { + private final Function5 f; + + Array5Func(Function5 f) { + this.f = f; + } + + @SuppressWarnings("unchecked") + @Override + public R apply(Object[] a) throws Exception { + if (a.length != 5) { + throw new IllegalArgumentException("Array of size 5 expected but got " + a.length); + } + return f.apply((T1)a[0], (T2)a[1], (T3)a[2], (T4)a[3], (T5)a[4]); + } + } + + static final class Array6Func implements Function { + final Function6 f; + + Array6Func(Function6 f) { + this.f = f; + } + + @SuppressWarnings("unchecked") + @Override + public R apply(Object[] a) throws Exception { + if (a.length != 6) { + throw new IllegalArgumentException("Array of size 6 expected but got " + a.length); + } + return f.apply((T1)a[0], (T2)a[1], (T3)a[2], (T4)a[3], (T5)a[4], (T6)a[5]); + } + } + + static final class Array7Func implements Function { + final Function7 f; + + Array7Func(Function7 f) { + this.f = f; + } + + @SuppressWarnings("unchecked") + @Override + public R apply(Object[] a) throws Exception { + if (a.length != 7) { + throw new IllegalArgumentException("Array of size 7 expected but got " + a.length); + } + return f.apply((T1)a[0], (T2)a[1], (T3)a[2], (T4)a[3], (T5)a[4], (T6)a[5], (T7)a[6]); + } + } + + static final class Array8Func implements Function { + final Function8 f; + + Array8Func(Function8 f) { + this.f = f; + } + + @SuppressWarnings("unchecked") + @Override + public R apply(Object[] a) throws Exception { + if (a.length != 8) { + throw new IllegalArgumentException("Array of size 8 expected but got " + a.length); + } + return f.apply((T1)a[0], (T2)a[1], (T3)a[2], (T4)a[3], (T5)a[4], (T6)a[5], (T7)a[6], (T8)a[7]); + } + } + + static final class Array9Func implements Function { + final Function9 f; + + Array9Func(Function9 f) { + this.f = f; + } + + @SuppressWarnings("unchecked") + @Override + public R apply(Object[] a) throws Exception { + if (a.length != 9) { + throw new IllegalArgumentException("Array of size 9 expected but got " + a.length); + } + return f.apply((T1)a[0], (T2)a[1], (T3)a[2], (T4)a[3], (T5)a[4], (T6)a[5], (T7)a[6], (T8)a[7], (T9)a[8]); + } + } + + static final class Identity implements Function { + @Override + public Object apply(Object v) { + return v; + } + + @Override + public String toString() { + return "IdentityFunction"; + } + } + + static final class EmptyRunnable implements Runnable { + @Override + public void run() { } + + @Override + public String toString() { + return "EmptyRunnable"; + } + } + + static final class EmptyAction implements Action { + @Override + public void run() { } + + @Override + public String toString() { + return "EmptyAction"; + } + } + + static final class EmptyConsumer implements Consumer { + @Override + public void accept(Object v) { } + + @Override + public String toString() { + return "EmptyConsumer"; + } + } + + static final class ErrorConsumer implements Consumer { + @Override + public void accept(Throwable error) { + RxJavaPlugins.onError(error); + } + } + + static final class OnErrorMissingConsumer implements Consumer { + @Override + public void accept(Throwable error) { + RxJavaPlugins.onError(new OnErrorNotImplementedException(error)); + } + } + + static final class EmptyLongConsumer implements LongConsumer { + @Override + public void accept(long v) { } + } + + static final class TruePredicate implements Predicate { + @Override + public boolean test(Object o) { + return true; + } + } + + static final class FalsePredicate implements Predicate { + @Override + public boolean test(Object o) { + return false; + } + } + + static final class NullCallable implements Callable { + @Override + public Object call() { + return null; + } + } + + static final class NaturalObjectComparator implements Comparator { + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public int compare(Object a, Object b) { + return ((Comparable)a).compareTo(b); + } + } + + static final class MaxRequestSubscription implements Consumer { + @Override + public void accept(Subscription t) throws Exception { + t.request(Long.MAX_VALUE); + } + } + + @SuppressWarnings("unchecked") + public static Consumer boundedConsumer(int bufferSize) { + return (Consumer) new BoundedConsumer(bufferSize); + } + + public static class BoundedConsumer implements Consumer { + + final int bufferSize; + + BoundedConsumer(int bufferSize) { + this.bufferSize = bufferSize; + } + + @Override + public void accept(Subscription s) throws Exception { + s.request(bufferSize); + } + } +} diff --git a/src/main/java/io/reactivex/internal/functions/ObjectHelper.java b/src/main/java/io/reactivex/internal/functions/ObjectHelper.java new file mode 100755 index 0000000..e77dd2a --- /dev/null +++ b/src/main/java/io/reactivex/internal/functions/ObjectHelper.java @@ -0,0 +1,144 @@ +/** + * 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.internal.functions; + +import io.reactivex.functions.BiPredicate; + +/** + * Utility methods containing the backport of Java 7's Objects utility class. + *

Named as such to avoid clash with java.util.Objects. + */ +public final class ObjectHelper { + + /** Utility class. */ + private ObjectHelper() { + throw new IllegalStateException("No instances!"); + } + + /** + * Verifies if the object is not null and returns it or throws a NullPointerException + * with the given message. + * @param the value type + * @param object the object to verify + * @param message the message to use with the NullPointerException + * @return the object itself + * @throws NullPointerException if object is null + */ + public static T requireNonNull(T object, String message) { + if (object == null) { + throw new NullPointerException(message); + } + return object; + } + + /** + * Compares two potentially null objects with each other using Object.equals. + * @param o1 the first object + * @param o2 the second object + * @return the comparison result + */ + public static boolean equals(Object o1, Object o2) { // NOPMD + return o1 == o2 || (o1 != null && o1.equals(o2)); + } + + /** + * Returns the hashCode of a non-null object or zero for a null object. + * @param o the object to get the hashCode for. + * @return the hashCode + */ + public static int hashCode(Object o) { + return o != null ? o.hashCode() : 0; + } + + /** + * Compares two integer values similar to Integer.compare. + * @param v1 the first value + * @param v2 the second value + * @return the comparison result + */ + public static int compare(int v1, int v2) { + return v1 < v2 ? -1 : (v1 > v2 ? 1 : 0); + } + + /** + * Compares two long values similar to Long.compare. + * @param v1 the first value + * @param v2 the second value + * @return the comparison result + */ + public static int compare(long v1, long v2) { + return v1 < v2 ? -1 : (v1 > v2 ? 1 : 0); + } + + static final BiPredicate EQUALS = new BiObjectPredicate(); + + /** + * Returns a BiPredicate that compares its parameters via Objects.equals(). + * @param the value type + * @return the bi-predicate instance + */ + @SuppressWarnings("unchecked") + public static BiPredicate equalsPredicate() { + return (BiPredicate)EQUALS; + } + + /** + * Validate that the given value is positive or report an IllegalArgumentException with + * the parameter name. + * @param value the value to validate + * @param paramName the parameter name of the value + * @return value + * @throws IllegalArgumentException if bufferSize <= 0 + */ + public static int verifyPositive(int value, String paramName) { + if (value <= 0) { + throw new IllegalArgumentException(paramName + " > 0 required but it was " + value); + } + return value; + } + + /** + * Validate that the given value is positive or report an IllegalArgumentException with + * the parameter name. + * @param value the value to validate + * @param paramName the parameter name of the value + * @return value + * @throws IllegalArgumentException if bufferSize <= 0 + */ + public static long verifyPositive(long value, String paramName) { + if (value <= 0L) { + throw new IllegalArgumentException(paramName + " > 0 required but it was " + value); + } + return value; + } + + static final class BiObjectPredicate implements BiPredicate { + @Override + public boolean test(Object o1, Object o2) { + return ObjectHelper.equals(o1, o2); + } + } + + /** + * Trap null-check attempts on primitives. + * @param value the value to check + * @param message the message to print + * @return the value + * @deprecated this method should not be used as there is no need + * to check primitives for nullness. + */ + @Deprecated + public static long requireNonNull(long value, String message) { + throw new InternalError("Null check on a primitive: " + message); + } +} diff --git a/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java b/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java new file mode 100755 index 0000000..4535b9c --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/ConditionalSubscriber.java @@ -0,0 +1,36 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.FlowableSubscriber; + +/** + * A Subscriber with an additional {@link #tryOnNext(Object)} method that + * tells the caller the specified value has been accepted or + * not. + * + *

This allows certain queue-drain or source-drain operators + * to avoid requesting 1 on behalf of a dropped value. + * + * @param the value type + */ +public interface ConditionalSubscriber extends FlowableSubscriber { + /** + * Conditionally takes the value. + * @param t the value to deliver + * @return true if the value has been accepted, false if the value has been rejected + * and the next value can be sent immediately + */ + boolean tryOnNext(T t); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/FuseToFlowable.java b/src/main/java/io/reactivex/internal/fuseable/FuseToFlowable.java new file mode 100755 index 0000000..782e081 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/FuseToFlowable.java @@ -0,0 +1,43 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.Flowable; + +/** + * Interface indicating a operator implementation can be macro-fused back to Flowable in case + * the operator goes from Flowable to some other reactive type and then the sequence calls + * for toFlowable again: + *

+ * Single<Integer> single = Flowable.range(1, 10).reduce((a, b) -> a + b);
+ * Flowable<Integer> flowable = single.toFlowable();
+ * 
+ * + * The {@code Single.toFlowable()} will check for this interface and call the {@link #fuseToFlowable()} + * to return a Flowable which could be the Flowable-specific implementation of reduce(BiFunction). + *

+ * This causes a slight overhead in assembly time (1 instanceof check, 1 operator allocation and 1 dropped + * operator) but does not incur the conversion overhead at runtime. + * + * @param the value type + */ +public interface FuseToFlowable { + + /** + * Returns a (direct) Flowable for the operator. + *

The implementation should handle the necessary RxJavaPlugins wrapping. + * @return the Flowable instance + */ + Flowable fuseToFlowable(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/FuseToMaybe.java b/src/main/java/io/reactivex/internal/fuseable/FuseToMaybe.java new file mode 100755 index 0000000..733ea13 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/FuseToMaybe.java @@ -0,0 +1,43 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.Maybe; + +/** + * Interface indicating an operator implementation can be macro-fused back to Maybe in case + * the operator goes from Maybe to some other reactive type and then the sequence calls + * for toMaybe again: + *

+ * Single<Integer> single = Maybe.just(1).isEmpty();
+ * Maybe<Integer> maybe = single.toMaybe();
+ * 
+ * + * The {@code Single.toMaybe()} will check for this interface and call the {@link #fuseToMaybe()} + * to return a Maybe which could be the Maybe-specific implementation of isEmpty(). + *

+ * This causes a slight overhead in assembly time (1 instanceof check, 1 operator allocation and 1 dropped + * operator) but does not incur the conversion overhead at runtime. + * + * @param the value type + */ +public interface FuseToMaybe { + + /** + * Returns a (direct) Maybe for the operator. + *

The implementation should handle the necessary RxJavaPlugins wrapping. + * @return the Maybe instance + */ + Maybe fuseToMaybe(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/FuseToObservable.java b/src/main/java/io/reactivex/internal/fuseable/FuseToObservable.java new file mode 100755 index 0000000..3d89c84 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/FuseToObservable.java @@ -0,0 +1,43 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.Observable; + +/** + * Interface indicating a operator implementation can be macro-fused back to Observable in case + * the operator goes from Observable to some other reactive type and then the sequence calls + * for toObservable again: + *

+ * Single<Integer> single = Observable.range(1, 10).reduce((a, b) -> a + b);
+ * Observable<Integer> observable = single.toObservable();
+ * 
+ * + * The {@code Single.toObservable()} will check for this interface and call the {@link #fuseToObservable()} + * to return an Observable which could be the Observable-specific implementation of reduce(BiFunction). + *

+ * This causes a slight overhead in assembly time (1 instanceof check, 1 operator allocation and 1 dropped + * operator) but does not incur the conversion overhead at runtime. + * + * @param the value type + */ +public interface FuseToObservable { + + /** + * Returns a (direct) Observable for the operator. + *

The implementation should handle the necessary RxJavaPlugins wrapping. + * @return the Observable instance + */ + Observable fuseToObservable(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/HasUpstreamCompletableSource.java b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamCompletableSource.java new file mode 100755 index 0000000..4914418 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamCompletableSource.java @@ -0,0 +1,29 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.CompletableSource; + +/** + * Interface indicating the implementor has an upstream CompletableSource-like source available + * via {@link #source()} method. + */ +public interface HasUpstreamCompletableSource { + /** + * Returns the upstream source of this Completable. + *

Allows discovering the chain of observables. + * @return the source CompletableSource + */ + CompletableSource source(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/HasUpstreamMaybeSource.java b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamMaybeSource.java new file mode 100755 index 0000000..a178cfe --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamMaybeSource.java @@ -0,0 +1,31 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.MaybeSource; + +/** + * Interface indicating the implementor has an upstream MaybeSource-like source available + * via {@link #source()} method. + * + * @param the value type + */ +public interface HasUpstreamMaybeSource { + /** + * Returns the upstream source of this Maybe. + *

Allows discovering the chain of observables. + * @return the source MaybeSource + */ + MaybeSource source(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/HasUpstreamObservableSource.java b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamObservableSource.java new file mode 100755 index 0000000..0729841 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamObservableSource.java @@ -0,0 +1,31 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.ObservableSource; + +/** + * Interface indicating the implementor has an upstream ObservableSource-like source available + * via {@link #source()} method. + * + * @param the value type + */ +public interface HasUpstreamObservableSource { + /** + * Returns the upstream source of this Observable. + *

Allows discovering the chain of observables. + * @return the source ObservableSource + */ + ObservableSource source(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/HasUpstreamPublisher.java b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamPublisher.java new file mode 100755 index 0000000..58ec7ed --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamPublisher.java @@ -0,0 +1,33 @@ +/** + * 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.internal.fuseable; + +import org.reactivestreams.Publisher; + +/** + * Interface indicating the implementor has an upstream Publisher-like source available + * via {@link #source()} method. + * + * @param the value type + */ +public interface HasUpstreamPublisher { + /** + * Returns the source Publisher. + *

+ * This method is intended to discover the assembly + * graph of sequences. + * @return the source Publisher + */ + Publisher source(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/HasUpstreamSingleSource.java b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamSingleSource.java new file mode 100755 index 0000000..baa2e14 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/HasUpstreamSingleSource.java @@ -0,0 +1,31 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.SingleSource; + +/** + * Interface indicating the implementor has an upstream SingleSource-like source available + * via {@link #source()} method. + * + * @param the value type + */ +public interface HasUpstreamSingleSource { + /** + * Returns the upstream source of this Single. + *

Allows discovering the chain of observables. + * @return the source SingleSource + */ + SingleSource source(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/QueueDisposable.java b/src/main/java/io/reactivex/internal/fuseable/QueueDisposable.java new file mode 100755 index 0000000..0785324 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/QueueDisposable.java @@ -0,0 +1,55 @@ +/** + * 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.internal.fuseable; + +import java.util.Queue; + +import io.reactivex.disposables.Disposable; + +/** + * An interface extending Queue and Disposable and allows negotiating + * the fusion mode between subsequent operators of the {@code Observable} base reactive type. + *

+ * The negotiation happens in subscription time when the upstream + * calls the {@code onSubscribe} with an instance of this interface. The + * downstream has then the obligation to call {@link #requestFusion(int)} + * with the appropriate mode before calling {@code request()}. + *

+ * In synchronous fusion, all upstream values are either already available or is generated + * when {@link #poll()} is called synchronously. When the {@link #poll()} returns null, + * that is the indication if a terminated stream. In this mode, the upstream won't call the onXXX methods. + *

+ * In asynchronous fusion, upstream values may become available to {@link #poll()} eventually. + * Upstream signals onError() and onComplete() as usual but onNext may not actually contain + * the upstream value but have {@code null} instead. Downstream should treat such onNext as indication + * that {@link #poll()} can be called. + *

+ * The general rules for consuming the {@link Queue} interface: + *

    + *
  • {@link #poll()} has to be called sequentially (from within a serializing drain-loop).
  • + *
  • In addition, callers of {@link #poll()} should be prepared to catch exceptions.
  • + *
  • Due to how computation attaches to the {@link #poll()}, {@link #poll()} may return + * {@code null} even if a preceding {@link #isEmpty()} returned false.
  • + *
+ *

+ * Implementations should only allow calling the following methods and the rest of the + * {@link Queue} interface methods should throw {@link UnsupportedOperationException}: + *

    + *
  • {@link #poll()}
  • + *
  • {@link #isEmpty()}
  • + *
  • {@link #clear()}
  • + *
+ * @param the value type transmitted through the queue + */ +public interface QueueDisposable extends QueueFuseable, Disposable { +} diff --git a/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java b/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java new file mode 100755 index 0000000..f5e8038 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/QueueFuseable.java @@ -0,0 +1,83 @@ +/** + * 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.internal.fuseable; + +/** + * Represents a SimpleQueue plus the means and constants for requesting a fusion mode. + * @param the value type returned by the SimpleQueue.poll() + */ +public interface QueueFuseable extends SimpleQueue { + /** + * Returned by the {@link #requestFusion(int)} if the upstream doesn't support + * the requested mode. + */ + int NONE = 0; + + /** + * Request a synchronous fusion mode and can be returned by {@link #requestFusion(int)} + * for an accepted mode. + *

+ * In synchronous fusion, all upstream values are either already available or is generated + * when {@link #poll()} is called synchronously. When the {@link #poll()} returns null, + * that is the indication if a terminated stream. + * In this mode, the upstream won't call the onXXX methods and callers of + * {@link #poll()} should be prepared to catch exceptions. Note that {@link #poll()} has + * to be called sequentially (from within a serializing drain-loop). + */ + int SYNC = 1; + + /** + * Request an asynchronous fusion mode and can be returned by {@link #requestFusion(int)} + * for an accepted mode. + *

+ * In asynchronous fusion, upstream values may become available to {@link #poll()} eventually. + * Upstream signals onError() and onComplete() as usual but onNext may not actually contain + * the upstream value but have {@code null} instead. Downstream should treat such onNext as indication + * that {@link #poll()} can be called. Note that {@link #poll()} has to be called sequentially + * (from within a serializing drain-loop). In addition, callers of {@link #poll()} should be + * prepared to catch exceptions. + */ + int ASYNC = 2; + + /** + * Request any of the {@link #SYNC} or {@link #ASYNC} modes. + */ + int ANY = SYNC | ASYNC; + + /** + * Used in binary or combination with the other constants as an input to {@link #requestFusion(int)} + * indicating that the {@link #poll()} will be called behind an asynchronous boundary and thus + * may change the non-trivial computation locations attached to the {@link #poll()} chain of + * fused operators. + *

+ * For example, fusing map() and observeOn() may move the computation of the map's function over to + * the thread run after the observeOn(), which is generally unexpected. + */ + int BOUNDARY = 4; + + /** + * Request a fusion mode from the upstream. + *

+ * This should be called before {@code onSubscribe} returns. + *

+ * Calling this method multiple times or after {@code onSubscribe} finished is not allowed + * and may result in undefined behavior. + *

+ * @param mode the requested fusion mode, allowed values are {@link #SYNC}, {@link #ASYNC}, + * {@link #ANY} combined with {@link #BOUNDARY} (e.g., {@code requestFusion(SYNC | BOUNDARY)}). + * @return the established fusion mode: {@link #NONE}, {@link #SYNC}, {@link #ASYNC}. + */ + int requestFusion(int mode); + +} diff --git a/src/main/java/io/reactivex/internal/fuseable/QueueSubscription.java b/src/main/java/io/reactivex/internal/fuseable/QueueSubscription.java new file mode 100755 index 0000000..c67224a --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/QueueSubscription.java @@ -0,0 +1,57 @@ +/** + * 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.internal.fuseable; + +import java.util.Queue; + +import org.reactivestreams.Subscription; + +/** + * An interface extending Queue and Subscription and allows negotiating + * the fusion mode between subsequent operators of the {@code Flowable} base reactive type. + *

+ * The negotiation happens in subscription time when the upstream + * calls the {@code onSubscribe} with an instance of this interface. The + * downstream has then the obligation to call {@link #requestFusion(int)} + * with the appropriate mode before calling {@code request()}. + *

+ * In synchronous fusion, all upstream values are either already available or is generated + * when {@link #poll()} is called synchronously. When the {@link #poll()} returns null, + * that is the indication if a terminated stream. Downstream should not call {@link #request(long)} + * in this mode. In this mode, the upstream won't call the onXXX methods. + *

+ * In asynchronous fusion, upstream values may become available to {@link #poll()} eventually. + * Upstream signals onError() and onComplete() as usual but onNext may not actually contain + * the upstream value but have {@code null} instead. Downstream should treat such onNext as indication + * that {@link #poll()} can be called. In this mode, the downstream still has to call {@link #request(long)} + * to indicate it is prepared to receive more values. + *

+ * The general rules for consuming the {@link Queue} interface: + *

    + *
  • {@link #poll()} has to be called sequentially (from within a serializing drain-loop).
  • + *
  • In addition, callers of {@link #poll()} should be prepared to catch exceptions.
  • + *
  • Due to how computation attaches to the {@link #poll()}, {@link #poll()} may return + * {@code null} even if a preceding {@link #isEmpty()} returned false.
  • + *
+ *

+ * Implementations should only allow calling the following methods and the rest of the + * {@link Queue} interface methods should throw {@link UnsupportedOperationException}: + *

    + *
  • {@link #poll()}
  • + *
  • {@link #isEmpty()}
  • + *
  • {@link #clear()}
  • + *
+ * @param the value type transmitted through the queue + */ +public interface QueueSubscription extends QueueFuseable, Subscription { +} diff --git a/src/main/java/io/reactivex/internal/fuseable/ScalarCallable.java b/src/main/java/io/reactivex/internal/fuseable/ScalarCallable.java new file mode 100755 index 0000000..13ac165 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/ScalarCallable.java @@ -0,0 +1,37 @@ +/** + * 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.internal.fuseable; + +import java.util.concurrent.Callable; + +/** + * A marker interface indicating that a scalar, constant value + * is held by the implementing reactive type which can be + * safely extracted during assembly time can be used for + * optimization. + *

+ * Implementors of {@link #call()} should not throw any exception. + *

+ * Design note: the interface extends {@link Callable} because if a scalar + * is safe to extract during assembly time, it is also safe to extract at + * subscription time or later. This allows optimizations to deal with such + * single-element sources uniformly. + *

+ * @param the scalar value type held by the implementing reactive type + */ +public interface ScalarCallable extends Callable { + + // overridden to remove the throws Exception + @Override + T call(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/SimplePlainQueue.java b/src/main/java/io/reactivex/internal/fuseable/SimplePlainQueue.java new file mode 100755 index 0000000..9af3e0c --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/SimplePlainQueue.java @@ -0,0 +1,28 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.annotations.Nullable; + +/** + * Override of the SimpleQueue interface with no throws Exception on poll(). + * + * @param the value type to offer and poll, not null + */ +public interface SimplePlainQueue extends SimpleQueue { + + @Nullable + @Override + T poll(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/SimpleQueue.java b/src/main/java/io/reactivex/internal/fuseable/SimpleQueue.java new file mode 100755 index 0000000..db0fe95 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/SimpleQueue.java @@ -0,0 +1,71 @@ +/** + * 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.internal.fuseable; + +import io.reactivex.annotations.*; + +/** + * A minimalist queue interface without the method bloat of java.util.Collection and java.util.Queue. + * + * @param the value type to offer and poll, not null + */ +public interface SimpleQueue { + + /** + * Atomically enqueue a single. + * @param value the value to enqueue, not null + * @return true if successful, false if the value was not enqueued + * likely due to reaching the queue capacity) + */ + boolean offer(@NonNull T value); + + /** + * Atomically enqueue two values. + * @param v1 the first value to enqueue, not null + * @param v2 the second value to enqueue, not null + * @return true if successful, false if the value was not enqueued + * likely due to reaching the queue capacity) + */ + boolean offer(@NonNull T v1, @NonNull T v2); + + /** + * Tries to dequeue a value (non-null) or returns null if + * the queue is empty. + *

+ * If the producer uses {@link #offer(Object, Object)} and + * when polling in pairs, if the first poll() returns a non-null + * item, the second poll() is guaranteed to return a non-null item + * as well. + * @return the item or null to indicate an empty queue + * @throws Exception if some pre-processing of the dequeued + * item (usually through fused functions) throws. + */ + @Nullable + T poll() throws Exception; + + /** + * Returns true if the queue is empty. + *

+ * Note however that due to potential fused functions in {@link #poll()} + * it is possible this method returns false but then poll() returns null + * because the fused function swallowed the available item(s). + * @return true if the queue is empty + */ + boolean isEmpty(); + + /** + * Removes all enqueued items from this queue. + */ + void clear(); +} diff --git a/src/main/java/io/reactivex/internal/fuseable/package-info.java b/src/main/java/io/reactivex/internal/fuseable/package-info.java new file mode 100755 index 0000000..6436502 --- /dev/null +++ b/src/main/java/io/reactivex/internal/fuseable/package-info.java @@ -0,0 +1,17 @@ +/** + * 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. + */ + +/** + * Base interfaces and types for supporting operator-fusion. + */ +package io.reactivex.internal.fuseable; diff --git a/src/main/java/io/reactivex/internal/observers/BasicFuseableObserver.java b/src/main/java/io/reactivex/internal/observers/BasicFuseableObserver.java new file mode 100755 index 0000000..56ec76b --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/BasicFuseableObserver.java @@ -0,0 +1,183 @@ +/** + * 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.internal.observers; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Base class for a fuseable intermediate observer. + * @param the upstream value type + * @param the downstream value type + */ +public abstract class BasicFuseableObserver implements Observer, QueueDisposable { + + /** The downstream subscriber. */ + protected final Observer downstream; + + /** The upstream subscription. */ + protected Disposable upstream; + + /** The upstream's QueueDisposable if not null. */ + protected QueueDisposable qd; + + /** Flag indicating no further onXXX event should be accepted. */ + protected boolean done; + + /** Holds the established fusion mode of the upstream. */ + protected int sourceMode; + + /** + * Construct a BasicFuseableObserver by wrapping the given subscriber. + * @param downstream the subscriber, not null (not verified) + */ + public BasicFuseableObserver(Observer downstream) { + this.downstream = downstream; + } + + // final: fixed protocol steps to support fuseable and non-fuseable upstream + @SuppressWarnings("unchecked") + @Override + public final void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + + this.upstream = d; + if (d instanceof QueueDisposable) { + this.qd = (QueueDisposable)d; + } + + if (beforeDownstream()) { + + downstream.onSubscribe(this); + + afterDownstream(); + } + + } + } + + /** + * Override this to perform actions before the call {@code actual.onSubscribe(this)} happens. + * @return true if onSubscribe should continue with the call + */ + protected boolean beforeDownstream() { + return true; + } + + /** + * Override this to perform actions after the call to {@code actual.onSubscribe(this)} happened. + */ + protected void afterDownstream() { + // default no-op + } + + // ----------------------------------- + // Convenience and state-aware methods + // ----------------------------------- + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + /** + * Rethrows the throwable if it is a fatal exception or calls {@link #onError(Throwable)}. + * @param t the throwable to rethrow or signal to the actual subscriber + */ + protected final void fail(Throwable t) { + Exceptions.throwIfFatal(t); + upstream.dispose(); + onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + /** + * Calls the upstream's QueueDisposable.requestFusion with the mode and + * saves the established mode in {@link #sourceMode} if that mode doesn't + * have the {@link QueueDisposable#BOUNDARY} flag set. + *

+ * If the upstream doesn't support fusion ({@link #qd} is null), the method + * returns {@link QueueDisposable#NONE}. + * @param mode the fusion mode requested + * @return the established fusion mode + */ + protected final int transitiveBoundaryFusion(int mode) { + QueueDisposable qd = this.qd; + if (qd != null) { + if ((mode & BOUNDARY) == 0) { + int m = qd.requestFusion(mode); + if (m != NONE) { + sourceMode = m; + } + return m; + } + } + return NONE; + } + + // -------------------------------------------------------------- + // Default implementation of the RS and QS protocol (can be overridden) + // -------------------------------------------------------------- + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public boolean isEmpty() { + return qd.isEmpty(); + } + + @Override + public void clear() { + qd.clear(); + } + + // ----------------------------------------------------------- + // The rest of the Queue interface methods shouldn't be called + // ----------------------------------------------------------- + + @Override + public final boolean offer(R e) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public final boolean offer(R v1, R v2) { + throw new UnsupportedOperationException("Should not be called!"); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java b/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java new file mode 100755 index 0000000..a5d2adf --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/BasicIntQueueDisposable.java @@ -0,0 +1,40 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.internal.fuseable.QueueDisposable; + +/** + * An abstract QueueDisposable implementation, extending an AtomicInteger, + * that defaults all unnecessary Queue methods to throw UnsupportedOperationException. + * @param the output value type + */ +public abstract class BasicIntQueueDisposable +extends AtomicInteger +implements QueueDisposable { + + private static final long serialVersionUID = -1001730202384742097L; + + @Override + public final boolean offer(T e) { + throw new UnsupportedOperationException("Should not be called"); + } + + @Override + public final boolean offer(T v1, T v2) { + throw new UnsupportedOperationException("Should not be called"); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/BasicQueueDisposable.java b/src/main/java/io/reactivex/internal/observers/BasicQueueDisposable.java new file mode 100755 index 0000000..cad108b --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/BasicQueueDisposable.java @@ -0,0 +1,34 @@ +/** + * 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.internal.observers; + +import io.reactivex.internal.fuseable.QueueDisposable; + +/** + * An abstract QueueDisposable implementation that defaults all + * unnecessary Queue methods to throw UnsupportedOperationException. + * @param the output value type + */ +public abstract class BasicQueueDisposable implements QueueDisposable { + + @Override + public final boolean offer(T e) { + throw new UnsupportedOperationException("Should not be called"); + } + + @Override + public final boolean offer(T v1, T v2) { + throw new UnsupportedOperationException("Should not be called"); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java b/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java new file mode 100755 index 0000000..188c78b --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/BiConsumerSingleObserver.java @@ -0,0 +1,72 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.SingleObserver; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.BiConsumer; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class BiConsumerSingleObserver +extends AtomicReference +implements SingleObserver, Disposable { + + private static final long serialVersionUID = 4943102778943297569L; + final BiConsumer onCallback; + + public BiConsumerSingleObserver(BiConsumer onCallback) { + this.onCallback = onCallback; + } + + @Override + public void onError(Throwable e) { + try { + lazySet(DisposableHelper.DISPOSED); + onCallback.accept(null, e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(new CompositeException(e, ex)); + } + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + try { + lazySet(DisposableHelper.DISPOSED); + onCallback.accept(value, null); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } +} diff --git a/src/main/java/io/reactivex/internal/observers/BlockingBaseObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingBaseObserver.java new file mode 100755 index 0000000..63078f8 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/BlockingBaseObserver.java @@ -0,0 +1,84 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.CountDownLatch; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.util.*; + +public abstract class BlockingBaseObserver extends CountDownLatch +implements Observer, Disposable { + + T value; + Throwable error; + + Disposable upstream; + + volatile boolean cancelled; + + public BlockingBaseObserver() { + super(1); + } + + @Override + public final void onSubscribe(Disposable d) { + this.upstream = d; + if (cancelled) { + d.dispose(); + } + } + + @Override + public final void onComplete() { + countDown(); + } + + @Override + public final void dispose() { + cancelled = true; + Disposable d = this.upstream; + if (d != null) { + d.dispose(); + } + } + + @Override + public final boolean isDisposed() { + return cancelled; + } + + /** + * Block until the first value arrives and return it, otherwise + * return null for an empty source and rethrow any exception. + * @return the first value or null if the source is empty + */ + public final T blockingGet() { + if (getCount() != 0) { + try { + BlockingHelper.verifyNonBlocking(); + await(); + } catch (InterruptedException ex) { + dispose(); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + return value; + } +} diff --git a/src/main/java/io/reactivex/internal/observers/BlockingFirstObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingFirstObserver.java new file mode 100755 index 0000000..212edb6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/BlockingFirstObserver.java @@ -0,0 +1,39 @@ +/** + * 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.internal.observers; + +/** + * Blocks until the upstream signals its first value or completes. + * + * @param the value type + */ +public final class BlockingFirstObserver extends BlockingBaseObserver { + + @Override + public void onNext(T t) { + if (value == null) { + value = t; + upstream.dispose(); + countDown(); + } + } + + @Override + public void onError(Throwable t) { + if (value == null) { + error = t; + } + countDown(); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/BlockingLastObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingLastObserver.java new file mode 100755 index 0000000..24d6345 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/BlockingLastObserver.java @@ -0,0 +1,34 @@ +/** + * 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.internal.observers; + +/** + * Blocks until the upstream signals its last value or completes. + * + * @param the value type + */ +public final class BlockingLastObserver extends BlockingBaseObserver { + + @Override + public void onNext(T t) { + value = t; + } + + @Override + public void onError(Throwable t) { + value = null; + error = t; + countDown(); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java new file mode 100755 index 0000000..2b5f560 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/BlockingMultiObserver.java @@ -0,0 +1,189 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.util.*; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + +/** + * A combined Observer that awaits the success or error signal via a CountDownLatch. + * @param the value type + */ +public final class BlockingMultiObserver +extends CountDownLatch +implements SingleObserver, CompletableObserver, MaybeObserver { + + T value; + Throwable error; + + Disposable upstream; + + volatile boolean cancelled; + + public BlockingMultiObserver() { + super(1); + } + + void dispose() { + cancelled = true; + Disposable d = this.upstream; + if (d != null) { + d.dispose(); + } + } + + @Override + public void onSubscribe(Disposable d) { + this.upstream = d; + if (cancelled) { + d.dispose(); + } + } + + @Override + public void onSuccess(T value) { + this.value = value; + countDown(); + } + + @Override + public void onError(Throwable e) { + error = e; + countDown(); + } + + @Override + public void onComplete() { + countDown(); + } + + /** + * Block until the latch is counted down then rethrow any exception received (wrapped if checked) + * or return the received value (null if none). + * @return the value received or null if no value received + */ + public T blockingGet() { + if (getCount() != 0) { + try { + BlockingHelper.verifyNonBlocking(); + await(); + } catch (InterruptedException ex) { + dispose(); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + Throwable ex = error; + if (ex != null) { + throw ExceptionHelper.wrapOrThrow(ex); + } + return value; + } + + /** + * Block until the latch is counted down then rethrow any exception received (wrapped if checked) + * or return the received value (the defaultValue if none). + * @param defaultValue the default value to return if no value was received + * @return the value received or defaultValue if no value received + */ + public T blockingGet(T defaultValue) { + if (getCount() != 0) { + try { + BlockingHelper.verifyNonBlocking(); + await(); + } catch (InterruptedException ex) { + dispose(); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + Throwable ex = error; + if (ex != null) { + throw ExceptionHelper.wrapOrThrow(ex); + } + T v = value; + return v != null ? v : defaultValue; + } + + /** + * Block until the latch is counted down and return the error received or null if no + * error happened. + * @return the error received or null + */ + public Throwable blockingGetError() { + if (getCount() != 0) { + try { + BlockingHelper.verifyNonBlocking(); + await(); + } catch (InterruptedException ex) { + dispose(); + return ex; + } + } + return error; + } + + /** + * Block until the latch is counted down and return the error received or + * when the wait is interrupted or times out, null otherwise. + * @param timeout the timeout value + * @param unit the time unit + * @return the error received or null + */ + public Throwable blockingGetError(long timeout, TimeUnit unit) { + if (getCount() != 0) { + try { + BlockingHelper.verifyNonBlocking(); + if (!await(timeout, unit)) { + dispose(); + throw ExceptionHelper.wrapOrThrow(new TimeoutException(timeoutMessage(timeout, unit))); + } + } catch (InterruptedException ex) { + dispose(); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + return error; + } + + /** + * Block until the observer terminates and return true; return false if + * the wait times out. + * @param timeout the timeout value + * @param unit the time unit + * @return true if the observer terminated in time, false otherwise + */ + public boolean blockingAwait(long timeout, TimeUnit unit) { + if (getCount() != 0) { + try { + BlockingHelper.verifyNonBlocking(); + if (!await(timeout, unit)) { + dispose(); + return false; + } + } catch (InterruptedException ex) { + dispose(); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + Throwable ex = error; + if (ex != null) { + throw ExceptionHelper.wrapOrThrow(ex); + } + return true; + } +} diff --git a/src/main/java/io/reactivex/internal/observers/BlockingObserver.java b/src/main/java/io/reactivex/internal/observers/BlockingObserver.java new file mode 100755 index 0000000..4731dc3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/BlockingObserver.java @@ -0,0 +1,67 @@ +/** + * 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.internal.observers; + +import java.util.Queue; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.util.NotificationLite; + +public final class BlockingObserver extends AtomicReference implements Observer, Disposable { + + private static final long serialVersionUID = -4875965440900746268L; + + public static final Object TERMINATED = new Object(); + + final Queue queue; + + public BlockingObserver(Queue queue) { + this.queue = queue; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(T t) { + queue.offer(NotificationLite.next(t)); + } + + @Override + public void onError(Throwable t) { + queue.offer(NotificationLite.error(t)); + } + + @Override + public void onComplete() { + queue.offer(NotificationLite.complete()); + } + + @Override + public void dispose() { + if (DisposableHelper.dispose(this)) { + queue.offer(TERMINATED); + } + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } +} diff --git a/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java b/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java new file mode 100755 index 0000000..ee059c5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/CallbackCompletableObserver.java @@ -0,0 +1,91 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.CompletableObserver; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.observers.LambdaConsumerIntrospection; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CallbackCompletableObserver +extends AtomicReference + implements CompletableObserver, Disposable, Consumer, LambdaConsumerIntrospection { + + private static final long serialVersionUID = -4361286194466301354L; + + final Consumer onError; + final Action onComplete; + + public CallbackCompletableObserver(Action onComplete) { + this.onError = this; + this.onComplete = onComplete; + } + + public CallbackCompletableObserver(Consumer onError, Action onComplete) { + this.onError = onError; + this.onComplete = onComplete; + } + + @Override + public void accept(Throwable e) { + RxJavaPlugins.onError(new OnErrorNotImplementedException(e)); + } + + @Override + public void onComplete() { + try { + onComplete.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + lazySet(DisposableHelper.DISPOSED); + } + + @Override + public void onError(Throwable e) { + try { + onError.accept(e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + lazySet(DisposableHelper.DISPOSED); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + + @Override + public boolean hasCustomOnError() { + return onError != this; + } +} diff --git a/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java b/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java new file mode 100755 index 0000000..59735e9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/ConsumerSingleObserver.java @@ -0,0 +1,83 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.SingleObserver; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.LambdaConsumerIntrospection; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ConsumerSingleObserver +extends AtomicReference +implements SingleObserver, Disposable, LambdaConsumerIntrospection { + + private static final long serialVersionUID = -7012088219455310787L; + + final Consumer onSuccess; + + final Consumer onError; + + public ConsumerSingleObserver(Consumer onSuccess, Consumer onError) { + this.onSuccess = onSuccess; + this.onError = onError; + } + + @Override + public void onError(Throwable e) { + lazySet(DisposableHelper.DISPOSED); + try { + onError.accept(e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(new CompositeException(e, ex)); + } + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + lazySet(DisposableHelper.DISPOSED); + try { + onSuccess.accept(value); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } +} diff --git a/src/main/java/io/reactivex/internal/observers/DeferredScalarDisposable.java b/src/main/java/io/reactivex/internal/observers/DeferredScalarDisposable.java new file mode 100755 index 0000000..a975f0a --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/DeferredScalarDisposable.java @@ -0,0 +1,157 @@ +/** + * 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.internal.observers; + +import io.reactivex.Observer; +import io.reactivex.annotations.Nullable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Represents a fuseable container for a single value. + * + * @param the value type received and emitted + */ +public class DeferredScalarDisposable extends BasicIntQueueDisposable { + + private static final long serialVersionUID = -5502432239815349361L; + + /** The target of the events. */ + protected final Observer downstream; + + /** The value stored temporarily when in fusion mode. */ + protected T value; + + /** Indicates there was a call to complete(T). */ + static final int TERMINATED = 2; + + /** Indicates the Disposable has been disposed. */ + static final int DISPOSED = 4; + + /** Indicates this Disposable is in fusion mode and is currently empty. */ + static final int FUSED_EMPTY = 8; + /** Indicates this Disposable is in fusion mode and has a value. */ + static final int FUSED_READY = 16; + /** Indicates this Disposable is in fusion mode and its value has been consumed. */ + static final int FUSED_CONSUMED = 32; + + /** + * Constructs a DeferredScalarDisposable by wrapping the Observer. + * @param downstream the Observer to wrap, not null (not verified) + */ + public DeferredScalarDisposable(Observer downstream) { + this.downstream = downstream; + } + + @Override + public final int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + lazySet(FUSED_EMPTY); + return ASYNC; + } + return NONE; + } + + /** + * Complete the target with a single value or indicate there is a value available in + * fusion mode. + * @param value the value to signal, not null (not verified) + */ + public final void complete(T value) { + int state = get(); + if ((state & (FUSED_READY | FUSED_CONSUMED | TERMINATED | DISPOSED)) != 0) { + return; + } + Observer a = downstream; + if (state == FUSED_EMPTY) { + this.value = value; + lazySet(FUSED_READY); + a.onNext(null); + } else { + lazySet(TERMINATED); + a.onNext(value); + } + if (get() != DISPOSED) { + a.onComplete(); + } + } + + /** + * Complete the target with an error signal. + * @param t the Throwable to signal, not null (not verified) + */ + public final void error(Throwable t) { + int state = get(); + if ((state & (FUSED_READY | FUSED_CONSUMED | TERMINATED | DISPOSED)) != 0) { + RxJavaPlugins.onError(t); + return; + } + lazySet(TERMINATED); + downstream.onError(t); + } + + /** + * Complete the target without any value. + */ + public final void complete() { + int state = get(); + if ((state & (FUSED_READY | FUSED_CONSUMED | TERMINATED | DISPOSED)) != 0) { + return; + } + lazySet(TERMINATED); + downstream.onComplete(); + } + + @Nullable + @Override + public final T poll() throws Exception { + if (get() == FUSED_READY) { + T v = value; + value = null; + lazySet(FUSED_CONSUMED); + return v; + } + return null; + } + + @Override + public final boolean isEmpty() { + return get() != FUSED_READY; + } + + @Override + public final void clear() { + lazySet(FUSED_CONSUMED); + value = null; + } + + @Override + public void dispose() { + set(DISPOSED); + value = null; + } + + /** + * Try disposing this Disposable and return true if the current thread succeeded. + * @return true if the current thread succeeded + */ + public final boolean tryDispose() { + return getAndSet(DISPOSED) != DISPOSED; + } + + @Override + public final boolean isDisposed() { + return get() == DISPOSED; + } + +} diff --git a/src/main/java/io/reactivex/internal/observers/DeferredScalarObserver.java b/src/main/java/io/reactivex/internal/observers/DeferredScalarObserver.java new file mode 100755 index 0000000..91236b3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/DeferredScalarObserver.java @@ -0,0 +1,73 @@ +/** + * 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.internal.observers; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * A fuseable Observer that can generate 0 or 1 resulting value. + * @param the input value type + * @param the output value type + */ +public abstract class DeferredScalarObserver +extends DeferredScalarDisposable +implements Observer { + + private static final long serialVersionUID = -266195175408988651L; + + /** The upstream disposable. */ + protected Disposable upstream; + + /** + * Creates a DeferredScalarObserver instance and wraps a downstream Observer. + * @param downstream the downstream subscriber, not null (not verified) + */ + public DeferredScalarObserver(Observer downstream) { + super(downstream); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onError(Throwable t) { + value = null; + error(t); + } + + @Override + public void onComplete() { + R v = value; + if (v != null) { + value = null; + complete(v); + } else { + complete(); + } + } + + @Override + public void dispose() { + super.dispose(); + upstream.dispose(); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java b/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java new file mode 100755 index 0000000..59d7fac --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/DisposableLambdaObserver.java @@ -0,0 +1,98 @@ +/** + * 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.internal.observers; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class DisposableLambdaObserver implements Observer, Disposable { + final Observer downstream; + final Consumer onSubscribe; + final Action onDispose; + + Disposable upstream; + + public DisposableLambdaObserver(Observer actual, + Consumer onSubscribe, + Action onDispose) { + this.downstream = actual; + this.onSubscribe = onSubscribe; + this.onDispose = onDispose; + } + + @Override + public void onSubscribe(Disposable d) { + // this way, multiple calls to onSubscribe can show up in tests that use doOnSubscribe to validate behavior + try { + onSubscribe.accept(d); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + d.dispose(); + this.upstream = DisposableHelper.DISPOSED; + EmptyDisposable.error(e, downstream); + return; + } + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (upstream != DisposableHelper.DISPOSED) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (upstream != DisposableHelper.DISPOSED) { + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); + } + } + + @Override + public void dispose() { + Disposable d = upstream; + if (d != DisposableHelper.DISPOSED) { + upstream = DisposableHelper.DISPOSED; + try { + onDispose.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + d.dispose(); + } + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java b/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java new file mode 100755 index 0000000..38d0f37 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/EmptyCompletableObserver.java @@ -0,0 +1,62 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.CompletableObserver; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.OnErrorNotImplementedException; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.observers.LambdaConsumerIntrospection; +import io.reactivex.plugins.RxJavaPlugins; + +public final class EmptyCompletableObserver +extends AtomicReference +implements CompletableObserver, Disposable, LambdaConsumerIntrospection { + + private static final long serialVersionUID = -7545121636549663526L; + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + + @Override + public void onComplete() { + // no-op + lazySet(DisposableHelper.DISPOSED); + } + + @Override + public void onError(Throwable e) { + lazySet(DisposableHelper.DISPOSED); + RxJavaPlugins.onError(new OnErrorNotImplementedException(e)); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public boolean hasCustomOnError() { + return false; + } +} diff --git a/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java b/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java new file mode 100755 index 0000000..22ba3f8 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/ForEachWhileObserver.java @@ -0,0 +1,111 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ForEachWhileObserver +extends AtomicReference +implements Observer, Disposable { + + private static final long serialVersionUID = -4403180040475402120L; + + final Predicate onNext; + + final Consumer onError; + + final Action onComplete; + + boolean done; + + public ForEachWhileObserver(Predicate onNext, + Consumer onError, Action onComplete) { + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + boolean b; + try { + b = onNext.test(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + dispose(); + onError(ex); + return; + } + + if (!b) { + dispose(); + onComplete(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + try { + onError.accept(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(new CompositeException(t, ex)); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + try { + onComplete.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(this.get()); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/FutureObserver.java b/src/main/java/io/reactivex/internal/observers/FutureObserver.java new file mode 100755 index 0000000..9b0d121 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/FutureObserver.java @@ -0,0 +1,175 @@ +/** + * 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.internal.observers; + +import java.util.NoSuchElementException; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.BlockingHelper; +import io.reactivex.plugins.RxJavaPlugins; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + +/** + * An Observer + Future that expects exactly one upstream value and provides it + * via the (blocking) Future API. + * + * @param the value type + */ +public final class FutureObserver extends CountDownLatch +implements Observer, Future, Disposable { + + T value; + Throwable error; + + final AtomicReference upstream; + + public FutureObserver() { + super(1); + this.upstream = new AtomicReference(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + for (;;) { + Disposable a = upstream.get(); + if (a == this || a == DisposableHelper.DISPOSED) { + return false; + } + + if (upstream.compareAndSet(a, DisposableHelper.DISPOSED)) { + if (a != null) { + a.dispose(); + } + countDown(); + return true; + } + } + } + + @Override + public boolean isCancelled() { + return DisposableHelper.isDisposed(upstream.get()); + } + + @Override + public boolean isDone() { + return getCount() == 0; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + if (getCount() != 0) { + BlockingHelper.verifyNonBlocking(); + await(); + } + + if (isCancelled()) { + throw new CancellationException(); + } + Throwable ex = error; + if (ex != null) { + throw new ExecutionException(ex); + } + return value; + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + if (getCount() != 0) { + BlockingHelper.verifyNonBlocking(); + if (!await(timeout, unit)) { + throw new TimeoutException(timeoutMessage(timeout, unit)); + } + } + + if (isCancelled()) { + throw new CancellationException(); + } + + Throwable ex = error; + if (ex != null) { + throw new ExecutionException(ex); + } + return value; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); + } + + @Override + public void onNext(T t) { + if (value != null) { + upstream.get().dispose(); + onError(new IndexOutOfBoundsException("More than one element received")); + return; + } + value = t; + } + + @Override + public void onError(Throwable t) { + if (error == null) { + error = t; + + for (;;) { + Disposable a = upstream.get(); + if (a == this || a == DisposableHelper.DISPOSED) { + RxJavaPlugins.onError(t); + return; + } + if (upstream.compareAndSet(a, this)) { + countDown(); + return; + } + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (value == null) { + onError(new NoSuchElementException("The source is empty")); + return; + } + for (;;) { + Disposable a = upstream.get(); + if (a == this || a == DisposableHelper.DISPOSED) { + return; + } + if (upstream.compareAndSet(a, this)) { + countDown(); + return; + } + } + } + + @Override + public void dispose() { + // ignoring as `this` means a finished Disposable only + } + + @Override + public boolean isDisposed() { + return isDone(); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java b/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java new file mode 100755 index 0000000..1ad8242 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/FutureSingleObserver.java @@ -0,0 +1,152 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.SingleObserver; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.BlockingHelper; +import io.reactivex.plugins.RxJavaPlugins; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + +/** + * An Observer + Future that expects exactly one upstream value and provides it + * via the (blocking) Future API. + * + * @param the value type + */ +public final class FutureSingleObserver extends CountDownLatch +implements SingleObserver, Future, Disposable { + + T value; + Throwable error; + + final AtomicReference upstream; + + public FutureSingleObserver() { + super(1); + this.upstream = new AtomicReference(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + for (;;) { + Disposable a = upstream.get(); + if (a == this || a == DisposableHelper.DISPOSED) { + return false; + } + + if (upstream.compareAndSet(a, DisposableHelper.DISPOSED)) { + if (a != null) { + a.dispose(); + } + countDown(); + return true; + } + } + } + + @Override + public boolean isCancelled() { + return DisposableHelper.isDisposed(upstream.get()); + } + + @Override + public boolean isDone() { + return getCount() == 0; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + if (getCount() != 0) { + BlockingHelper.verifyNonBlocking(); + await(); + } + + if (isCancelled()) { + throw new CancellationException(); + } + Throwable ex = error; + if (ex != null) { + throw new ExecutionException(ex); + } + return value; + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + if (getCount() != 0) { + BlockingHelper.verifyNonBlocking(); + if (!await(timeout, unit)) { + throw new TimeoutException(timeoutMessage(timeout, unit)); + } + } + + if (isCancelled()) { + throw new CancellationException(); + } + + Throwable ex = error; + if (ex != null) { + throw new ExecutionException(ex); + } + return value; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); + } + + @Override + public void onSuccess(T t) { + Disposable a = upstream.get(); + if (a == DisposableHelper.DISPOSED) { + return; + } + value = t; + upstream.compareAndSet(a, this); + countDown(); + } + + @Override + public void onError(Throwable t) { + for (;;) { + Disposable a = upstream.get(); + if (a == DisposableHelper.DISPOSED) { + RxJavaPlugins.onError(t); + return; + } + error = t; + if (upstream.compareAndSet(a, this)) { + countDown(); + return; + } + } + } + + @Override + public void dispose() { + // ignoring as `this` means a finished Disposable only + } + + @Override + public boolean isDisposed() { + return isDone(); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java b/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java new file mode 100755 index 0000000..3a18b38 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/InnerQueuedObserver.java @@ -0,0 +1,121 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.util.QueueDrainHelper; + +/** + * Subscriber that can fuse with the upstream and calls a support interface + * whenever an event is available. + * + * @param the value type + */ +public final class InnerQueuedObserver +extends AtomicReference +implements Observer, Disposable { + + private static final long serialVersionUID = -5417183359794346637L; + + final InnerQueuedObserverSupport parent; + + final int prefetch; + + SimpleQueue queue; + + volatile boolean done; + + int fusionMode; + + public InnerQueuedObserver(InnerQueuedObserverSupport parent, int prefetch) { + this.parent = parent; + this.prefetch = prefetch; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + if (d instanceof QueueDisposable) { + @SuppressWarnings("unchecked") + QueueDisposable qd = (QueueDisposable) d; + + int m = qd.requestFusion(QueueDisposable.ANY); + if (m == QueueSubscription.SYNC) { + fusionMode = m; + queue = qd; + done = true; + parent.innerComplete(this); + return; + } + if (m == QueueDisposable.ASYNC) { + fusionMode = m; + queue = qd; + return; + } + } + + queue = QueueDrainHelper.createQueue(-prefetch); + } + } + + @Override + public void onNext(T t) { + if (fusionMode == QueueDisposable.NONE) { + parent.innerNext(this, t); + } else { + parent.drain(); + } + } + + @Override + public void onError(Throwable t) { + parent.innerError(this, t); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + public boolean isDone() { + return done; + } + + public void setDone() { + this.done = true; + } + + public SimpleQueue queue() { + return queue; + } + + public int fusionMode() { + return fusionMode; + } +} diff --git a/src/main/java/io/reactivex/internal/observers/InnerQueuedObserverSupport.java b/src/main/java/io/reactivex/internal/observers/InnerQueuedObserverSupport.java new file mode 100755 index 0000000..e2ff045 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/InnerQueuedObserverSupport.java @@ -0,0 +1,31 @@ +/** + * 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.internal.observers; + +/** + * Interface to allow the InnerQueuedSubscriber to call back a parent + * with signals. + * + * @param the value type + */ +public interface InnerQueuedObserverSupport { + + void innerNext(InnerQueuedObserver inner, T value); + + void innerError(InnerQueuedObserver inner, Throwable e); + + void innerComplete(InnerQueuedObserver inner); + + void drain(); +} diff --git a/src/main/java/io/reactivex/internal/observers/LambdaObserver.java b/src/main/java/io/reactivex/internal/observers/LambdaObserver.java new file mode 100755 index 0000000..7549910 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/LambdaObserver.java @@ -0,0 +1,114 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.LambdaConsumerIntrospection; +import io.reactivex.plugins.RxJavaPlugins; + +public final class LambdaObserver extends AtomicReference + implements Observer, Disposable, LambdaConsumerIntrospection { + + private static final long serialVersionUID = -7251123623727029452L; + final Consumer onNext; + final Consumer onError; + final Action onComplete; + final Consumer onSubscribe; + + public LambdaObserver(Consumer onNext, Consumer onError, + Action onComplete, + Consumer onSubscribe) { + super(); + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + this.onSubscribe = onSubscribe; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + try { + onSubscribe.accept(this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + d.dispose(); + onError(ex); + } + } + } + + @Override + public void onNext(T t) { + if (!isDisposed()) { + try { + onNext.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + get().dispose(); + onError(e); + } + } + } + + @Override + public void onError(Throwable t) { + if (!isDisposed()) { + lazySet(DisposableHelper.DISPOSED); + try { + onError.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(new CompositeException(t, e)); + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (!isDisposed()) { + lazySet(DisposableHelper.DISPOSED); + try { + onComplete.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } +} diff --git a/src/main/java/io/reactivex/internal/observers/QueueDrainObserver.java b/src/main/java/io/reactivex/internal/observers/QueueDrainObserver.java new file mode 100755 index 0000000..ab24a70 --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/QueueDrainObserver.java @@ -0,0 +1,146 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.util.*; + +/** + * Abstract base class for subscribers that hold another subscriber, a queue + * and requires queue-drain behavior. + * + * @param the source type to which this subscriber will be subscribed + * @param the value type in the queue + * @param the value type the child subscriber accepts + */ +public abstract class QueueDrainObserver extends QueueDrainSubscriberPad2 implements Observer, ObservableQueueDrain { + protected final Observer downstream; + protected final SimplePlainQueue queue; + + protected volatile boolean cancelled; + + protected volatile boolean done; + protected Throwable error; + + public QueueDrainObserver(Observer actual, SimplePlainQueue queue) { + this.downstream = actual; + this.queue = queue; + } + + @Override + public final boolean cancelled() { + return cancelled; + } + + @Override + public final boolean done() { + return done; + } + + @Override + public final boolean enter() { + return wip.getAndIncrement() == 0; + } + + public final boolean fastEnter() { + return wip.get() == 0 && wip.compareAndSet(0, 1); + } + + protected final void fastPathEmit(U value, boolean delayError, Disposable dispose) { + final Observer observer = downstream; + final SimplePlainQueue q = queue; + + if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + accept(observer, value); + if (leave(-1) == 0) { + return; + } + } else { + q.offer(value); + if (!enter()) { + return; + } + } + QueueDrainHelper.drainLoop(q, observer, delayError, dispose, this); + } + + /** + * Makes sure the fast-path emits in order. + * @param value the value to emit or queue up + * @param delayError if true, errors are delayed until the source has terminated + * @param disposable the resource to dispose if the drain terminates + */ + protected final void fastPathOrderedEmit(U value, boolean delayError, Disposable disposable) { + final Observer observer = downstream; + final SimplePlainQueue q = queue; + + if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + if (q.isEmpty()) { + accept(observer, value); + if (leave(-1) == 0) { + return; + } + } else { + q.offer(value); + } + } else { + q.offer(value); + if (!enter()) { + return; + } + } + QueueDrainHelper.drainLoop(q, observer, delayError, disposable, this); + } + + @Override + public final Throwable error() { + return error; + } + + @Override + public final int leave(int m) { + return wip.addAndGet(m); + } + + @Override + public void accept(Observer a, U v) { + // ignored by default + } +} + +// ------------------------------------------------------------------- +// Padding superclasses +//------------------------------------------------------------------- + +/** Pads the header away from other fields. */ +class QueueDrainSubscriberPad0 { + volatile long p1, p2, p3, p4, p5, p6, p7; + volatile long p8, p9, p10, p11, p12, p13, p14, p15; +} + +/** The wip counter. */ +class QueueDrainSubscriberWip extends QueueDrainSubscriberPad0 { + final AtomicInteger wip = new AtomicInteger(); +} + +/** Pads away the wip from the other fields. */ +class QueueDrainSubscriberPad2 extends QueueDrainSubscriberWip { + volatile long p1a, p2a, p3a, p4a, p5a, p6a, p7a; + volatile long p8a, p9a, p10a, p11a, p12a, p13a, p14a, p15a; +} + diff --git a/src/main/java/io/reactivex/internal/observers/ResumeSingleObserver.java b/src/main/java/io/reactivex/internal/observers/ResumeSingleObserver.java new file mode 100755 index 0000000..1dc1c5f --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/ResumeSingleObserver.java @@ -0,0 +1,53 @@ +/** + * 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.internal.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.SingleObserver; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * A SingleObserver implementation used for subscribing to the actual SingleSource + * and replace the current Disposable in a parent AtomicReference. + * + * @param the value type + */ +public final class ResumeSingleObserver implements SingleObserver { + + final AtomicReference parent; + + final SingleObserver downstream; + + public ResumeSingleObserver(AtomicReference parent, SingleObserver downstream) { + this.parent = parent; + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(parent, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } +} diff --git a/src/main/java/io/reactivex/internal/observers/SubscriberCompletableObserver.java b/src/main/java/io/reactivex/internal/observers/SubscriberCompletableObserver.java new file mode 100755 index 0000000..8a5255d --- /dev/null +++ b/src/main/java/io/reactivex/internal/observers/SubscriberCompletableObserver.java @@ -0,0 +1,59 @@ +/** + * 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.internal.observers; + +import org.reactivestreams.*; + +import io.reactivex.CompletableObserver; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class SubscriberCompletableObserver implements CompletableObserver, Subscription { + final Subscriber subscriber; + + Disposable upstream; + + public SubscriberCompletableObserver(Subscriber subscriber) { + this.subscriber = subscriber; + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + + @Override + public void onError(Throwable e) { + subscriber.onError(e); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + subscriber.onSubscribe(this); + } + } + + @Override + public void request(long n) { + // ignored, no values emitted anyway + } + + @Override + public void cancel() { + upstream.dispose(); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java new file mode 100755 index 0000000..7de1c64 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableAmb.java @@ -0,0 +1,133 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableAmb extends Completable { + private final CompletableSource[] sources; + private final Iterable sourcesIterable; + + public CompletableAmb(CompletableSource[] sources, Iterable sourcesIterable) { + this.sources = sources; + this.sourcesIterable = sourcesIterable; + } + + @Override + public void subscribeActual(final CompletableObserver observer) { + CompletableSource[] sources = this.sources; + int count = 0; + if (sources == null) { + sources = new CompletableSource[8]; + try { + for (CompletableSource element : sourcesIterable) { + if (element == null) { + EmptyDisposable.error(new NullPointerException("One of the sources is null"), observer); + return; + } + if (count == sources.length) { + CompletableSource[] b = new CompletableSource[count + (count >> 2)]; + System.arraycopy(sources, 0, b, 0, count); + sources = b; + } + sources[count++] = element; + } + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + } else { + count = sources.length; + } + + final CompositeDisposable set = new CompositeDisposable(); + observer.onSubscribe(set); + + final AtomicBoolean once = new AtomicBoolean(); + + for (int i = 0; i < count; i++) { + CompletableSource c = sources[i]; + if (set.isDisposed()) { + return; + } + if (c == null) { + NullPointerException npe = new NullPointerException("One of the sources is null"); + if (once.compareAndSet(false, true)) { + set.dispose(); + observer.onError(npe); + } else { + RxJavaPlugins.onError(npe); + } + return; + } + + // no need to have separate subscribers because inner is stateless + c.subscribe(new Amb(once, set, observer)); + } + + if (count == 0) { + observer.onComplete(); + } + } + + static final class Amb implements CompletableObserver { + + final AtomicBoolean once; + + final CompositeDisposable set; + + final CompletableObserver downstream; + + Disposable upstream; + + Amb(AtomicBoolean once, CompositeDisposable set, CompletableObserver observer) { + this.once = once; + this.set = set; + this.downstream = observer; + } + + @Override + public void onComplete() { + if (once.compareAndSet(false, true)) { + set.delete(upstream); + set.dispose(); + downstream.onComplete(); + } + } + + @Override + public void onError(Throwable e) { + if (once.compareAndSet(false, true)) { + set.delete(upstream); + set.dispose(); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onSubscribe(Disposable d) { + upstream = d; + set.add(d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java new file mode 100755 index 0000000..ff7b6cc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java @@ -0,0 +1,107 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class CompletableAndThenCompletable extends Completable { + + final CompletableSource source; + + final CompletableSource next; + + public CompletableAndThenCompletable(CompletableSource source, CompletableSource next) { + this.source = source; + this.next = next; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new SourceObserver(observer, next)); + } + + static final class SourceObserver + extends AtomicReference + implements CompletableObserver, Disposable { + + private static final long serialVersionUID = -4101678820158072998L; + + final CompletableObserver actualObserver; + + final CompletableSource next; + + SourceObserver(CompletableObserver actualObserver, CompletableSource next) { + this.actualObserver = actualObserver; + this.next = next; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + actualObserver.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + actualObserver.onError(e); + } + + @Override + public void onComplete() { + next.subscribe(new NextObserver(this, actualObserver)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + + static final class NextObserver implements CompletableObserver { + + final AtomicReference parent; + + final CompletableObserver downstream; + + NextObserver(AtomicReference parent, CompletableObserver downstream) { + this.parent = parent; + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(parent, d); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableCache.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableCache.java new file mode 100755 index 0000000..ef1cc3c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableCache.java @@ -0,0 +1,170 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; + +/** + * Consume the upstream source exactly once and cache its terminal event. + *

History: 2.0.4 - experimental + * @since 2.1 + */ +public final class CompletableCache extends Completable implements CompletableObserver { + + static final InnerCompletableCache[] EMPTY = new InnerCompletableCache[0]; + + static final InnerCompletableCache[] TERMINATED = new InnerCompletableCache[0]; + + final CompletableSource source; + + final AtomicReference observers; + + final AtomicBoolean once; + + Throwable error; + + public CompletableCache(CompletableSource source) { + this.source = source; + this.observers = new AtomicReference(EMPTY); + this.once = new AtomicBoolean(); + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + InnerCompletableCache inner = new InnerCompletableCache(observer); + observer.onSubscribe(inner); + + if (add(inner)) { + if (inner.isDisposed()) { + remove(inner); + } + + if (once.compareAndSet(false, true)) { + source.subscribe(this); + } + } else { + Throwable ex = error; + if (ex != null) { + observer.onError(ex); + } else { + observer.onComplete(); + } + } + } + + @Override + public void onSubscribe(Disposable d) { + // not used + } + + @Override + public void onError(Throwable e) { + error = e; + for (InnerCompletableCache inner : observers.getAndSet(TERMINATED)) { + if (!inner.get()) { + inner.downstream.onError(e); + } + } + } + + @Override + public void onComplete() { + for (InnerCompletableCache inner : observers.getAndSet(TERMINATED)) { + if (!inner.get()) { + inner.downstream.onComplete(); + } + } + } + + boolean add(InnerCompletableCache inner) { + for (;;) { + InnerCompletableCache[] a = observers.get(); + if (a == TERMINATED) { + return false; + } + int n = a.length; + InnerCompletableCache[] b = new InnerCompletableCache[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (observers.compareAndSet(a, b)) { + return true; + } + } + } + + void remove(InnerCompletableCache inner) { + for (;;) { + InnerCompletableCache[] a = observers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + InnerCompletableCache[] b; + + if (n == 1) { + b = EMPTY; + } else { + b = new InnerCompletableCache[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + + if (observers.compareAndSet(a, b)) { + break; + } + } + } + + final class InnerCompletableCache + extends AtomicBoolean + implements Disposable { + + private static final long serialVersionUID = 8943152917179642732L; + + final CompletableObserver downstream; + + InnerCompletableCache(CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public boolean isDisposed() { + return get(); + } + + @Override + public void dispose() { + if (compareAndSet(false, true)) { + remove(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableConcat.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcat.java new file mode 100755 index 0000000..cf52bf9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcat.java @@ -0,0 +1,255 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableConcat extends Completable { + final Publisher sources; + final int prefetch; + + public CompletableConcat(Publisher sources, int prefetch) { + this.sources = sources; + this.prefetch = prefetch; + } + + @Override + public void subscribeActual(CompletableObserver observer) { + sources.subscribe(new CompletableConcatSubscriber(observer, prefetch)); + } + + static final class CompletableConcatSubscriber + extends AtomicInteger + implements FlowableSubscriber, Disposable { + private static final long serialVersionUID = 9032184911934499404L; + + final CompletableObserver downstream; + + final int prefetch; + + final int limit; + + final ConcatInnerObserver inner; + + final AtomicBoolean once; + + int sourceFused; + + int consumed; + + SimpleQueue queue; + + Subscription upstream; + + volatile boolean done; + + volatile boolean active; + + CompletableConcatSubscriber(CompletableObserver actual, int prefetch) { + this.downstream = actual; + this.prefetch = prefetch; + this.inner = new ConcatInnerObserver(this); + this.once = new AtomicBoolean(); + this.limit = prefetch - (prefetch >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + long r = prefetch == Integer.MAX_VALUE ? Long.MAX_VALUE : prefetch; + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY); + + if (m == QueueSubscription.SYNC) { + sourceFused = m; + queue = qs; + done = true; + downstream.onSubscribe(this); + drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceFused = m; + queue = qs; + downstream.onSubscribe(this); + s.request(r); + return; + } + } + + if (prefetch == Integer.MAX_VALUE) { + queue = new SpscLinkedArrayQueue(Flowable.bufferSize()); + } else { + queue = new SpscArrayQueue(prefetch); + } + + downstream.onSubscribe(this); + + s.request(r); + } + } + + @Override + public void onNext(CompletableSource t) { + if (sourceFused == QueueSubscription.NONE) { + if (!queue.offer(t)) { + onError(new MissingBackpressureException()); + return; + } + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(inner); + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + upstream.cancel(); + DisposableHelper.dispose(inner); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(inner.get()); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + for (;;) { + if (isDisposed()) { + return; + } + + if (!active) { + + boolean d = done; + + CompletableSource cs; + + try { + cs = queue.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + innerError(ex); + return; + } + + boolean empty = cs == null; + + if (d && empty) { + if (once.compareAndSet(false, true)) { + downstream.onComplete(); + } + return; + } + + if (!empty) { + active = true; + cs.subscribe(inner); + request(); + } + } + + if (decrementAndGet() == 0) { + break; + } + } + } + + void request() { + if (sourceFused != QueueSubscription.SYNC) { + int p = consumed + 1; + if (p == limit) { + consumed = 0; + upstream.request(p); + } else { + consumed = p; + } + } + } + + void innerError(Throwable e) { + if (once.compareAndSet(false, true)) { + upstream.cancel(); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + void innerComplete() { + active = false; + drain(); + } + + static final class ConcatInnerObserver extends AtomicReference implements CompletableObserver { + private static final long serialVersionUID = -5454794857847146511L; + + final CompletableConcatSubscriber parent; + + ConcatInnerObserver(CompletableConcatSubscriber parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatArray.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatArray.java new file mode 100755 index 0000000..6bcf9d5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatArray.java @@ -0,0 +1,93 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.SequentialDisposable; + +public final class CompletableConcatArray extends Completable { + final CompletableSource[] sources; + + public CompletableConcatArray(CompletableSource[] sources) { + this.sources = sources; + } + + @Override + public void subscribeActual(CompletableObserver observer) { + ConcatInnerObserver inner = new ConcatInnerObserver(observer, sources); + observer.onSubscribe(inner.sd); + inner.next(); + } + + static final class ConcatInnerObserver extends AtomicInteger implements CompletableObserver { + + private static final long serialVersionUID = -7965400327305809232L; + + final CompletableObserver downstream; + final CompletableSource[] sources; + + int index; + + final SequentialDisposable sd; + + ConcatInnerObserver(CompletableObserver actual, CompletableSource[] sources) { + this.downstream = actual; + this.sources = sources; + this.sd = new SequentialDisposable(); + } + + @Override + public void onSubscribe(Disposable d) { + sd.replace(d); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + next(); + } + + void next() { + if (sd.isDisposed()) { + return; + } + + if (getAndIncrement() != 0) { + return; + } + + CompletableSource[] a = sources; + do { + if (sd.isDisposed()) { + return; + } + + int idx = index++; + if (idx == a.length) { + downstream.onComplete(); + return; + } + + a[idx].subscribe(this); + } while (decrementAndGet() != 0); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatIterable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatIterable.java new file mode 100755 index 0000000..acc2d12 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableConcatIterable.java @@ -0,0 +1,123 @@ +/** + * 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.internal.operators.completable; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; + +public final class CompletableConcatIterable extends Completable { + final Iterable sources; + + public CompletableConcatIterable(Iterable sources) { + this.sources = sources; + } + + @Override + public void subscribeActual(CompletableObserver observer) { + + Iterator it; + + try { + it = ObjectHelper.requireNonNull(sources.iterator(), "The iterator returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + + ConcatInnerObserver inner = new ConcatInnerObserver(observer, it); + observer.onSubscribe(inner.sd); + inner.next(); + } + + static final class ConcatInnerObserver extends AtomicInteger implements CompletableObserver { + + private static final long serialVersionUID = -7965400327305809232L; + + final CompletableObserver downstream; + final Iterator sources; + + final SequentialDisposable sd; + + ConcatInnerObserver(CompletableObserver actual, Iterator sources) { + this.downstream = actual; + this.sources = sources; + this.sd = new SequentialDisposable(); + } + + @Override + public void onSubscribe(Disposable d) { + sd.replace(d); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + next(); + } + + void next() { + if (sd.isDisposed()) { + return; + } + + if (getAndIncrement() != 0) { + return; + } + + Iterator a = sources; + do { + if (sd.isDisposed()) { + return; + } + + boolean b; + try { + b = a.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + if (!b) { + downstream.onComplete(); + return; + } + + CompletableSource c; + + try { + c = ObjectHelper.requireNonNull(a.next(), "The CompletableSource returned is null"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + c.subscribe(this); + } while (decrementAndGet() != 0); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableCreate.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableCreate.java new file mode 100755 index 0000000..1e2bc74 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableCreate.java @@ -0,0 +1,127 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Cancellable; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableCreate extends Completable { + + final CompletableOnSubscribe source; + + public CompletableCreate(CompletableOnSubscribe source) { + this.source = source; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + Emitter parent = new Emitter(observer); + observer.onSubscribe(parent); + + try { + source.subscribe(parent); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + parent.onError(ex); + } + } + + static final class Emitter + extends AtomicReference + implements CompletableEmitter, Disposable { + + private static final long serialVersionUID = -2467358622224974244L; + + final CompletableObserver downstream; + + Emitter(CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onComplete() { + if (get() != DisposableHelper.DISPOSED) { + Disposable d = getAndSet(DisposableHelper.DISPOSED); + if (d != DisposableHelper.DISPOSED) { + try { + downstream.onComplete(); + } finally { + if (d != null) { + d.dispose(); + } + } + } + } + } + + @Override + public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + if (get() != DisposableHelper.DISPOSED) { + Disposable d = getAndSet(DisposableHelper.DISPOSED); + if (d != DisposableHelper.DISPOSED) { + try { + downstream.onError(t); + } finally { + if (d != null) { + d.dispose(); + } + } + return true; + } + } + return false; + } + + @Override + public void setDisposable(Disposable d) { + DisposableHelper.set(this, d); + } + + @Override + public void setCancellable(Cancellable c) { + setDisposable(new CancellableDisposable(c)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDefer.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDefer.java new file mode 100755 index 0000000..030ca4f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDefer.java @@ -0,0 +1,46 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; + +public final class CompletableDefer extends Completable { + + final Callable completableSupplier; + + public CompletableDefer(Callable completableSupplier) { + this.completableSupplier = completableSupplier; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + CompletableSource c; + + try { + c = ObjectHelper.requireNonNull(completableSupplier.call(), "The completableSupplier returned a null CompletableSource"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + + c.subscribe(observer); + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDelay.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDelay.java new file mode 100755 index 0000000..a23fd00 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDelay.java @@ -0,0 +1,112 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class CompletableDelay extends Completable { + + final CompletableSource source; + + final long delay; + + final TimeUnit unit; + + final Scheduler scheduler; + + final boolean delayError; + + public CompletableDelay(CompletableSource source, long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) { + this.source = source; + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + this.delayError = delayError; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + source.subscribe(new Delay(observer, delay, unit, scheduler, delayError)); + } + + static final class Delay extends AtomicReference + implements CompletableObserver, Runnable, Disposable { + + private static final long serialVersionUID = 465972761105851022L; + + final CompletableObserver downstream; + + final long delay; + + final TimeUnit unit; + + final Scheduler scheduler; + + final boolean delayError; + + Throwable error; + + Delay(CompletableObserver downstream, long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) { + this.downstream = downstream; + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + this.delayError = delayError; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onComplete() { + DisposableHelper.replace(this, scheduler.scheduleDirect(this, delay, unit)); + } + + @Override + public void onError(final Throwable e) { + error = e; + DisposableHelper.replace(this, scheduler.scheduleDirect(this, delayError ? delay : 0, unit)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void run() { + Throwable e = error; + error = null; + if (e != null) { + downstream.onError(e); + } else { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDetach.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDetach.java new file mode 100755 index 0000000..f19a0a0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDetach.java @@ -0,0 +1,89 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Breaks the references between the upstream and downstream when the Completable terminates. + *

History: 2.1.5 - experimental + * @since 2.2 + */ +public final class CompletableDetach extends Completable { + + final CompletableSource source; + + public CompletableDetach(CompletableSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new DetachCompletableObserver(observer)); + } + + static final class DetachCompletableObserver implements CompletableObserver, Disposable { + + CompletableObserver downstream; + + Disposable upstream; + + DetachCompletableObserver(CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + downstream = null; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + CompletableObserver a = downstream; + if (a != null) { + downstream = null; + a.onError(e); + } + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + CompletableObserver a = downstream; + if (a != null) { + downstream = null; + a.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDisposeOn.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDisposeOn.java new file mode 100755 index 0000000..904894d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDisposeOn.java @@ -0,0 +1,95 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableDisposeOn extends Completable { + + final CompletableSource source; + + final Scheduler scheduler; + + public CompletableDisposeOn(CompletableSource source, Scheduler scheduler) { + this.source = source; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + source.subscribe(new DisposeOnObserver(observer, scheduler)); + } + + static final class DisposeOnObserver implements CompletableObserver, Disposable, Runnable { + final CompletableObserver downstream; + + final Scheduler scheduler; + + Disposable upstream; + + volatile boolean disposed; + + DisposeOnObserver(CompletableObserver observer, Scheduler scheduler) { + this.downstream = observer; + this.scheduler = scheduler; + } + + @Override + public void onComplete() { + if (disposed) { + return; + } + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + if (disposed) { + RxJavaPlugins.onError(e); + return; + } + downstream.onError(e); + } + + @Override + public void onSubscribe(final Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + disposed = true; + scheduler.scheduleDirect(this); + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void run() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDoFinally.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDoFinally.java new file mode 100755 index 0000000..67e4ad5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDoFinally.java @@ -0,0 +1,104 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Execute an action after an onError, onComplete or a dispose event. + *

History: 2.0.1 - experimental + * @since 2.1 + */ +public final class CompletableDoFinally extends Completable { + + final CompletableSource source; + + final Action onFinally; + + public CompletableDoFinally(CompletableSource source, Action onFinally) { + this.source = source; + this.onFinally = onFinally; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new DoFinallyObserver(observer, onFinally)); + } + + static final class DoFinallyObserver extends AtomicInteger implements CompletableObserver, Disposable { + + private static final long serialVersionUID = 4109457741734051389L; + + final CompletableObserver downstream; + + final Action onFinally; + + Disposable upstream; + + DoFinallyObserver(CompletableObserver actual, Action onFinally) { + this.downstream = actual; + this.onFinally = onFinally; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + runFinally(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + runFinally(); + } + + @Override + public void dispose() { + upstream.dispose(); + runFinally(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + void runFinally() { + if (compareAndSet(0, 1)) { + try { + onFinally.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableDoOnEvent.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableDoOnEvent.java new file mode 100755 index 0000000..3499a55 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableDoOnEvent.java @@ -0,0 +1,75 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.Completable; +import io.reactivex.CompletableObserver; +import io.reactivex.CompletableSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; + +public final class CompletableDoOnEvent extends Completable { + final CompletableSource source; + final Consumer onEvent; + + public CompletableDoOnEvent(final CompletableSource source, final Consumer onEvent) { + this.source = source; + this.onEvent = onEvent; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + source.subscribe(new DoOnEvent(observer)); + } + + final class DoOnEvent implements CompletableObserver { + private final CompletableObserver observer; + + DoOnEvent(CompletableObserver observer) { + this.observer = observer; + } + + @Override + public void onComplete() { + try { + onEvent.accept(null); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + observer.onError(e); + return; + } + + observer.onComplete(); + } + + @Override + public void onError(Throwable e) { + try { + onEvent.accept(e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + observer.onError(e); + } + + @Override + public void onSubscribe(final Disposable d) { + observer.onSubscribe(d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableEmpty.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableEmpty.java new file mode 100755 index 0000000..dc6b6c5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableEmpty.java @@ -0,0 +1,29 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.internal.disposables.EmptyDisposable; + +public final class CompletableEmpty extends Completable { + public static final Completable INSTANCE = new CompletableEmpty(); + + private CompletableEmpty() { + } + + @Override + public void subscribeActual(CompletableObserver observer) { + EmptyDisposable.complete(observer); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableError.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableError.java new file mode 100755 index 0000000..e7d6a23 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableError.java @@ -0,0 +1,31 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.internal.disposables.EmptyDisposable; + +public final class CompletableError extends Completable { + + final Throwable error; + + public CompletableError(Throwable error) { + this.error = error; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + EmptyDisposable.error(error, observer); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableErrorSupplier.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableErrorSupplier.java new file mode 100755 index 0000000..16df486 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableErrorSupplier.java @@ -0,0 +1,45 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; + +public final class CompletableErrorSupplier extends Completable { + + final Callable errorSupplier; + + public CompletableErrorSupplier(Callable errorSupplier) { + this.errorSupplier = errorSupplier; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + Throwable error; + + try { + error = ObjectHelper.requireNonNull(errorSupplier.call(), "The error returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + error = e; + } + + EmptyDisposable.error(error, observer); + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java new file mode 100755 index 0000000..6722722 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java @@ -0,0 +1,50 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableFromAction extends Completable { + + final Action run; + + public CompletableFromAction(Action run) { + this.run = run; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + Disposable d = Disposables.empty(); + observer.onSubscribe(d); + try { + run.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + if (!d.isDisposed()) { + observer.onError(e); + } else { + RxJavaPlugins.onError(e); + } + return; + } + if (!d.isDisposed()) { + observer.onComplete(); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java new file mode 100755 index 0000000..6b7e68d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromCallable.java @@ -0,0 +1,50 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableFromCallable extends Completable { + + final Callable callable; + + public CompletableFromCallable(Callable callable) { + this.callable = callable; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + Disposable d = Disposables.empty(); + observer.onSubscribe(d); + try { + callable.call(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + if (!d.isDisposed()) { + observer.onError(e); + } else { + RxJavaPlugins.onError(e); + } + return; + } + if (!d.isDisposed()) { + observer.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromObservable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromObservable.java new file mode 100755 index 0000000..fdf3523 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromObservable.java @@ -0,0 +1,59 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; + +public final class CompletableFromObservable extends Completable { + + final ObservableSource observable; + + public CompletableFromObservable(ObservableSource observable) { + this.observable = observable; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + observable.subscribe(new CompletableFromObservableObserver(observer)); + } + + static final class CompletableFromObservableObserver implements Observer { + final CompletableObserver co; + + CompletableFromObservableObserver(CompletableObserver co) { + this.co = co; + } + + @Override + public void onSubscribe(Disposable d) { + co.onSubscribe(d); + } + + @Override + public void onNext(T value) { + // Deliberately ignored. + } + + @Override + public void onError(Throwable e) { + co.onError(e); + } + + @Override + public void onComplete() { + co.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java new file mode 100755 index 0000000..ca33d58 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromPublisher.java @@ -0,0 +1,83 @@ +/** + * 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.internal.operators.completable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +public final class CompletableFromPublisher extends Completable { + + final Publisher flowable; + + public CompletableFromPublisher(Publisher flowable) { + this.flowable = flowable; + } + + @Override + protected void subscribeActual(final CompletableObserver downstream) { + flowable.subscribe(new FromPublisherSubscriber(downstream)); + } + + static final class FromPublisherSubscriber implements FlowableSubscriber, Disposable { + + final CompletableObserver downstream; + + Subscription upstream; + + FromPublisherSubscriber(CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + // ignored + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java new file mode 100755 index 0000000..3ce78a1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java @@ -0,0 +1,50 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.Completable; +import io.reactivex.CompletableObserver; +import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableFromRunnable extends Completable { + + final Runnable runnable; + + public CompletableFromRunnable(Runnable runnable) { + this.runnable = runnable; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + Disposable d = Disposables.empty(); + observer.onSubscribe(d); + try { + runnable.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + if (!d.isDisposed()) { + observer.onError(e); + } else { + RxJavaPlugins.onError(e); + } + return; + } + if (!d.isDisposed()) { + observer.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromSingle.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromSingle.java new file mode 100755 index 0000000..251ae5c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromSingle.java @@ -0,0 +1,54 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; + +public final class CompletableFromSingle extends Completable { + + final SingleSource single; + + public CompletableFromSingle(SingleSource single) { + this.single = single; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + single.subscribe(new CompletableFromSingleObserver(observer)); + } + + static final class CompletableFromSingleObserver implements SingleObserver { + final CompletableObserver co; + + CompletableFromSingleObserver(CompletableObserver co) { + this.co = co; + } + + @Override + public void onError(Throwable e) { + co.onError(e); + } + + @Override + public void onSubscribe(Disposable d) { + co.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + co.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromUnsafeSource.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromUnsafeSource.java new file mode 100755 index 0000000..e3e6068 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromUnsafeSource.java @@ -0,0 +1,30 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; + +public final class CompletableFromUnsafeSource extends Completable { + + final CompletableSource source; + + public CompletableFromUnsafeSource(CompletableSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(observer); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableHide.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableHide.java new file mode 100755 index 0000000..0e1828d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableHide.java @@ -0,0 +1,78 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.Completable; +import io.reactivex.CompletableObserver; +import io.reactivex.CompletableSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Hides the identity of the upstream Completable and its Disposable sent through onSubscribe. + */ +public final class CompletableHide extends Completable { + + final CompletableSource source; + + public CompletableHide(CompletableSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new HideCompletableObserver(observer)); + } + + static final class HideCompletableObserver implements CompletableObserver, Disposable { + + final CompletableObserver downstream; + + Disposable upstream; + + HideCompletableObserver(CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableLift.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableLift.java new file mode 100755 index 0000000..25a08d5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableLift.java @@ -0,0 +1,47 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableLift extends Completable { + + final CompletableSource source; + + final CompletableOperator onLift; + + public CompletableLift(CompletableSource source, CompletableOperator onLift) { + this.source = source; + this.onLift = onLift; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + try { + // TODO plugin wrapping + + CompletableObserver sw = onLift.apply(observer); + + source.subscribe(sw); + } catch (NullPointerException ex) { // NOPMD + throw ex; + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMaterialize.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMaterialize.java new file mode 100755 index 0000000..5eda7f6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMaterialize.java @@ -0,0 +1,40 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.internal.operators.mixed.MaterializeSingleObserver; + +/** + * Turn the signal types of a Completable source into a single Notification of + * equal kind. + * + * @param the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class CompletableMaterialize extends Single> { + + final Completable source; + + public CompletableMaterialize(Completable source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver> observer) { + source.subscribe(new MaterializeSingleObserver(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMerge.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMerge.java new file mode 100755 index 0000000..a1dff5b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMerge.java @@ -0,0 +1,212 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableMerge extends Completable { + final Publisher source; + final int maxConcurrency; + final boolean delayErrors; + + public CompletableMerge(Publisher source, int maxConcurrency, boolean delayErrors) { + this.source = source; + this.maxConcurrency = maxConcurrency; + this.delayErrors = delayErrors; + } + + @Override + public void subscribeActual(CompletableObserver observer) { + CompletableMergeSubscriber parent = new CompletableMergeSubscriber(observer, maxConcurrency, delayErrors); + source.subscribe(parent); + } + + static final class CompletableMergeSubscriber + extends AtomicInteger + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = -2108443387387077490L; + + final CompletableObserver downstream; + final int maxConcurrency; + final boolean delayErrors; + + final AtomicThrowable error; + + final CompositeDisposable set; + + Subscription upstream; + + CompletableMergeSubscriber(CompletableObserver actual, int maxConcurrency, boolean delayErrors) { + this.downstream = actual; + this.maxConcurrency = maxConcurrency; + this.delayErrors = delayErrors; + this.set = new CompositeDisposable(); + this.error = new AtomicThrowable(); + lazySet(1); + } + + @Override + public void dispose() { + upstream.cancel(); + set.dispose(); + } + + @Override + public boolean isDisposed() { + return set.isDisposed(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + if (maxConcurrency == Integer.MAX_VALUE) { + s.request(Long.MAX_VALUE); + } else { + s.request(maxConcurrency); + } + } + } + + @Override + public void onNext(CompletableSource t) { + getAndIncrement(); + + MergeInnerObserver inner = new MergeInnerObserver(); + set.add(inner); + t.subscribe(inner); + } + + @Override + public void onError(Throwable t) { + if (!delayErrors) { + set.dispose(); + + if (error.addThrowable(t)) { + if (getAndSet(0) > 0) { + downstream.onError(error.terminate()); + } + } else { + RxJavaPlugins.onError(t); + } + } else { + if (error.addThrowable(t)) { + if (decrementAndGet() == 0) { + downstream.onError(error.terminate()); + } + } else { + RxJavaPlugins.onError(t); + } + } + } + + @Override + public void onComplete() { + if (decrementAndGet() == 0) { + Throwable ex = error.get(); + if (ex != null) { + downstream.onError(error.terminate()); + } else { + downstream.onComplete(); + } + } + } + + void innerError(MergeInnerObserver inner, Throwable t) { + set.delete(inner); + if (!delayErrors) { + upstream.cancel(); + set.dispose(); + + if (error.addThrowable(t)) { + if (getAndSet(0) > 0) { + downstream.onError(error.terminate()); + } + } else { + RxJavaPlugins.onError(t); + } + } else { + if (error.addThrowable(t)) { + if (decrementAndGet() == 0) { + downstream.onError(error.terminate()); + } else { + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } + } else { + RxJavaPlugins.onError(t); + } + } + } + + void innerComplete(MergeInnerObserver inner) { + set.delete(inner); + if (decrementAndGet() == 0) { + Throwable ex = error.get(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + } else { + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } + } + + final class MergeInnerObserver + extends AtomicReference + implements CompletableObserver, Disposable { + private static final long serialVersionUID = 251330541679988317L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public void onComplete() { + innerComplete(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeArray.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeArray.java new file mode 100755 index 0000000..e6a42b1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeArray.java @@ -0,0 +1,95 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableMergeArray extends Completable { + final CompletableSource[] sources; + + public CompletableMergeArray(CompletableSource[] sources) { + this.sources = sources; + } + + @Override + public void subscribeActual(final CompletableObserver observer) { + final CompositeDisposable set = new CompositeDisposable(); + final AtomicBoolean once = new AtomicBoolean(); + + InnerCompletableObserver shared = new InnerCompletableObserver(observer, once, set, sources.length + 1); + observer.onSubscribe(set); + + for (CompletableSource c : sources) { + if (set.isDisposed()) { + return; + } + + if (c == null) { + set.dispose(); + NullPointerException npe = new NullPointerException("A completable source is null"); + shared.onError(npe); + return; + } + + c.subscribe(shared); + } + + shared.onComplete(); + } + + static final class InnerCompletableObserver extends AtomicInteger implements CompletableObserver { + private static final long serialVersionUID = -8360547806504310570L; + + final CompletableObserver downstream; + + final AtomicBoolean once; + + final CompositeDisposable set; + + InnerCompletableObserver(CompletableObserver actual, AtomicBoolean once, CompositeDisposable set, int n) { + this.downstream = actual; + this.once = once; + this.set = set; + this.lazySet(n); + } + + @Override + public void onSubscribe(Disposable d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + set.dispose(); + if (once.compareAndSet(false, true)) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (decrementAndGet() == 0) { + if (once.compareAndSet(false, true)) { + downstream.onComplete(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorArray.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorArray.java new file mode 100755 index 0000000..c2508a4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorArray.java @@ -0,0 +1,110 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableMergeDelayErrorArray extends Completable { + + final CompletableSource[] sources; + + public CompletableMergeDelayErrorArray(CompletableSource[] sources) { + this.sources = sources; + } + + @Override + public void subscribeActual(final CompletableObserver observer) { + final CompositeDisposable set = new CompositeDisposable(); + final AtomicInteger wip = new AtomicInteger(sources.length + 1); + + final AtomicThrowable error = new AtomicThrowable(); + + observer.onSubscribe(set); + + for (CompletableSource c : sources) { + if (set.isDisposed()) { + return; + } + + if (c == null) { + Throwable ex = new NullPointerException("A completable source is null"); + error.addThrowable(ex); + wip.decrementAndGet(); + continue; + } + + c.subscribe(new MergeInnerCompletableObserver(observer, set, error, wip)); + } + + if (wip.decrementAndGet() == 0) { + Throwable ex = error.terminate(); + if (ex == null) { + observer.onComplete(); + } else { + observer.onError(ex); + } + } + } + + static final class MergeInnerCompletableObserver + implements CompletableObserver { + final CompletableObserver downstream; + final CompositeDisposable set; + final AtomicThrowable error; + final AtomicInteger wip; + + MergeInnerCompletableObserver(CompletableObserver observer, CompositeDisposable set, AtomicThrowable error, + AtomicInteger wip) { + this.downstream = observer; + this.set = set; + this.error = error; + this.wip = wip; + } + + @Override + public void onSubscribe(Disposable d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + if (error.addThrowable(e)) { + tryTerminate(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + tryTerminate(); + } + + void tryTerminate() { + if (wip.decrementAndGet() == 0) { + Throwable ex = error.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorIterable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorIterable.java new file mode 100755 index 0000000..40c84d1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeDelayErrorIterable.java @@ -0,0 +1,104 @@ +/** + * 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.internal.operators.completable; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.completable.CompletableMergeDelayErrorArray.MergeInnerCompletableObserver; +import io.reactivex.internal.util.AtomicThrowable; + +public final class CompletableMergeDelayErrorIterable extends Completable { + + final Iterable sources; + + public CompletableMergeDelayErrorIterable(Iterable sources) { + this.sources = sources; + } + + @Override + public void subscribeActual(final CompletableObserver observer) { + final CompositeDisposable set = new CompositeDisposable(); + + observer.onSubscribe(set); + + Iterator iterator; + + try { + iterator = ObjectHelper.requireNonNull(sources.iterator(), "The source iterator returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + observer.onError(e); + return; + } + + final AtomicInteger wip = new AtomicInteger(1); + + final AtomicThrowable error = new AtomicThrowable(); + + for (;;) { + if (set.isDisposed()) { + return; + } + + boolean b; + try { + b = iterator.hasNext(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + error.addThrowable(e); + break; + } + + if (!b) { + break; + } + + if (set.isDisposed()) { + return; + } + + CompletableSource c; + + try { + c = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null CompletableSource"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + error.addThrowable(e); + break; + } + + if (set.isDisposed()) { + return; + } + + wip.getAndIncrement(); + + c.subscribe(new MergeInnerCompletableObserver(observer, set, error, wip)); + } + + if (wip.decrementAndGet() == 0) { + Throwable ex = error.terminate(); + if (ex == null) { + observer.onComplete(); + } else { + observer.onError(ex); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeIterable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeIterable.java new file mode 100755 index 0000000..0130250 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableMergeIterable.java @@ -0,0 +1,137 @@ +/** + * 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.internal.operators.completable; + +import java.util.Iterator; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableMergeIterable extends Completable { + final Iterable sources; + + public CompletableMergeIterable(Iterable sources) { + this.sources = sources; + } + + @Override + public void subscribeActual(final CompletableObserver observer) { + final CompositeDisposable set = new CompositeDisposable(); + + observer.onSubscribe(set); + + Iterator iterator; + + try { + iterator = ObjectHelper.requireNonNull(sources.iterator(), "The source iterator returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + observer.onError(e); + return; + } + + final AtomicInteger wip = new AtomicInteger(1); + + MergeCompletableObserver shared = new MergeCompletableObserver(observer, set, wip); + for (;;) { + if (set.isDisposed()) { + return; + } + + boolean b; + try { + b = iterator.hasNext(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + set.dispose(); + shared.onError(e); + return; + } + + if (!b) { + break; + } + + if (set.isDisposed()) { + return; + } + + CompletableSource c; + + try { + c = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null CompletableSource"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + set.dispose(); + shared.onError(e); + return; + } + + if (set.isDisposed()) { + return; + } + + wip.getAndIncrement(); + + c.subscribe(shared); + } + + shared.onComplete(); + } + + static final class MergeCompletableObserver extends AtomicBoolean implements CompletableObserver { + + private static final long serialVersionUID = -7730517613164279224L; + + final CompositeDisposable set; + + final CompletableObserver downstream; + + final AtomicInteger wip; + + MergeCompletableObserver(CompletableObserver actual, CompositeDisposable set, AtomicInteger wip) { + this.downstream = actual; + this.set = set; + this.wip = wip; + } + + @Override + public void onSubscribe(Disposable d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + set.dispose(); + if (compareAndSet(false, true)) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (wip.decrementAndGet() == 0) { + if (compareAndSet(false, true)) { + downstream.onComplete(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableNever.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableNever.java new file mode 100755 index 0000000..73b52d5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableNever.java @@ -0,0 +1,30 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.internal.disposables.EmptyDisposable; + +public final class CompletableNever extends Completable { + public static final Completable INSTANCE = new CompletableNever(); + + private CompletableNever() { + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + observer.onSubscribe(EmptyDisposable.NEVER); + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java new file mode 100755 index 0000000..b931ed4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableObserveOn.java @@ -0,0 +1,94 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class CompletableObserveOn extends Completable { + + final CompletableSource source; + + final Scheduler scheduler; + public CompletableObserveOn(CompletableSource source, Scheduler scheduler) { + this.source = source; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + source.subscribe(new ObserveOnCompletableObserver(observer, scheduler)); + } + + static final class ObserveOnCompletableObserver + extends AtomicReference + implements CompletableObserver, Disposable, Runnable { + + private static final long serialVersionUID = 8571289934935992137L; + + final CompletableObserver downstream; + + final Scheduler scheduler; + + Throwable error; + + ObserveOnCompletableObserver(CompletableObserver actual, Scheduler scheduler) { + this.downstream = actual; + this.scheduler = scheduler; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + this.error = e; + DisposableHelper.replace(this, scheduler.scheduleDirect(this)); + } + + @Override + public void onComplete() { + DisposableHelper.replace(this, scheduler.scheduleDirect(this)); + } + + @Override + public void run() { + Throwable ex = error; + if (ex != null) { + error = null; + downstream.onError(ex); + } else { + downstream.onComplete(); + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableOnErrorComplete.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableOnErrorComplete.java new file mode 100755 index 0000000..5ff0cc3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableOnErrorComplete.java @@ -0,0 +1,76 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Predicate; + +public final class CompletableOnErrorComplete extends Completable { + + final CompletableSource source; + + final Predicate predicate; + + public CompletableOnErrorComplete(CompletableSource source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + + source.subscribe(new OnError(observer)); + } + + final class OnError implements CompletableObserver { + + private final CompletableObserver downstream; + + OnError(CompletableObserver observer) { + this.downstream = observer; + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + boolean b; + + try { + b = predicate.test(e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(new CompositeException(e, ex)); + return; + } + + if (b) { + downstream.onComplete(); + } else { + downstream.onError(e); + } + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java b/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java new file mode 100755 index 0000000..02180e4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletablePeek.java @@ -0,0 +1,145 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletablePeek extends Completable { + + final CompletableSource source; + final Consumer onSubscribe; + final Consumer onError; + final Action onComplete; + final Action onTerminate; + final Action onAfterTerminate; + final Action onDispose; + + public CompletablePeek(CompletableSource source, Consumer onSubscribe, + Consumer onError, + Action onComplete, + Action onTerminate, + Action onAfterTerminate, + Action onDispose) { + this.source = source; + this.onSubscribe = onSubscribe; + this.onError = onError; + this.onComplete = onComplete; + this.onTerminate = onTerminate; + this.onAfterTerminate = onAfterTerminate; + this.onDispose = onDispose; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + + source.subscribe(new CompletableObserverImplementation(observer)); + } + + final class CompletableObserverImplementation implements CompletableObserver, Disposable { + + final CompletableObserver downstream; + + Disposable upstream; + + CompletableObserverImplementation(CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(final Disposable d) { + try { + onSubscribe.accept(d); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + d.dispose(); + this.upstream = DisposableHelper.DISPOSED; + EmptyDisposable.error(ex, downstream); + return; + } + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + if (upstream == DisposableHelper.DISPOSED) { + RxJavaPlugins.onError(e); + return; + } + try { + onError.accept(e); + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + downstream.onError(e); + + doAfter(); + } + + @Override + public void onComplete() { + if (upstream == DisposableHelper.DISPOSED) { + return; + } + + try { + onComplete.run(); + onTerminate.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + + downstream.onComplete(); + + doAfter(); + } + + void doAfter() { + try { + onAfterTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public void dispose() { + try { + onDispose.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java new file mode 100755 index 0000000..5111d46 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableResumeNext.java @@ -0,0 +1,102 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +public final class CompletableResumeNext extends Completable { + + final CompletableSource source; + + final Function errorMapper; + + public CompletableResumeNext(CompletableSource source, + Function errorMapper) { + this.source = source; + this.errorMapper = errorMapper; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + ResumeNextObserver parent = new ResumeNextObserver(observer, errorMapper); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class ResumeNextObserver + extends AtomicReference + implements CompletableObserver, Disposable { + + private static final long serialVersionUID = 5018523762564524046L; + + final CompletableObserver downstream; + + final Function errorMapper; + + boolean once; + + ResumeNextObserver(CompletableObserver observer, Function errorMapper) { + this.downstream = observer; + this.errorMapper = errorMapper; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + if (once) { + downstream.onError(e); + return; + } + once = true; + + CompletableSource c; + + try { + c = ObjectHelper.requireNonNull(errorMapper.apply(e), "The errorMapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(new CompositeException(e, ex)); + return; + } + + c.subscribe(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableSubscribeOn.java new file mode 100755 index 0000000..3b2b394 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableSubscribeOn.java @@ -0,0 +1,94 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; + +public final class CompletableSubscribeOn extends Completable { + final CompletableSource source; + + final Scheduler scheduler; + + public CompletableSubscribeOn(CompletableSource source, Scheduler scheduler) { + this.source = source; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + + final SubscribeOnObserver parent = new SubscribeOnObserver(observer, source); + observer.onSubscribe(parent); + + Disposable f = scheduler.scheduleDirect(parent); + + parent.task.replace(f); + + } + + static final class SubscribeOnObserver + extends AtomicReference + implements CompletableObserver, Disposable, Runnable { + + private static final long serialVersionUID = 7000911171163930287L; + + final CompletableObserver downstream; + + final SequentialDisposable task; + + final CompletableSource source; + + SubscribeOnObserver(CompletableObserver actual, CompletableSource source) { + this.downstream = actual; + this.source = source; + this.task = new SequentialDisposable(); + } + + @Override + public void run() { + source.subscribe(this); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + task.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableTakeUntilCompletable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableTakeUntilCompletable.java new file mode 100755 index 0000000..7a27b68 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableTakeUntilCompletable.java @@ -0,0 +1,144 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Terminates the sequence if either the main or the other Completable terminate. + *

History: 2.1.17 - experimental + * @since 2.2 + */ +public final class CompletableTakeUntilCompletable extends Completable { + + final Completable source; + + final CompletableSource other; + + public CompletableTakeUntilCompletable(Completable source, + CompletableSource other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + TakeUntilMainObserver parent = new TakeUntilMainObserver(observer); + observer.onSubscribe(parent); + + other.subscribe(parent.other); + source.subscribe(parent); + } + + static final class TakeUntilMainObserver extends AtomicReference + implements CompletableObserver, Disposable { + + private static final long serialVersionUID = 3533011714830024923L; + + final CompletableObserver downstream; + + final OtherObserver other; + + final AtomicBoolean once; + + TakeUntilMainObserver(CompletableObserver downstream) { + this.downstream = downstream; + this.other = new OtherObserver(this); + this.once = new AtomicBoolean(); + } + + @Override + public void dispose() { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(this); + DisposableHelper.dispose(other); + } + } + + @Override + public boolean isDisposed() { + return once.get(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onComplete() { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(other); + downstream.onComplete(); + } + } + + @Override + public void onError(Throwable e) { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(other); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + void innerComplete() { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(this); + downstream.onComplete(); + } + } + + void innerError(Throwable e) { + if (once.compareAndSet(false, true)) { + DisposableHelper.dispose(this); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + static final class OtherObserver extends AtomicReference + implements CompletableObserver { + + private static final long serialVersionUID = 5176264485428790318L; + final TakeUntilMainObserver parent; + + OtherObserver(TakeUntilMainObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java new file mode 100755 index 0000000..c11daa7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimeout.java @@ -0,0 +1,137 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + +public final class CompletableTimeout extends Completable { + + final CompletableSource source; + final long timeout; + final TimeUnit unit; + final Scheduler scheduler; + final CompletableSource other; + + public CompletableTimeout(CompletableSource source, long timeout, + TimeUnit unit, Scheduler scheduler, CompletableSource other) { + this.source = source; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.other = other; + } + + @Override + public void subscribeActual(final CompletableObserver observer) { + final CompositeDisposable set = new CompositeDisposable(); + observer.onSubscribe(set); + + final AtomicBoolean once = new AtomicBoolean(); + + Disposable timer = scheduler.scheduleDirect(new DisposeTask(once, set, observer), timeout, unit); + + set.add(timer); + + source.subscribe(new TimeOutObserver(set, once, observer)); + } + + static final class TimeOutObserver implements CompletableObserver { + + private final CompositeDisposable set; + private final AtomicBoolean once; + private final CompletableObserver downstream; + + TimeOutObserver(CompositeDisposable set, AtomicBoolean once, CompletableObserver observer) { + this.set = set; + this.once = once; + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + if (once.compareAndSet(false, true)) { + set.dispose(); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (once.compareAndSet(false, true)) { + set.dispose(); + downstream.onComplete(); + } + } + + } + + final class DisposeTask implements Runnable { + private final AtomicBoolean once; + final CompositeDisposable set; + final CompletableObserver downstream; + + DisposeTask(AtomicBoolean once, CompositeDisposable set, CompletableObserver observer) { + this.once = once; + this.set = set; + this.downstream = observer; + } + + @Override + public void run() { + if (once.compareAndSet(false, true)) { + set.clear(); + if (other == null) { + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); + } else { + other.subscribe(new DisposeObserver()); + } + } + } + + final class DisposeObserver implements CompletableObserver { + + @Override + public void onSubscribe(Disposable d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + set.dispose(); + downstream.onError(e); + } + + @Override + public void onComplete() { + set.dispose(); + downstream.onComplete(); + } + + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableTimer.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimer.java new file mode 100755 index 0000000..4f1eae4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableTimer.java @@ -0,0 +1,73 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Signals an {@code onComplete} event after the specified delay. + */ +public final class CompletableTimer extends Completable { + + final long delay; + final TimeUnit unit; + final Scheduler scheduler; + + public CompletableTimer(long delay, TimeUnit unit, Scheduler scheduler) { + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(final CompletableObserver observer) { + TimerDisposable parent = new TimerDisposable(observer); + observer.onSubscribe(parent); + parent.setFuture(scheduler.scheduleDirect(parent, delay, unit)); + } + + static final class TimerDisposable extends AtomicReference implements Disposable, Runnable { + + private static final long serialVersionUID = 3167244060586201109L; + final CompletableObserver downstream; + + TimerDisposable(final CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public void run() { + downstream.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + void setFuture(Disposable d) { + DisposableHelper.replace(this, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableToFlowable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableToFlowable.java new file mode 100755 index 0000000..d8820a5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableToFlowable.java @@ -0,0 +1,34 @@ +/** + * 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.internal.operators.completable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.internal.observers.SubscriberCompletableObserver; + +public final class CompletableToFlowable extends Flowable { + + final CompletableSource source; + + public CompletableToFlowable(CompletableSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(Subscriber s) { + SubscriberCompletableObserver os = new SubscriberCompletableObserver(s); + source.subscribe(os); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableToObservable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableToObservable.java new file mode 100755 index 0000000..f55a310 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableToObservable.java @@ -0,0 +1,98 @@ +/** + * 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.internal.operators.completable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.observers.BasicQueueDisposable; + +/** + * Wraps a Completable and exposes it as an Observable. + * + * @param the value type + */ +public final class CompletableToObservable extends Observable { + + final CompletableSource source; + + public CompletableToObservable(CompletableSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new ObserverCompletableObserver(observer)); + } + + static final class ObserverCompletableObserver extends BasicQueueDisposable + implements CompletableObserver { + + final Observer observer; + + Disposable upstream; + + ObserverCompletableObserver(Observer observer) { + this.observer = observer; + } + + @Override + public void onComplete() { + observer.onComplete(); + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + observer.onSubscribe(this); + } + } + + @Override + public int requestFusion(int mode) { + return mode & ASYNC; + } + + @Override + public Void poll() throws Exception { + return null; // always empty + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + // always empty + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableToSingle.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableToSingle.java new file mode 100755 index 0000000..e0e5559 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableToSingle.java @@ -0,0 +1,83 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; + +public final class CompletableToSingle extends Single { + final CompletableSource source; + + final Callable completionValueSupplier; + + final T completionValue; + + public CompletableToSingle(CompletableSource source, + Callable completionValueSupplier, T completionValue) { + this.source = source; + this.completionValue = completionValue; + this.completionValueSupplier = completionValueSupplier; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + source.subscribe(new ToSingle(observer)); + } + + final class ToSingle implements CompletableObserver { + + private final SingleObserver observer; + + ToSingle(SingleObserver observer) { + this.observer = observer; + } + + @Override + public void onComplete() { + T v; + + if (completionValueSupplier != null) { + try { + v = completionValueSupplier.call(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + observer.onError(e); + return; + } + } else { + v = completionValue; + } + + if (v == null) { + observer.onError(new NullPointerException("The value supplied is null")); + } else { + observer.onSuccess(v); + } + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onSubscribe(Disposable d) { + observer.onSubscribe(d); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java new file mode 100755 index 0000000..1f107f2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableUsing.java @@ -0,0 +1,194 @@ +/** + * 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.internal.operators.completable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class CompletableUsing extends Completable { + + final Callable resourceSupplier; + final Function completableFunction; + final Consumer disposer; + final boolean eager; + + public CompletableUsing(Callable resourceSupplier, + Function completableFunction, Consumer disposer, + boolean eager) { + this.resourceSupplier = resourceSupplier; + this.completableFunction = completableFunction; + this.disposer = disposer; + this.eager = eager; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + R resource; + + try { + resource = resourceSupplier.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + CompletableSource source; + + try { + source = ObjectHelper.requireNonNull(completableFunction.apply(resource), "The completableFunction returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (eager) { + try { + disposer.accept(resource); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + EmptyDisposable.error(new CompositeException(ex, exc), observer); + return; + } + } + + EmptyDisposable.error(ex, observer); + + if (!eager) { + try { + disposer.accept(resource); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + RxJavaPlugins.onError(exc); + } + } + return; + } + + source.subscribe(new UsingObserver(observer, resource, disposer, eager)); + } + + static final class UsingObserver + extends AtomicReference + implements CompletableObserver, Disposable { + + private static final long serialVersionUID = -674404550052917487L; + + final CompletableObserver downstream; + + final Consumer disposer; + + final boolean eager; + + Disposable upstream; + + UsingObserver(CompletableObserver actual, R resource, Consumer disposer, boolean eager) { + super(resource); + this.downstream = actual; + this.disposer = disposer; + this.eager = eager; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + disposeResourceAfter(); + } + + @SuppressWarnings("unchecked") + void disposeResourceAfter() { + Object resource = getAndSet(this); + if (resource != this) { + try { + disposer.accept((R)resource); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + if (eager) { + Object resource = getAndSet(this); + if (resource != this) { + try { + disposer.accept((R)resource); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + } else { + return; + } + } + + downstream.onError(e); + + if (!eager) { + disposeResourceAfter(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + if (eager) { + Object resource = getAndSet(this); + if (resource != this) { + try { + disposer.accept((R)resource); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + } else { + return; + } + } + + downstream.onComplete(); + + if (!eager) { + disposeResourceAfter(); + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/AbstractFlowableWithUpstream.java b/src/main/java/io/reactivex/internal/operators/flowable/AbstractFlowableWithUpstream.java new file mode 100755 index 0000000..2e67141 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/AbstractFlowableWithUpstream.java @@ -0,0 +1,49 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Publisher; + +import io.reactivex.Flowable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.HasUpstreamPublisher; + +/** + * Abstract base class for operators that take an upstream + * source {@link Publisher}. + * + * @param the upstream value type + * @param the output value type + */ +abstract class AbstractFlowableWithUpstream extends Flowable implements HasUpstreamPublisher { + + /** + * The upstream source Publisher. + */ + protected final Flowable source; + + /** + * Constructs a FlowableSource wrapping the given non-null (verified) + * source Publisher. + * @param source the source (upstream) Publisher instance, not null (verified) + */ + AbstractFlowableWithUpstream(Flowable source) { + this.source = ObjectHelper.requireNonNull(source, "source is null"); + } + + @Override + public final Publisher source() { + return source; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java new file mode 100755 index 0000000..d09af33 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java @@ -0,0 +1,193 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.*; + +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; + +public final class BlockingFlowableIterable implements Iterable { + final Flowable source; + + final int bufferSize; + + public BlockingFlowableIterable(Flowable source, int bufferSize) { + this.source = source; + this.bufferSize = bufferSize; + } + + @Override + public Iterator iterator() { + BlockingFlowableIterator it = new BlockingFlowableIterator(bufferSize); + source.subscribe(it); + return it; + } + + static final class BlockingFlowableIterator + extends AtomicReference + implements FlowableSubscriber, Iterator, Runnable, Disposable { + + private static final long serialVersionUID = 6695226475494099826L; + + final SpscArrayQueue queue; + + final long batchSize; + + final long limit; + + final Lock lock; + + final Condition condition; + + long produced; + + volatile boolean done; + volatile Throwable error; + + BlockingFlowableIterator(int batchSize) { + this.queue = new SpscArrayQueue(batchSize); + this.batchSize = batchSize; + this.limit = batchSize - (batchSize >> 2); + this.lock = new ReentrantLock(); + this.condition = lock.newCondition(); + } + + @Override + public boolean hasNext() { + for (;;) { + if (isDisposed()) { + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + return false; + } + boolean d = done; + boolean empty = queue.isEmpty(); + if (d) { + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } else + if (empty) { + return false; + } + } + if (empty) { + BlockingHelper.verifyNonBlocking(); + lock.lock(); + try { + while (!done && queue.isEmpty() && !isDisposed()) { + condition.await(); + } + } catch (InterruptedException ex) { + run(); + throw ExceptionHelper.wrapOrThrow(ex); + } finally { + lock.unlock(); + } + } else { + return true; + } + } + } + + @Override + public T next() { + if (hasNext()) { + T v = queue.poll(); + + long p = produced + 1; + if (p == limit) { + produced = 0; + get().request(p); + } else { + produced = p; + } + + return v; + } + throw new NoSuchElementException(); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, batchSize); + } + + @Override + public void onNext(T t) { + if (!queue.offer(t)) { + SubscriptionHelper.cancel(this); + + onError(new MissingBackpressureException("Queue full?!")); + } else { + signalConsumer(); + } + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + signalConsumer(); + } + + @Override + public void onComplete() { + done = true; + signalConsumer(); + } + + void signalConsumer() { + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); + } + } + + @Override + public void run() { + SubscriptionHelper.cancel(this); + signalConsumer(); + } + + @Override // otherwise default method which isn't available in Java 7 + public void remove() { + throw new UnsupportedOperationException("remove"); + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(this); + signalConsumer(); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatest.java new file mode 100755 index 0000000..55ef6c4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatest.java @@ -0,0 +1,118 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.DisposableSubscriber; + +/** + * Wait for and iterate over the latest values of the source observable. If the source works faster than the + * iterator, values may be skipped, but not the {@code onError} or {@code onComplete} events. + * @param the value type emitted + */ +public final class BlockingFlowableLatest implements Iterable { + + final Publisher source; + + public BlockingFlowableLatest(Publisher source) { + this.source = source; + } + + @Override + public Iterator iterator() { + LatestSubscriberIterator lio = new LatestSubscriberIterator(); + Flowable.fromPublisher(source).materialize().subscribe(lio); + return lio; + } + + /** Subscriber of source, iterator for output. */ + static final class LatestSubscriberIterator extends DisposableSubscriber> implements Iterator { + final Semaphore notify = new Semaphore(0); + // observer's notification + final AtomicReference> value = new AtomicReference>(); + + // iterator's notification + Notification iteratorNotification; + + @Override + public void onNext(Notification args) { + boolean wasNotAvailable = value.getAndSet(args) == null; + if (wasNotAvailable) { + notify.release(); + } + } + + @Override + public void onError(Throwable e) { + RxJavaPlugins.onError(e); + } + + @Override + public void onComplete() { + // not expected + } + + @Override + public boolean hasNext() { + if (iteratorNotification != null && iteratorNotification.isOnError()) { + throw ExceptionHelper.wrapOrThrow(iteratorNotification.getError()); + } + if (iteratorNotification == null || iteratorNotification.isOnNext()) { + if (iteratorNotification == null) { + try { + BlockingHelper.verifyNonBlocking(); + notify.acquire(); + } catch (InterruptedException ex) { + dispose(); + iteratorNotification = Notification.createOnError(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + + Notification n = value.getAndSet(null); + iteratorNotification = n; + if (n.isOnError()) { + throw ExceptionHelper.wrapOrThrow(n.getError()); + } + } + } + return iteratorNotification.isOnNext(); + } + + @Override + public T next() { + if (hasNext()) { + if (iteratorNotification.isOnNext()) { + T v = iteratorNotification.getValue(); + iteratorNotification = null; + return v; + } + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Read-only iterator."); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java new file mode 100755 index 0000000..235d850 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java @@ -0,0 +1,119 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; + +import io.reactivex.Flowable; +import io.reactivex.internal.util.*; +import io.reactivex.subscribers.DefaultSubscriber; + +/** + * Returns an Iterable that always returns the item most recently emitted by an Observable, or a + * seed value if no item has yet been emitted. + *

+ * + * + * @param the value type + */ +public final class BlockingFlowableMostRecent implements Iterable { + + final Flowable source; + + final T initialValue; + + public BlockingFlowableMostRecent(Flowable source, T initialValue) { + this.source = source; + this.initialValue = initialValue; + } + + @Override + public Iterator iterator() { + MostRecentSubscriber mostRecentSubscriber = new MostRecentSubscriber(initialValue); + + source.subscribe(mostRecentSubscriber); + + return mostRecentSubscriber.getIterable(); + } + + static final class MostRecentSubscriber extends DefaultSubscriber { + volatile Object value; + + MostRecentSubscriber(T value) { + this.value = NotificationLite.next(value); + } + + @Override + public void onComplete() { + value = NotificationLite.complete(); + } + + @Override + public void onError(Throwable e) { + value = NotificationLite.error(e); + } + + @Override + public void onNext(T args) { + value = NotificationLite.next(args); + } + + /** + * The {@link Iterator} return is not thread safe. In other words don't call {@link Iterator#hasNext()} in one + * thread expect {@link Iterator#next()} called from a different thread to work. + * @return the Iterator + */ + public Iterator getIterable() { + return new Iterator(); + } + + final class Iterator implements java.util.Iterator { + /** + * buffer to make sure that the state of the iterator doesn't change between calling hasNext() and next(). + */ + private Object buf; + + @Override + public boolean hasNext() { + buf = value; + return !NotificationLite.isComplete(buf); + } + + @Override + public T next() { + try { + // if hasNext wasn't called before calling next. + if (buf == null) { + buf = value; + } + if (NotificationLite.isComplete(buf)) { + throw new NoSuchElementException(); + } + if (NotificationLite.isError(buf)) { + throw ExceptionHelper.wrapOrThrow(NotificationLite.getError(buf)); + } + return NotificationLite.getValue(buf); + } + finally { + buf = null; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Read only iterator"); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java new file mode 100755 index 0000000..8cd4028 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableNext.java @@ -0,0 +1,175 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.DisposableSubscriber; + +/** + * Returns an Iterable that blocks until the Observable emits another item, then returns that item. + *

+ * + * + * @param the value type + */ +public final class BlockingFlowableNext implements Iterable { + + final Publisher source; + + public BlockingFlowableNext(Publisher source) { + this.source = source; + } + + @Override + public Iterator iterator() { + NextSubscriber nextSubscriber = new NextSubscriber(); + return new NextIterator(source, nextSubscriber); + } + + // test needs to access the observer.waiting flag + static final class NextIterator implements Iterator { + + private final NextSubscriber subscriber; + private final Publisher items; + private T next; + private boolean hasNext = true; + private boolean isNextConsumed = true; + private Throwable error; + private boolean started; + + NextIterator(Publisher items, NextSubscriber subscriber) { + this.items = items; + this.subscriber = subscriber; + } + + @Override + public boolean hasNext() { + if (error != null) { + // If any error has already been thrown, throw it again. + throw ExceptionHelper.wrapOrThrow(error); + } + // Since an iterator should not be used in different thread, + // so we do not need any synchronization. + if (!hasNext) { + // the iterator has reached the end. + return false; + } + // next has not been used yet. + return !isNextConsumed || moveToNext(); + } + + private boolean moveToNext() { + try { + if (!started) { + started = true; + // if not started, start now + subscriber.setWaiting(); + Flowable.fromPublisher(items) + .materialize().subscribe(subscriber); + } + + Notification nextNotification = subscriber.takeNext(); + if (nextNotification.isOnNext()) { + isNextConsumed = false; + next = nextNotification.getValue(); + return true; + } + // If an observable is completed or fails, + // hasNext() always return false. + hasNext = false; + if (nextNotification.isOnComplete()) { + return false; + } + if (nextNotification.isOnError()) { + error = nextNotification.getError(); + throw ExceptionHelper.wrapOrThrow(error); + } + throw new IllegalStateException("Should not reach here"); + } catch (InterruptedException e) { + subscriber.dispose(); + error = e; + throw ExceptionHelper.wrapOrThrow(e); + } + } + + @Override + public T next() { + if (error != null) { + // If any error has already been thrown, throw it again. + throw ExceptionHelper.wrapOrThrow(error); + } + if (hasNext()) { + isNextConsumed = true; + return next; + } + else { + throw new NoSuchElementException("No more elements"); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Read only iterator"); + } + } + + static final class NextSubscriber extends DisposableSubscriber> { + private final BlockingQueue> buf = new ArrayBlockingQueue>(1); + final AtomicInteger waiting = new AtomicInteger(); + + @Override + public void onComplete() { + // ignore + } + + @Override + public void onError(Throwable e) { + RxJavaPlugins.onError(e); + } + + @Override + public void onNext(Notification args) { + + if (waiting.getAndSet(0) == 1 || !args.isOnNext()) { + Notification toOffer = args; + while (!buf.offer(toOffer)) { + Notification concurrentItem = buf.poll(); + + // in case if we won race condition with onComplete/onError method + if (concurrentItem != null && !concurrentItem.isOnNext()) { + toOffer = concurrentItem; + } + } + } + + } + + public Notification takeNext() throws InterruptedException { + setWaiting(); + BlockingHelper.verifyNonBlocking(); + return buf.take(); + } + void setWaiting() { + waiting.set(1); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java new file mode 100755 index 0000000..608089a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAll.java @@ -0,0 +1,107 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableAll extends AbstractFlowableWithUpstream { + + final Predicate predicate; + + public FlowableAll(Flowable source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new AllSubscriber(s, predicate)); + } + + static final class AllSubscriber extends DeferredScalarSubscription implements FlowableSubscriber { + + private static final long serialVersionUID = -3521127104134758517L; + final Predicate predicate; + + Subscription upstream; + + boolean done; + + AllSubscriber(Subscriber actual, Predicate predicate) { + super(actual); + this.predicate = predicate; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + onError(e); + return; + } + if (!b) { + done = true; + upstream.cancel(); + complete(false); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + complete(true); + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java new file mode 100755 index 0000000..1ec3e94 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAllSingle.java @@ -0,0 +1,126 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableAllSingle extends Single implements FuseToFlowable { + + final Flowable source; + + final Predicate predicate; + + public FlowableAllSingle(Flowable source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new AllSubscriber(observer, predicate)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableAll(source, predicate)); + } + + static final class AllSubscriber implements FlowableSubscriber, Disposable { + + final SingleObserver downstream; + + final Predicate predicate; + + Subscription upstream; + + boolean done; + + AllSubscriber(SingleObserver actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + onError(e); + return; + } + if (!b) { + done = true; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(false); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + + downstream.onSuccess(true); + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAmb.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAmb.java new file mode 100755 index 0000000..203c62f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAmb.java @@ -0,0 +1,226 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableAmb extends Flowable { + final Publisher[] sources; + final Iterable> sourcesIterable; + + public FlowableAmb(Publisher[] sources, Iterable> sourcesIterable) { + this.sources = sources; + this.sourcesIterable = sourcesIterable; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribeActual(Subscriber s) { + Publisher[] sources = this.sources; + int count = 0; + if (sources == null) { + sources = new Publisher[8]; + try { + for (Publisher p : sourcesIterable) { + if (p == null) { + EmptySubscription.error(new NullPointerException("One of the sources is null"), s); + return; + } + if (count == sources.length) { + Publisher[] b = new Publisher[count + (count >> 2)]; + System.arraycopy(sources, 0, b, 0, count); + sources = b; + } + sources[count++] = p; + } + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + } else { + count = sources.length; + } + + if (count == 0) { + EmptySubscription.complete(s); + return; + } else + if (count == 1) { + sources[0].subscribe(s); + return; + } + + AmbCoordinator ac = new AmbCoordinator(s, count); + ac.subscribe(sources); + } + + static final class AmbCoordinator implements Subscription { + final Subscriber downstream; + final AmbInnerSubscriber[] subscribers; + + final AtomicInteger winner = new AtomicInteger(); + + @SuppressWarnings("unchecked") + AmbCoordinator(Subscriber actual, int count) { + this.downstream = actual; + this.subscribers = new AmbInnerSubscriber[count]; + } + + public void subscribe(Publisher[] sources) { + AmbInnerSubscriber[] as = subscribers; + int len = as.length; + for (int i = 0; i < len; i++) { + as[i] = new AmbInnerSubscriber(this, i + 1, downstream); + } + winner.lazySet(0); // release the contents of 'as' + downstream.onSubscribe(this); + + for (int i = 0; i < len; i++) { + if (winner.get() != 0) { + return; + } + + sources[i].subscribe(as[i]); + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + int w = winner.get(); + if (w > 0) { + subscribers[w - 1].request(n); + } else + if (w == 0) { + for (AmbInnerSubscriber a : subscribers) { + a.request(n); + } + } + } + } + + public boolean win(int index) { + int w = winner.get(); + if (w == 0) { + if (winner.compareAndSet(0, index)) { + AmbInnerSubscriber[] a = subscribers; + int n = a.length; + for (int i = 0; i < n; i++) { + if (i + 1 != index) { + a[i].cancel(); + } + } + return true; + } + } + return false; + } + + @Override + public void cancel() { + if (winner.get() != -1) { + winner.lazySet(-1); + + for (AmbInnerSubscriber a : subscribers) { + a.cancel(); + } + } + } + } + + static final class AmbInnerSubscriber extends AtomicReference implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -1185974347409665484L; + final AmbCoordinator parent; + final int index; + final Subscriber downstream; + + boolean won; + + final AtomicLong missedRequested = new AtomicLong(); + + AmbInnerSubscriber(AmbCoordinator parent, int index, Subscriber downstream) { + this.parent = parent; + this.index = index; + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(this, missedRequested, s); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(this, missedRequested, n); + } + + @Override + public void onNext(T t) { + if (won) { + downstream.onNext(t); + } else { + if (parent.win(index)) { + won = true; + downstream.onNext(t); + } else { + get().cancel(); + } + } + } + + @Override + public void onError(Throwable t) { + if (won) { + downstream.onError(t); + } else { + if (parent.win(index)) { + won = true; + downstream.onError(t); + } else { + get().cancel(); + RxJavaPlugins.onError(t); + } + } + } + + @Override + public void onComplete() { + if (won) { + downstream.onComplete(); + } else { + if (parent.win(index)) { + won = true; + downstream.onComplete(); + } else { + get().cancel(); + } + } + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(this); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java new file mode 100755 index 0000000..aed5010 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAny.java @@ -0,0 +1,105 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableAny extends AbstractFlowableWithUpstream { + final Predicate predicate; + public FlowableAny(Flowable source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new AnySubscriber(s, predicate)); + } + + static final class AnySubscriber extends DeferredScalarSubscription implements FlowableSubscriber { + + private static final long serialVersionUID = -2311252482644620661L; + + final Predicate predicate; + + Subscription upstream; + + boolean done; + + AnySubscriber(Subscriber actual, Predicate predicate) { + super(actual); + this.predicate = predicate; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + onError(e); + return; + } + if (b) { + done = true; + upstream.cancel(); + complete(true); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + complete(false); + } + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java new file mode 100755 index 0000000..0c30c11 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAnySingle.java @@ -0,0 +1,124 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableAnySingle extends Single implements FuseToFlowable { + final Flowable source; + + final Predicate predicate; + + public FlowableAnySingle(Flowable source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new AnySubscriber(observer, predicate)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableAny(source, predicate)); + } + + static final class AnySubscriber implements FlowableSubscriber, Disposable { + + final SingleObserver downstream; + + final Predicate predicate; + + Subscription upstream; + + boolean done; + + AnySubscriber(SingleObserver actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + onError(e); + return; + } + if (b) { + done = true; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(true); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + + done = true; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(false); + } + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableAutoConnect.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAutoConnect.java new file mode 100755 index 0000000..619d551 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableAutoConnect.java @@ -0,0 +1,53 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.disposables.Disposable; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.Consumer; + +/** + * Wraps a {@link ConnectableFlowable} and calls its {@code connect()} method once + * the specified number of {@link Subscriber}s have subscribed. + * + * @param the value type of the chain + */ +public final class FlowableAutoConnect extends Flowable { + final ConnectableFlowable source; + final int numberOfSubscribers; + final Consumer connection; + final AtomicInteger clients; + + public FlowableAutoConnect(ConnectableFlowable source, + int numberOfSubscribers, + Consumer connection) { + this.source = source; + this.numberOfSubscribers = numberOfSubscribers; + this.connection = connection; + this.clients = new AtomicInteger(); + } + + @Override + public void subscribeActual(Subscriber child) { + source.subscribe(child); + if (clients.incrementAndGet() == numberOfSubscribers) { + source.connect(connection); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java new file mode 100755 index 0000000..c5ac688 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBlockingSubscribe.java @@ -0,0 +1,130 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.*; + +import org.reactivestreams.*; + +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.subscribers.*; +import io.reactivex.internal.util.*; + +/** + * Utility methods to consume a Publisher in a blocking manner with callbacks or Subscriber. + */ +public final class FlowableBlockingSubscribe { + + /** Utility class. */ + private FlowableBlockingSubscribe() { + throw new IllegalStateException("No instances!"); + } + + /** + * Subscribes to the source and calls the Subscriber methods on the current thread. + *

+ * @param o the source publisher + * The cancellation and backpressure is composed through. + * @param subscriber the subscriber to forward events and calls to in the current thread + * @param the value type + */ + public static void subscribe(Publisher o, Subscriber subscriber) { + final BlockingQueue queue = new LinkedBlockingQueue(); + + BlockingSubscriber bs = new BlockingSubscriber(queue); + + o.subscribe(bs); + + try { + for (;;) { + if (bs.isCancelled()) { + break; + } + Object v = queue.poll(); + if (v == null) { + if (bs.isCancelled()) { + break; + } + BlockingHelper.verifyNonBlocking(); + v = queue.take(); + } + if (bs.isCancelled()) { + break; + } + if (v == BlockingSubscriber.TERMINATED + || NotificationLite.acceptFull(v, subscriber)) { + break; + } + } + } catch (InterruptedException e) { + bs.cancel(); + subscriber.onError(e); + } + } + + /** + * Runs the source observable to a terminal event, ignoring any values and rethrowing any exception. + * @param o the source publisher + * @param the value type + */ + public static void subscribe(Publisher o) { + BlockingIgnoringReceiver callback = new BlockingIgnoringReceiver(); + LambdaSubscriber ls = new LambdaSubscriber(Functions.emptyConsumer(), + callback, callback, Functions.REQUEST_MAX); + + o.subscribe(ls); + + BlockingHelper.awaitForComplete(callback, ls); + Throwable e = callback.error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + } + + /** + * Subscribes to the source and calls the given actions on the current thread. + * @param o the source publisher + * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param onComplete the callback action for the completion event. + * @param the value type + */ + public static void subscribe(Publisher o, final Consumer onNext, + final Consumer onError, final Action onComplete) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + subscribe(o, new LambdaSubscriber(onNext, onError, onComplete, Functions.REQUEST_MAX)); + } + + /** + * Subscribes to the source and calls the given actions on the current thread. + * @param o the source publisher + * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param onComplete the callback action for the completion event. + * @param bufferSize the number of elements to prefetch from the source Publisher + * @param the value type + */ + public static void subscribe(Publisher o, final Consumer onNext, + final Consumer onError, final Action onComplete, int bufferSize) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + ObjectHelper.verifyPositive(bufferSize, "number > 0 required"); + subscribe(o, new BoundedSubscriber(onNext, onError, onComplete, Functions.boundedConsumer(bufferSize), + bufferSize)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java new file mode 100755 index 0000000..f2f940a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBuffer.java @@ -0,0 +1,443 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BooleanSupplier; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableBuffer> extends AbstractFlowableWithUpstream { + final int size; + + final int skip; + + final Callable bufferSupplier; + + public FlowableBuffer(Flowable source, int size, int skip, Callable bufferSupplier) { + super(source); + this.size = size; + this.skip = skip; + this.bufferSupplier = bufferSupplier; + } + + @Override + public void subscribeActual(Subscriber s) { + if (size == skip) { + source.subscribe(new PublisherBufferExactSubscriber(s, size, bufferSupplier)); + } else if (skip > size) { + source.subscribe(new PublisherBufferSkipSubscriber(s, size, skip, bufferSupplier)); + } else { + source.subscribe(new PublisherBufferOverlappingSubscriber(s, size, skip, bufferSupplier)); + } + } + + static final class PublisherBufferExactSubscriber> + implements FlowableSubscriber, Subscription { + + final Subscriber downstream; + + final Callable bufferSupplier; + + final int size; + + C buffer; + + Subscription upstream; + + boolean done; + + int index; + + PublisherBufferExactSubscriber(Subscriber actual, int size, Callable bufferSupplier) { + this.downstream = actual; + this.size = size; + this.bufferSupplier = bufferSupplier; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + upstream.request(BackpressureHelper.multiplyCap(n, size)); + } + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + C b = buffer; + if (b == null) { + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null buffer"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + onError(e); + return; + } + + buffer = b; + } + + b.add(t); + + int i = index + 1; + if (i == size) { + index = 0; + buffer = null; + downstream.onNext(b); + } else { + index = i; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + C b = buffer; + + if (b != null && !b.isEmpty()) { + downstream.onNext(b); + } + downstream.onComplete(); + } + } + + static final class PublisherBufferSkipSubscriber> + extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -5616169793639412593L; + + final Subscriber downstream; + + final Callable bufferSupplier; + + final int size; + + final int skip; + + C buffer; + + Subscription upstream; + + boolean done; + + int index; + + PublisherBufferSkipSubscriber(Subscriber actual, int size, int skip, + Callable bufferSupplier) { + this.downstream = actual; + this.size = size; + this.skip = skip; + this.bufferSupplier = bufferSupplier; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + if (get() == 0 && compareAndSet(0, 1)) { + // n full buffers + long u = BackpressureHelper.multiplyCap(n, size); + // + (n - 1) gaps + long v = BackpressureHelper.multiplyCap(skip - size, n - 1); + + upstream.request(BackpressureHelper.addCap(u, v)); + } else { + // n full buffer + gap + upstream.request(BackpressureHelper.multiplyCap(skip, n)); + } + } + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + C b = buffer; + + int i = index; + + if (i++ == 0) { + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null buffer"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + + onError(e); + return; + } + + buffer = b; + } + + if (b != null) { + b.add(t); + if (b.size() == size) { + buffer = null; + downstream.onNext(b); + } + } + + if (i == skip) { + i = 0; + } + index = i; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + + done = true; + buffer = null; + + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + + done = true; + C b = buffer; + buffer = null; + + if (b != null) { + downstream.onNext(b); + } + + downstream.onComplete(); + } + } + + static final class PublisherBufferOverlappingSubscriber> + extends AtomicLong + implements FlowableSubscriber, Subscription, BooleanSupplier { + + private static final long serialVersionUID = -7370244972039324525L; + + final Subscriber downstream; + + final Callable bufferSupplier; + + final int size; + + final int skip; + + final ArrayDeque buffers; + + final AtomicBoolean once; + + Subscription upstream; + + boolean done; + + int index; + + volatile boolean cancelled; + + long produced; + + PublisherBufferOverlappingSubscriber(Subscriber actual, int size, int skip, + Callable bufferSupplier) { + this.downstream = actual; + this.size = size; + this.skip = skip; + this.bufferSupplier = bufferSupplier; + this.once = new AtomicBoolean(); + this.buffers = new ArrayDeque(); + } + + @Override + public boolean getAsBoolean() { + return cancelled; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + if (QueueDrainHelper.postCompleteRequest(n, downstream, buffers, this, this)) { + return; + } + + if (!once.get() && once.compareAndSet(false, true)) { + // (n - 1) skips + long u = BackpressureHelper.multiplyCap(skip, n - 1); + + // + 1 full buffer + long r = BackpressureHelper.addCap(size, u); + upstream.request(r); + } else { + // n skips + long r = BackpressureHelper.multiplyCap(skip, n); + upstream.request(r); + } + } + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + ArrayDeque bs = buffers; + + int i = index; + + if (i++ == 0) { + C b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null buffer"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + onError(e); + return; + } + + bs.offer(b); + } + + C b = bs.peek(); + + if (b != null && b.size() + 1 == size) { + bs.poll(); + + b.add(t); + + produced++; + + downstream.onNext(b); + } + + for (C b0 : bs) { + b0.add(t); + } + + if (i == skip) { + i = 0; + } + index = i; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + + done = true; + buffers.clear(); + + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + + done = true; + + long p = produced; + if (p != 0L) { + BackpressureHelper.produced(this, p); + } + QueueDrainHelper.postComplete(downstream, buffers, this, this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java new file mode 100755 index 0000000..44146a5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundary.java @@ -0,0 +1,420 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableBufferBoundary, Open, Close> +extends AbstractFlowableWithUpstream { + final Callable bufferSupplier; + final Publisher bufferOpen; + final Function> bufferClose; + + public FlowableBufferBoundary(Flowable source, Publisher bufferOpen, + Function> bufferClose, Callable bufferSupplier) { + super(source); + this.bufferOpen = bufferOpen; + this.bufferClose = bufferClose; + this.bufferSupplier = bufferSupplier; + } + + @Override + protected void subscribeActual(Subscriber s) { + BufferBoundarySubscriber parent = + new BufferBoundarySubscriber( + s, bufferOpen, bufferClose, bufferSupplier + ); + s.onSubscribe(parent); + source.subscribe(parent); + } + + static final class BufferBoundarySubscriber, Open, Close> + extends AtomicInteger implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -8466418554264089604L; + + final Subscriber downstream; + + final Callable bufferSupplier; + + final Publisher bufferOpen; + + final Function> bufferClose; + + final CompositeDisposable subscribers; + + final AtomicLong requested; + + final AtomicReference upstream; + + final AtomicThrowable errors; + + volatile boolean done; + + final SpscLinkedArrayQueue queue; + + volatile boolean cancelled; + + long index; + + Map buffers; + + long emitted; + + BufferBoundarySubscriber(Subscriber actual, + Publisher bufferOpen, + Function> bufferClose, + Callable bufferSupplier + ) { + this.downstream = actual; + this.bufferSupplier = bufferSupplier; + this.bufferOpen = bufferOpen; + this.bufferClose = bufferClose; + this.queue = new SpscLinkedArrayQueue(bufferSize()); + this.subscribers = new CompositeDisposable(); + this.requested = new AtomicLong(); + this.upstream = new AtomicReference(); + this.buffers = new LinkedHashMap(); + this.errors = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { + + BufferOpenSubscriber open = new BufferOpenSubscriber(this); + subscribers.add(open); + + bufferOpen.subscribe(open); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + for (C b : bufs.values()) { + b.add(t); + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + subscribers.dispose(); + synchronized (this) { + buffers = null; + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + subscribers.dispose(); + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + for (C b : bufs.values()) { + queue.offer(b); + } + buffers = null; + } + done = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + if (SubscriptionHelper.cancel(upstream)) { + cancelled = true; + subscribers.dispose(); + synchronized (this) { + buffers = null; + } + if (getAndIncrement() != 0) { + queue.clear(); + } + } + } + + void open(Open token) { + Publisher p; + C buf; + try { + buf = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null Collection"); + p = ObjectHelper.requireNonNull(bufferClose.apply(token), "The bufferClose returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + SubscriptionHelper.cancel(upstream); + onError(ex); + return; + } + + long idx = index; + index = idx + 1; + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + bufs.put(idx, buf); + } + + BufferCloseSubscriber bc = new BufferCloseSubscriber(this, idx); + subscribers.add(bc); + p.subscribe(bc); + } + + void openComplete(BufferOpenSubscriber os) { + subscribers.delete(os); + if (subscribers.size() == 0) { + SubscriptionHelper.cancel(upstream); + done = true; + drain(); + } + } + + void close(BufferCloseSubscriber closer, long idx) { + subscribers.delete(closer); + boolean makeDone = false; + if (subscribers.size() == 0) { + makeDone = true; + SubscriptionHelper.cancel(upstream); + } + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + queue.offer(buffers.remove(idx)); + } + if (makeDone) { + done = true; + } + drain(); + } + + void boundaryError(Disposable subscriber, Throwable ex) { + SubscriptionHelper.cancel(upstream); + subscribers.delete(subscriber); + onError(ex); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + long e = emitted; + Subscriber a = downstream; + SpscLinkedArrayQueue q = queue; + + for (;;) { + long r = requested.get(); + + while (e != r) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + if (d && errors.get() != null) { + q.clear(); + Throwable ex = errors.terminate(); + a.onError(ex); + return; + } + + C v = q.poll(); + boolean empty = v == null; + + if (d && empty) { + a.onComplete(); + return; + } + + if (empty) { + break; + } + + a.onNext(v); + e++; + } + + if (e == r) { + if (cancelled) { + q.clear(); + return; + } + + if (done) { + if (errors.get() != null) { + q.clear(); + Throwable ex = errors.terminate(); + a.onError(ex); + return; + } else if (q.isEmpty()) { + a.onComplete(); + return; + } + } + } + + emitted = e; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class BufferOpenSubscriber + extends AtomicReference + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = -8498650778633225126L; + + final BufferBoundarySubscriber parent; + + BufferOpenSubscriber(BufferBoundarySubscriber parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Open t) { + parent.open(t); + } + + @Override + public void onError(Throwable t) { + lazySet(SubscriptionHelper.CANCELLED); + parent.boundaryError(this, t); + } + + @Override + public void onComplete() { + lazySet(SubscriptionHelper.CANCELLED); + parent.openComplete(this); + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; + } + } + } + + static final class BufferCloseSubscriber> + extends AtomicReference + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = -8498650778633225126L; + + final BufferBoundarySubscriber parent; + + final long index; + + BufferCloseSubscriber(BufferBoundarySubscriber parent, long index) { + this.parent = parent; + this.index = index; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + s.cancel(); + parent.close(this, index); + } + } + + @Override + public void onError(Throwable t) { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + parent.boundaryError(this, t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + parent.close(this, index); + } + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundarySupplier.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundarySupplier.java new file mode 100755 index 0000000..8c544d9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferBoundarySupplier.java @@ -0,0 +1,272 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Collection; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.subscribers.QueueDrainSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.QueueDrainHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.*; + +public final class FlowableBufferBoundarySupplier, B> +extends AbstractFlowableWithUpstream { + final Callable> boundarySupplier; + final Callable bufferSupplier; + + public FlowableBufferBoundarySupplier(Flowable source, Callable> boundarySupplier, Callable bufferSupplier) { + super(source); + this.boundarySupplier = boundarySupplier; + this.bufferSupplier = bufferSupplier; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new BufferBoundarySupplierSubscriber(new SerializedSubscriber(s), bufferSupplier, boundarySupplier)); + } + + static final class BufferBoundarySupplierSubscriber, B> + extends QueueDrainSubscriber implements FlowableSubscriber, Subscription, Disposable { + + final Callable bufferSupplier; + final Callable> boundarySupplier; + + Subscription upstream; + + final AtomicReference other = new AtomicReference(); + + U buffer; + + BufferBoundarySupplierSubscriber(Subscriber actual, Callable bufferSupplier, + Callable> boundarySupplier) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.boundarySupplier = boundarySupplier; + } + + @Override + public void onSubscribe(Subscription s) { + if (!SubscriptionHelper.validate(this.upstream, s)) { + return; + } + this.upstream = s; + + Subscriber actual = this.downstream; + + U b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancelled = true; + s.cancel(); + EmptySubscription.error(e, actual); + return; + } + + buffer = b; + + Publisher boundary; + + try { + boundary = ObjectHelper.requireNonNull(boundarySupplier.call(), "The boundary publisher supplied is null"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelled = true; + s.cancel(); + EmptySubscription.error(ex, actual); + return; + } + + BufferBoundarySubscriber bs = new BufferBoundarySubscriber(this); + other.set(bs); + + actual.onSubscribe(this); + + if (!cancelled) { + s.request(Long.MAX_VALUE); + + boundary.subscribe(bs); + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + U b = buffer; + if (b == null) { + return; + } + b.add(t); + } + } + + @Override + public void onError(Throwable t) { + cancel(); + downstream.onError(t); + } + + @Override + public void onComplete() { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = null; + } + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainMaxLoop(queue, downstream, false, this, this); + } + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + upstream.cancel(); + disposeOther(); + + if (enter()) { + queue.clear(); + } + } + } + + void disposeOther() { + DisposableHelper.dispose(other); + } + + void next() { + + U next; + + try { + next = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + downstream.onError(e); + return; + } + + Publisher boundary; + + try { + boundary = ObjectHelper.requireNonNull(boundarySupplier.call(), "The boundary publisher supplied is null"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelled = true; + upstream.cancel(); + downstream.onError(ex); + return; + } + + BufferBoundarySubscriber bs = new BufferBoundarySubscriber(this); + + if (DisposableHelper.replace(other, bs)) { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = next; + } + + boundary.subscribe(bs); + + fastPathEmitMax(b, false, this); + } + } + + @Override + public void dispose() { + upstream.cancel(); + disposeOther(); + } + + @Override + public boolean isDisposed() { + return other.get() == DisposableHelper.DISPOSED; + } + + @Override + public boolean accept(Subscriber a, U v) { + downstream.onNext(v); + return true; + } + + } + + static final class BufferBoundarySubscriber, B> extends DisposableSubscriber { + final BufferBoundarySupplierSubscriber parent; + + boolean once; + + BufferBoundarySubscriber(BufferBoundarySupplierSubscriber parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + if (once) { + return; + } + once = true; + cancel(); + parent.next(); + } + + @Override + public void onError(Throwable t) { + if (once) { + RxJavaPlugins.onError(t); + return; + } + once = true; + parent.onError(t); + } + + @Override + public void onComplete() { + if (once) { + return; + } + once = true; + parent.next(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferExactBoundary.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferExactBoundary.java new file mode 100755 index 0000000..82215ae --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferExactBoundary.java @@ -0,0 +1,216 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.subscribers.QueueDrainSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.QueueDrainHelper; +import io.reactivex.subscribers.*; + +public final class FlowableBufferExactBoundary, B> +extends AbstractFlowableWithUpstream { + final Publisher boundary; + final Callable bufferSupplier; + + public FlowableBufferExactBoundary(Flowable source, Publisher boundary, Callable bufferSupplier) { + super(source); + this.boundary = boundary; + this.bufferSupplier = bufferSupplier; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new BufferExactBoundarySubscriber(new SerializedSubscriber(s), bufferSupplier, boundary)); + } + + static final class BufferExactBoundarySubscriber, B> + extends QueueDrainSubscriber implements FlowableSubscriber, Subscription, Disposable { + + final Callable bufferSupplier; + final Publisher boundary; + + Subscription upstream; + + Disposable other; + + U buffer; + + BufferExactBoundarySubscriber(Subscriber actual, Callable bufferSupplier, + Publisher boundary) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.boundary = boundary; + } + + @Override + public void onSubscribe(Subscription s) { + if (!SubscriptionHelper.validate(this.upstream, s)) { + return; + } + this.upstream = s; + + U b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancelled = true; + s.cancel(); + EmptySubscription.error(e, downstream); + return; + } + + buffer = b; + + BufferBoundarySubscriber bs = new BufferBoundarySubscriber(this); + other = bs; + + downstream.onSubscribe(this); + + if (!cancelled) { + s.request(Long.MAX_VALUE); + + boundary.subscribe(bs); + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + U b = buffer; + if (b == null) { + return; + } + b.add(t); + } + } + + @Override + public void onError(Throwable t) { + cancel(); + downstream.onError(t); + } + + @Override + public void onComplete() { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = null; + } + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainMaxLoop(queue, downstream, false, this, this); + } + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + other.dispose(); + upstream.cancel(); + + if (enter()) { + queue.clear(); + } + } + } + + void next() { + + U next; + + try { + next = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + downstream.onError(e); + return; + } + + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = next; + } + + fastPathEmitMax(b, false, this); + } + + @Override + public void dispose() { + cancel(); + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public boolean accept(Subscriber a, U v) { + downstream.onNext(v); + return true; + } + + } + + static final class BufferBoundarySubscriber, B> extends DisposableSubscriber { + final BufferExactBoundarySubscriber parent; + + BufferBoundarySubscriber(BufferExactBoundarySubscriber parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + parent.next(); + } + + @Override + public void onError(Throwable t) { + parent.onError(t); + } + + @Override + public void onComplete() { + parent.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java new file mode 100755 index 0000000..73130f7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java @@ -0,0 +1,573 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.subscribers.QueueDrainSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.QueueDrainHelper; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableBufferTimed> extends AbstractFlowableWithUpstream { + + final long timespan; + final long timeskip; + final TimeUnit unit; + final Scheduler scheduler; + final Callable bufferSupplier; + final int maxSize; + final boolean restartTimerOnMaxSize; + + public FlowableBufferTimed(Flowable source, long timespan, long timeskip, TimeUnit unit, Scheduler scheduler, Callable bufferSupplier, int maxSize, + boolean restartTimerOnMaxSize) { + super(source); + this.timespan = timespan; + this.timeskip = timeskip; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSupplier = bufferSupplier; + this.maxSize = maxSize; + this.restartTimerOnMaxSize = restartTimerOnMaxSize; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (timespan == timeskip && maxSize == Integer.MAX_VALUE) { + source.subscribe(new BufferExactUnboundedSubscriber( + new SerializedSubscriber(s), + bufferSupplier, timespan, unit, scheduler)); + return; + } + Worker w = scheduler.createWorker(); + + if (timespan == timeskip) { + source.subscribe(new BufferExactBoundedSubscriber( + new SerializedSubscriber(s), + bufferSupplier, + timespan, unit, maxSize, restartTimerOnMaxSize, w + )); + return; + } + // Can't use maxSize because what to do if a buffer is full but its + // timespan hasn't been elapsed? + source.subscribe(new BufferSkipBoundedSubscriber( + new SerializedSubscriber(s), + bufferSupplier, timespan, timeskip, unit, w)); + } + + static final class BufferExactUnboundedSubscriber> + extends QueueDrainSubscriber implements Subscription, Runnable, Disposable { + final Callable bufferSupplier; + final long timespan; + final TimeUnit unit; + final Scheduler scheduler; + + Subscription upstream; + + U buffer; + + final AtomicReference timer = new AtomicReference(); + + BufferExactUnboundedSubscriber( + Subscriber actual, Callable bufferSupplier, + long timespan, TimeUnit unit, Scheduler scheduler) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.timespan = timespan; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + U b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The supplied buffer is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + EmptySubscription.error(e, downstream); + return; + } + + buffer = b; + + downstream.onSubscribe(this); + + if (!cancelled) { + s.request(Long.MAX_VALUE); + + Disposable d = scheduler.schedulePeriodicallyDirect(this, timespan, timespan, unit); + if (!timer.compareAndSet(null, d)) { + d.dispose(); + } + } + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + U b = buffer; + if (b != null) { + b.add(t); + } + } + } + + @Override + public void onError(Throwable t) { + DisposableHelper.dispose(timer); + synchronized (this) { + buffer = null; + } + downstream.onError(t); + } + + @Override + public void onComplete() { + DisposableHelper.dispose(timer); + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = null; + } + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainMaxLoop(queue, downstream, false, null, this); + } + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + DisposableHelper.dispose(timer); + } + + @Override + public void run() { + U next; + + try { + next = ObjectHelper.requireNonNull(bufferSupplier.call(), "The supplied buffer is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + downstream.onError(e); + return; + } + + U current; + + synchronized (this) { + current = buffer; + if (current == null) { + return; + } + buffer = next; + } + + fastPathEmitMax(current, false, this); + } + + @Override + public boolean accept(Subscriber a, U v) { + downstream.onNext(v); + return true; + } + + @Override + public void dispose() { + cancel(); + } + + @Override + public boolean isDisposed() { + return timer.get() == DisposableHelper.DISPOSED; + } + } + + static final class BufferSkipBoundedSubscriber> + extends QueueDrainSubscriber implements Subscription, Runnable { + final Callable bufferSupplier; + final long timespan; + final long timeskip; + final TimeUnit unit; + final Worker w; + final List buffers; + + Subscription upstream; + + BufferSkipBoundedSubscriber(Subscriber actual, + Callable bufferSupplier, long timespan, + long timeskip, TimeUnit unit, Worker w) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.timespan = timespan; + this.timeskip = timeskip; + this.unit = unit; + this.w = w; + this.buffers = new LinkedList(); + } + + @Override + public void onSubscribe(Subscription s) { + if (!SubscriptionHelper.validate(this.upstream, s)) { + return; + } + this.upstream = s; + + final U b; // NOPMD + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The supplied buffer is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + w.dispose(); + s.cancel(); + EmptySubscription.error(e, downstream); + return; + } + + buffers.add(b); + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + + w.schedulePeriodically(this, timeskip, timeskip, unit); + + w.schedule(new RemoveFromBuffer(b), timespan, unit); + } + + @Override + public void onNext(T t) { + synchronized (this) { + for (U b : buffers) { + b.add(t); + } + } + } + + @Override + public void onError(Throwable t) { + done = true; + w.dispose(); + clear(); + downstream.onError(t); + } + + @Override + public void onComplete() { + List bs; + synchronized (this) { + bs = new ArrayList(buffers); + buffers.clear(); + } + + for (U b : bs) { + queue.offer(b); + } + done = true; + if (enter()) { + QueueDrainHelper.drainMaxLoop(queue, downstream, false, w, this); + } + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + w.dispose(); + clear(); + } + + void clear() { + synchronized (this) { + buffers.clear(); + } + } + + @Override + public void run() { + if (cancelled) { + return; + } + final U b; // NOPMD + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The supplied buffer is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + downstream.onError(e); + return; + } + + synchronized (this) { + if (cancelled) { + return; + } + buffers.add(b); + } + + w.schedule(new RemoveFromBuffer(b), timespan, unit); + } + + @Override + public boolean accept(Subscriber a, U v) { + a.onNext(v); + return true; + } + + final class RemoveFromBuffer implements Runnable { + private final U buffer; + + RemoveFromBuffer(U buffer) { + this.buffer = buffer; + } + + @Override + public void run() { + synchronized (BufferSkipBoundedSubscriber.this) { + buffers.remove(buffer); + } + + fastPathOrderedEmitMax(buffer, false, w); + } + } + } + + static final class BufferExactBoundedSubscriber> + extends QueueDrainSubscriber implements Subscription, Runnable, Disposable { + final Callable bufferSupplier; + final long timespan; + final TimeUnit unit; + final int maxSize; + final boolean restartTimerOnMaxSize; + final Worker w; + + U buffer; + + Disposable timer; + + Subscription upstream; + + long producerIndex; + + long consumerIndex; + + BufferExactBoundedSubscriber( + Subscriber actual, + Callable bufferSupplier, + long timespan, TimeUnit unit, int maxSize, + boolean restartOnMaxSize, Worker w) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.timespan = timespan; + this.unit = unit; + this.maxSize = maxSize; + this.restartTimerOnMaxSize = restartOnMaxSize; + this.w = w; + } + + @Override + public void onSubscribe(Subscription s) { + if (!SubscriptionHelper.validate(this.upstream, s)) { + return; + } + this.upstream = s; + + U b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The supplied buffer is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + w.dispose(); + s.cancel(); + EmptySubscription.error(e, downstream); + return; + } + + buffer = b; + + downstream.onSubscribe(this); + + timer = w.schedulePeriodically(this, timespan, timespan, unit); + + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + + b.add(t); + + if (b.size() < maxSize) { + return; + } + + buffer = null; + producerIndex++; + } + + if (restartTimerOnMaxSize) { + timer.dispose(); + } + + fastPathOrderedEmitMax(b, false, this); + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The supplied buffer is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + downstream.onError(e); + return; + } + + synchronized (this) { + buffer = b; + consumerIndex++; + } + if (restartTimerOnMaxSize) { + timer = w.schedulePeriodically(this, timespan, timespan, unit); + } + } + + @Override + public void onError(Throwable t) { + synchronized (this) { + buffer = null; + } + downstream.onError(t); + w.dispose(); + } + + @Override + public void onComplete() { + U b; + synchronized (this) { + b = buffer; + buffer = null; + } + + if (b != null) { + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainMaxLoop(queue, downstream, false, this, this); + } + w.dispose(); + } + } + + @Override + public boolean accept(Subscriber a, U v) { + a.onNext(v); + return true; + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + dispose(); + } + } + + @Override + public void dispose() { + synchronized (this) { + buffer = null; + } + upstream.cancel(); + w.dispose(); + } + + @Override + public boolean isDisposed() { + return w.isDisposed(); + } + + @Override + public void run() { + U next; + + try { + next = ObjectHelper.requireNonNull(bufferSupplier.call(), "The supplied buffer is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + downstream.onError(e); + return; + } + + U current; + + synchronized (this) { + current = buffer; + if (current == null || producerIndex != consumerIndex) { + return; + } + buffer = next; + } + + fastPathOrderedEmitMax(current, false, this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java new file mode 100755 index 0000000..830b098 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCache.java @@ -0,0 +1,417 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * An observable which auto-connects to another observable, caches the elements + * from that observable but allows terminating the connection and completing the cache. + * + * @param the source element type + */ +public final class FlowableCache extends AbstractFlowableWithUpstream +implements FlowableSubscriber { + + /** + * The subscription to the source should happen at most once. + */ + final AtomicBoolean once; + + /** + * The number of items per cached nodes. + */ + final int capacityHint; + + /** + * The current known array of subscriber state to notify. + */ + final AtomicReference[]> subscribers; + + /** + * A shared instance of an empty array of subscribers to avoid creating + * a new empty array when all subscribers cancel. + */ + @SuppressWarnings("rawtypes") + static final CacheSubscription[] EMPTY = new CacheSubscription[0]; + /** + * A shared instance indicating the source has no more events and there + * is no need to remember subscribers anymore. + */ + @SuppressWarnings("rawtypes") + static final CacheSubscription[] TERMINATED = new CacheSubscription[0]; + + /** + * The total number of elements in the list available for reads. + */ + volatile long size; + + /** + * The starting point of the cached items. + */ + final Node head; + + /** + * The current tail of the linked structure holding the items. + */ + Node tail; + + /** + * How many items have been put into the tail node so far. + */ + int tailOffset; + + /** + * If {@link #subscribers} is {@link #TERMINATED}, this holds the terminal error if not null. + */ + Throwable error; + + /** + * True if the source has terminated. + */ + volatile boolean done; + + /** + * Constructs an empty, non-connected cache. + * @param source the source to subscribe to for the first incoming subscriber + * @param capacityHint the number of items expected (reduce allocation frequency) + */ + @SuppressWarnings("unchecked") + public FlowableCache(Flowable source, int capacityHint) { + super(source); + this.capacityHint = capacityHint; + this.once = new AtomicBoolean(); + Node n = new Node(capacityHint); + this.head = n; + this.tail = n; + this.subscribers = new AtomicReference[]>(EMPTY); + } + + @Override + protected void subscribeActual(Subscriber t) { + CacheSubscription consumer = new CacheSubscription(t, this); + t.onSubscribe(consumer); + add(consumer); + + if (!once.get() && once.compareAndSet(false, true)) { + source.subscribe(this); + } else { + replay(consumer); + } + } + + /** + * Check if this cached observable is connected to its source. + * @return true if already connected + */ + /* public */boolean isConnected() { + return once.get(); + } + + /** + * Returns true if there are observers subscribed to this observable. + * @return true if the cache has Subscribers + */ + /* public */ boolean hasSubscribers() { + return subscribers.get().length != 0; + } + + /** + * Returns the number of events currently cached. + * @return the number of currently cached event count + */ + /* public */ long cachedEventCount() { + return size; + } + + /** + * Atomically adds the consumer to the {@link #subscribers} copy-on-write array + * if the source has not yet terminated. + * @param consumer the consumer to add + */ + void add(CacheSubscription consumer) { + for (;;) { + CacheSubscription[] current = subscribers.get(); + if (current == TERMINATED) { + return; + } + int n = current.length; + + @SuppressWarnings("unchecked") + CacheSubscription[] next = new CacheSubscription[n + 1]; + System.arraycopy(current, 0, next, 0, n); + next[n] = consumer; + + if (subscribers.compareAndSet(current, next)) { + return; + } + } + } + + /** + * Atomically removes the consumer from the {@link #subscribers} copy-on-write array. + * @param consumer the consumer to remove + */ + @SuppressWarnings("unchecked") + void remove(CacheSubscription consumer) { + for (;;) { + CacheSubscription[] current = subscribers.get(); + int n = current.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (current[i] == consumer) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + CacheSubscription[] next; + + if (n == 1) { + next = EMPTY; + } else { + next = new CacheSubscription[n - 1]; + System.arraycopy(current, 0, next, 0, j); + System.arraycopy(current, j + 1, next, j, n - j - 1); + } + + if (subscribers.compareAndSet(current, next)) { + return; + } + } + } + + /** + * Replays the contents of this cache to the given consumer based on its + * current state and number of items requested by it. + * @param consumer the consumer to continue replaying items to + */ + void replay(CacheSubscription consumer) { + // make sure there is only one replay going on at a time + if (consumer.getAndIncrement() != 0) { + return; + } + + // see if there were more replay request in the meantime + int missed = 1; + // read out state into locals upfront to avoid being re-read due to volatile reads + long index = consumer.index; + int offset = consumer.offset; + Node node = consumer.node; + AtomicLong requested = consumer.requested; + Subscriber downstream = consumer.downstream; + int capacity = capacityHint; + + for (;;) { + // first see if the source has terminated, read order matters! + boolean sourceDone = done; + // and if the number of items is the same as this consumer has received + boolean empty = size == index; + + // if the source is done and we have all items so far, terminate the consumer + if (sourceDone && empty) { + // release the node object to avoid leaks through retained consumers + consumer.node = null; + // if error is not null then the source failed + Throwable ex = error; + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + // there are still items not sent to the consumer + if (!empty) { + // see how many items the consumer has requested in total so far + long consumerRequested = requested.get(); + // MIN_VALUE indicates a cancelled consumer, we stop replaying + if (consumerRequested == Long.MIN_VALUE) { + // release the node object to avoid leaks through retained consumers + consumer.node = null; + return; + } + // if the consumer has requested more and there is more, we will emit an item + if (consumerRequested != index) { + + // if the offset in the current node has reached the node capacity + if (offset == capacity) { + // switch to the subsequent node + node = node.next; + // reset the in-node offset + offset = 0; + } + + // emit the cached item + downstream.onNext(node.values[offset]); + + // move the node offset forward + offset++; + // move the total consumed item count forward + index++; + + // retry for the next item/terminal event if any + continue; + } + } + + // commit the changed references back + consumer.index = index; + consumer.offset = offset; + consumer.node = node; + // release the changes and see if there were more replay request in the meantime + missed = consumer.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + int tailOffset = this.tailOffset; + // if the current tail node is full, create a fresh node + if (tailOffset == capacityHint) { + Node n = new Node(tailOffset); + n.values[0] = t; + this.tailOffset = 1; + tail.next = n; + tail = n; + } else { + tail.values[tailOffset] = t; + this.tailOffset = tailOffset + 1; + } + size++; + for (CacheSubscription consumer : subscribers.get()) { + replay(consumer); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + for (CacheSubscription consumer : subscribers.getAndSet(TERMINATED)) { + replay(consumer); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + done = true; + for (CacheSubscription consumer : subscribers.getAndSet(TERMINATED)) { + replay(consumer); + } + } + + /** + * Hosts the downstream consumer and its current requested and replay states. + * {@code this} holds the work-in-progress counter for the serialized replay. + * @param the value type + */ + static final class CacheSubscription extends AtomicInteger + implements Subscription { + + private static final long serialVersionUID = 6770240836423125754L; + + final Subscriber downstream; + + final FlowableCache parent; + + final AtomicLong requested; + + Node node; + + int offset; + + long index; + + /** + * Constructs a new instance with the actual downstream consumer and + * the parent cache object. + * @param downstream the actual consumer + * @param parent the parent that holds onto the cached items + */ + CacheSubscription(Subscriber downstream, FlowableCache parent) { + this.downstream = downstream; + this.parent = parent; + this.node = parent.head; + this.requested = new AtomicLong(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.addCancel(requested, n); + parent.replay(this); + } + } + + @Override + public void cancel() { + if (requested.getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + } + } + } + + /** + * Represents a segment of the cached item list as + * part of a linked-node-list structure. + * @param the element type + */ + static final class Node { + + /** + * The array of values held by this node. + */ + final T[] values; + + /** + * The next node if not null. + */ + volatile Node next; + + @SuppressWarnings("unchecked") + Node(int capacityHint) { + this.values = (T[])new Object[capacityHint]; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollect.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollect.java new file mode 100755 index 0000000..ff85850 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollect.java @@ -0,0 +1,116 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiConsumer; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableCollect extends AbstractFlowableWithUpstream { + + final Callable initialSupplier; + final BiConsumer collector; + + public FlowableCollect(Flowable source, Callable initialSupplier, BiConsumer collector) { + super(source); + this.initialSupplier = initialSupplier; + this.collector = collector; + } + + @Override + protected void subscribeActual(Subscriber s) { + U u; + try { + u = ObjectHelper.requireNonNull(initialSupplier.call(), "The initial value supplied is null"); + } catch (Throwable e) { + EmptySubscription.error(e, s); + return; + } + + source.subscribe(new CollectSubscriber(s, u, collector)); + } + + static final class CollectSubscriber extends DeferredScalarSubscription implements FlowableSubscriber { + + private static final long serialVersionUID = -3589550218733891694L; + + final BiConsumer collector; + + final U u; + + Subscription upstream; + + boolean done; + + CollectSubscriber(Subscriber actual, U u, BiConsumer collector) { + super(actual); + this.collector = collector; + this.u = u; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + collector.accept(u, t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + onError(e); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + complete(u); + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollectSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollectSingle.java new file mode 100755 index 0000000..c5d3dc1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCollectSingle.java @@ -0,0 +1,133 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiConsumer; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableCollectSingle extends Single implements FuseToFlowable { + + final Flowable source; + + final Callable initialSupplier; + final BiConsumer collector; + + public FlowableCollectSingle(Flowable source, Callable initialSupplier, BiConsumer collector) { + this.source = source; + this.initialSupplier = initialSupplier; + this.collector = collector; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + U u; + try { + u = ObjectHelper.requireNonNull(initialSupplier.call(), "The initialSupplier returned a null value"); + } catch (Throwable e) { + EmptyDisposable.error(e, observer); + return; + } + + source.subscribe(new CollectSubscriber(observer, u, collector)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableCollect(source, initialSupplier, collector)); + } + + static final class CollectSubscriber implements FlowableSubscriber, Disposable { + + final SingleObserver downstream; + + final BiConsumer collector; + + final U u; + + Subscription upstream; + + boolean done; + + CollectSubscriber(SingleObserver actual, U u, BiConsumer collector) { + this.downstream = actual; + this.collector = collector; + this.u = u; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + collector.accept(u, t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + onError(e); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(u); + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java new file mode 100755 index 0000000..c690701 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCombineLatest.java @@ -0,0 +1,557 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Iterator; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.annotations.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.flowable.FlowableMap.MapSubscriber; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Combines the latest values from multiple sources through a function. + * + * @param the value type of the sources + * @param the result type + */ +public final class FlowableCombineLatest +extends Flowable { + + @Nullable + final Publisher[] array; + + @Nullable + final Iterable> iterable; + + final Function combiner; + + final int bufferSize; + + final boolean delayErrors; + + public FlowableCombineLatest(@NonNull Publisher[] array, + @NonNull Function combiner, + int bufferSize, boolean delayErrors) { + this.array = array; + this.iterable = null; + this.combiner = combiner; + this.bufferSize = bufferSize; + this.delayErrors = delayErrors; + } + + public FlowableCombineLatest(@NonNull Iterable> iterable, + @NonNull Function combiner, + int bufferSize, boolean delayErrors) { + this.array = null; + this.iterable = iterable; + this.combiner = combiner; + this.bufferSize = bufferSize; + this.delayErrors = delayErrors; + } + + @SuppressWarnings("unchecked") + @Override + public void subscribeActual(Subscriber s) { + Publisher[] a = array; + int n; + if (a == null) { + n = 0; + a = new Publisher[8]; + + Iterator> it; + + try { + it = ObjectHelper.requireNonNull(iterable.iterator(), "The iterator returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + + for (;;) { + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + + if (!b) { + break; + } + + Publisher p; + + try { + p = ObjectHelper.requireNonNull(it.next(), "The publisher returned by the iterator is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + + if (n == a.length) { + Publisher[] c = new Publisher[n + (n >> 2)]; + System.arraycopy(a, 0, c, 0, n); + a = c; + } + a[n++] = p; + } + + } else { + n = a.length; + } + + if (n == 0) { + EmptySubscription.complete(s); + return; + } + if (n == 1) { + ((Publisher)a[0]).subscribe(new MapSubscriber(s, new SingletonArrayFunc())); + return; + } + + CombineLatestCoordinator coordinator = + new CombineLatestCoordinator(s, combiner, n, bufferSize, delayErrors); + + s.onSubscribe(coordinator); + + coordinator.subscribe(a, n); + } + + static final class CombineLatestCoordinator + extends BasicIntQueueSubscription { + + private static final long serialVersionUID = -5082275438355852221L; + + final Subscriber downstream; + + final Function combiner; + + final CombineLatestInnerSubscriber[] subscribers; + + final SpscLinkedArrayQueue queue; + + final Object[] latest; + + final boolean delayErrors; + + boolean outputFused; + + int nonEmptySources; + + int completedSources; + + volatile boolean cancelled; + + final AtomicLong requested; + + volatile boolean done; + + final AtomicReference error; + + CombineLatestCoordinator(Subscriber actual, + Function combiner, int n, + int bufferSize, boolean delayErrors) { + this.downstream = actual; + this.combiner = combiner; + @SuppressWarnings("unchecked") + CombineLatestInnerSubscriber[] a = new CombineLatestInnerSubscriber[n]; + for (int i = 0; i < n; i++) { + a[i] = new CombineLatestInnerSubscriber(this, i, bufferSize); + } + this.subscribers = a; + this.latest = new Object[n]; + this.queue = new SpscLinkedArrayQueue(bufferSize); + this.requested = new AtomicLong(); + this.error = new AtomicReference(); + this.delayErrors = delayErrors; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + cancelled = true; + cancelAll(); + } + + void subscribe(Publisher[] sources, int n) { + CombineLatestInnerSubscriber[] a = subscribers; + + for (int i = 0; i < n; i++) { + if (done || cancelled) { + return; + } + sources[i].subscribe(a[i]); + } + } + + void innerValue(int index, T value) { + + boolean replenishInsteadOfDrain; + + synchronized (this) { + Object[] os = latest; + + int localNonEmptySources = nonEmptySources; + + if (os[index] == null) { + localNonEmptySources++; + nonEmptySources = localNonEmptySources; + } + + os[index] = value; + + if (os.length == localNonEmptySources) { + + queue.offer(subscribers[index], os.clone()); + + replenishInsteadOfDrain = false; + } else { + replenishInsteadOfDrain = true; + } + } + + if (replenishInsteadOfDrain) { + subscribers[index].requestOne(); + } else { + drain(); + } + } + + void innerComplete(int index) { + synchronized (this) { + Object[] os = latest; + + if (os[index] != null) { + int localCompletedSources = completedSources + 1; + + if (localCompletedSources == os.length) { + done = true; + } else { + completedSources = localCompletedSources; + return; + } + } else { + done = true; + } + } + drain(); + } + + void innerError(int index, Throwable e) { + + if (ExceptionHelper.addThrowable(error, e)) { + if (!delayErrors) { + cancelAll(); + done = true; + drain(); + } else { + innerComplete(index); + } + } else { + RxJavaPlugins.onError(e); + } + } + + void drainOutput() { + final Subscriber a = downstream; + final SpscLinkedArrayQueue q = queue; + + int missed = 1; + + for (;;) { + + if (cancelled) { + q.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + + a.onError(ex); + return; + } + + boolean d = done; + + boolean empty = q.isEmpty(); + + if (!empty) { + a.onNext(null); + } + + if (d && empty) { + a.onComplete(); + return; + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @SuppressWarnings("unchecked") + void drainAsync() { + final Subscriber a = downstream; + final SpscLinkedArrayQueue q = queue; + + int missed = 1; + + for (;;) { + + long r = requested.get(); + long e = 0L; + + while (e != r) { + boolean d = done; + + Object v = q.poll(); + + boolean empty = v == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + T[] va = (T[])q.poll(); + + R w; + + try { + w = ObjectHelper.requireNonNull(combiner.apply(va), "The combiner returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + cancelAll(); + ExceptionHelper.addThrowable(error, ex); + ex = ExceptionHelper.terminate(error); + + a.onError(ex); + return; + } + + a.onNext(w); + + ((CombineLatestInnerSubscriber)v).requestOne(); + + e++; + } + + if (e == r) { + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + } + + if (e != 0L && r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + if (outputFused) { + drainOutput(); + } else { + drainAsync(); + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, SpscLinkedArrayQueue q) { + if (cancelled) { + cancelAll(); + q.clear(); + return true; + } + + if (d) { + if (delayErrors) { + if (empty) { + cancelAll(); + Throwable e = ExceptionHelper.terminate(error); + + if (e != null && e != ExceptionHelper.TERMINATED) { + a.onError(e); + } else { + a.onComplete(); + } + return true; + } + } else { + Throwable e = ExceptionHelper.terminate(error); + + if (e != null && e != ExceptionHelper.TERMINATED) { + cancelAll(); + q.clear(); + a.onError(e); + return true; + } else + if (empty) { + cancelAll(); + + a.onComplete(); + return true; + } + } + } + return false; + } + + void cancelAll() { + for (CombineLatestInnerSubscriber inner : subscribers) { + inner.cancel(); + } + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & BOUNDARY) != 0) { + return NONE; + } + int m = requestedMode & ASYNC; + outputFused = m != 0; + return m; + } + + @Nullable + @SuppressWarnings("unchecked") + @Override + public R poll() throws Exception { + Object e = queue.poll(); + if (e == null) { + return null; + } + T[] a = (T[])queue.poll(); + R r = ObjectHelper.requireNonNull(combiner.apply(a), "The combiner returned a null value"); + ((CombineLatestInnerSubscriber)e).requestOne(); + return r; + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + } + + static final class CombineLatestInnerSubscriber + extends AtomicReference + implements FlowableSubscriber { + + private static final long serialVersionUID = -8730235182291002949L; + + final CombineLatestCoordinator parent; + + final int index; + + final int prefetch; + + final int limit; + + int produced; + + CombineLatestInnerSubscriber(CombineLatestCoordinator parent, int index, int prefetch) { + this.parent = parent; + this.index = index; + this.prefetch = prefetch; + this.limit = prefetch - (prefetch >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, prefetch); + } + + @Override + public void onNext(T t) { + parent.innerValue(index, t); + } + + @Override + public void onError(Throwable t) { + parent.innerError(index, t); + } + + @Override + public void onComplete() { + parent.innerComplete(index); + } + + public void cancel() { + SubscriptionHelper.cancel(this); + } + + public void requestOne() { + + int p = produced + 1; + if (p == limit) { + produced = 0; + get().request(p); + } else { + produced = p; + } + + } + } + + final class SingletonArrayFunc implements Function { + @Override + public R apply(T t) throws Exception { + return combiner.apply(new Object[] { t }); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java new file mode 100755 index 0000000..4d7cd06 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatArray.java @@ -0,0 +1,153 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.internal.subscriptions.SubscriptionArbiter; + +public final class FlowableConcatArray extends Flowable { + + final Publisher[] sources; + + final boolean delayError; + + public FlowableConcatArray(Publisher[] sources, boolean delayError) { + this.sources = sources; + this.delayError = delayError; + } + + @Override + protected void subscribeActual(Subscriber s) { + ConcatArraySubscriber parent = new ConcatArraySubscriber(sources, delayError, s); + s.onSubscribe(parent); + + parent.onComplete(); + } + + static final class ConcatArraySubscriber extends SubscriptionArbiter implements FlowableSubscriber { + + private static final long serialVersionUID = -8158322871608889516L; + + final Subscriber downstream; + + final Publisher[] sources; + + final boolean delayError; + + final AtomicInteger wip; + + int index; + + List errors; + + long produced; + + ConcatArraySubscriber(Publisher[] sources, boolean delayError, Subscriber downstream) { + super(false); + this.downstream = downstream; + this.sources = sources; + this.delayError = delayError; + this.wip = new AtomicInteger(); + } + + @Override + public void onSubscribe(Subscription s) { + setSubscription(s); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (delayError) { + List list = errors; + if (list == null) { + list = new ArrayList(sources.length - index + 1); + errors = list; + } + list.add(t); + onComplete(); + } else { + downstream.onError(t); + } + } + + @Override + public void onComplete() { + if (wip.getAndIncrement() == 0) { + Publisher[] sources = this.sources; + int n = sources.length; + int i = index; + for (;;) { + + if (i == n) { + List list = errors; + if (list != null) { + if (list.size() == 1) { + downstream.onError(list.get(0)); + } else { + downstream.onError(new CompositeException(list)); + } + } else { + downstream.onComplete(); + } + return; + } + + Publisher p = sources[i]; + + if (p == null) { + Throwable ex = new NullPointerException("A Publisher entry is null"); + if (delayError) { + List list = errors; + if (list == null) { + list = new ArrayList(n - i + 1); + errors = list; + } + list.add(ex); + i++; + continue; + } else { + downstream.onError(ex); + return; + } + } else { + long r = produced; + if (r != 0L) { + produced = 0L; + produced(r); + } + p.subscribe(this); + } + + index = ++i; + + if (wip.decrementAndGet() == 0) { + break; + } + } + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java new file mode 100755 index 0000000..64df7cc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java @@ -0,0 +1,617 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableConcatMap extends AbstractFlowableWithUpstream { + + final Function> mapper; + + final int prefetch; + + final ErrorMode errorMode; + + public FlowableConcatMap(Flowable source, + Function> mapper, + int prefetch, ErrorMode errorMode) { + super(source); + this.mapper = mapper; + this.prefetch = prefetch; + this.errorMode = errorMode; + } + + public static Subscriber subscribe(Subscriber s, Function> mapper, + int prefetch, ErrorMode errorMode) { + switch (errorMode) { + case BOUNDARY: + return new ConcatMapDelayed(s, mapper, prefetch, false); + case END: + return new ConcatMapDelayed(s, mapper, prefetch, true); + default: + return new ConcatMapImmediate(s, mapper, prefetch); + } + } + + @Override + protected void subscribeActual(Subscriber s) { + + if (FlowableScalarXMap.tryScalarXMapSubscribe(source, s, mapper)) { + return; + } + + source.subscribe(subscribe(s, mapper, prefetch, errorMode)); + } + + abstract static class BaseConcatMapSubscriber + extends AtomicInteger + implements FlowableSubscriber, ConcatMapSupport, Subscription { + + private static final long serialVersionUID = -3511336836796789179L; + + final ConcatMapInner inner; + + final Function> mapper; + + final int prefetch; + + final int limit; + + Subscription upstream; + + int consumed; + + SimpleQueue queue; + + volatile boolean done; + + volatile boolean cancelled; + + final AtomicThrowable errors; + + volatile boolean active; + + int sourceMode; + + BaseConcatMapSubscriber( + Function> mapper, + int prefetch) { + this.mapper = mapper; + this.prefetch = prefetch; + this.limit = prefetch - (prefetch >> 2); + this.inner = new ConcatMapInner(this); + this.errors = new AtomicThrowable(); + } + + @Override + public final void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") QueueSubscription f = (QueueSubscription)s; + int m = f.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = f; + done = true; + + subscribeActual(); + + drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = f; + + subscribeActual(); + + s.request(prefetch); + return; + } + } + + queue = new SpscArrayQueue(prefetch); + + subscribeActual(); + + s.request(prefetch); + } + } + + abstract void drain(); + + abstract void subscribeActual(); + + @Override + public final void onNext(T t) { + if (sourceMode != QueueSubscription.ASYNC) { + if (!queue.offer(t)) { + upstream.cancel(); + onError(new IllegalStateException("Queue full?!")); + return; + } + } + drain(); + } + + @Override + public final void onComplete() { + done = true; + drain(); + } + + @Override + public final void innerComplete() { + active = false; + drain(); + } + + } + + static final class ConcatMapImmediate + extends BaseConcatMapSubscriber { + + private static final long serialVersionUID = 7898995095634264146L; + + final Subscriber downstream; + + final AtomicInteger wip; + + ConcatMapImmediate(Subscriber actual, + Function> mapper, + int prefetch) { + super(mapper, prefetch); + this.downstream = actual; + this.wip = new AtomicInteger(); + } + + @Override + void subscribeActual() { + downstream.onSubscribe(this); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + inner.cancel(); + + if (getAndIncrement() == 0) { + downstream.onError(errors.terminate()); + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void innerNext(R value) { + if (get() == 0 && compareAndSet(0, 1)) { + downstream.onNext(value); + if (compareAndSet(1, 0)) { + return; + } + downstream.onError(errors.terminate()); + } + } + + @Override + public void innerError(Throwable e) { + if (errors.addThrowable(e)) { + upstream.cancel(); + + if (getAndIncrement() == 0) { + downstream.onError(errors.terminate()); + } + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void request(long n) { + inner.request(n); + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + inner.cancel(); + upstream.cancel(); + } + } + + @Override + void drain() { + if (wip.getAndIncrement() == 0) { + for (;;) { + if (cancelled) { + return; + } + + if (!active) { + boolean d = done; + + T v; + + try { + v = queue.poll(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + errors.addThrowable(e); + downstream.onError(errors.terminate()); + return; + } + + boolean empty = v == null; + + if (d && empty) { + downstream.onComplete(); + return; + } + + if (!empty) { + Publisher p; + + try { + p = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null Publisher"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + + upstream.cancel(); + errors.addThrowable(e); + downstream.onError(errors.terminate()); + return; + } + + if (sourceMode != QueueSubscription.SYNC) { + int c = consumed + 1; + if (c == limit) { + consumed = 0; + upstream.request(c); + } else { + consumed = c; + } + } + + if (p instanceof Callable) { + @SuppressWarnings("unchecked") + Callable callable = (Callable) p; + + R vr; + + try { + vr = callable.call(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + errors.addThrowable(e); + downstream.onError(errors.terminate()); + return; + } + + if (vr == null) { + continue; + } + + if (inner.isUnbounded()) { + if (get() == 0 && compareAndSet(0, 1)) { + downstream.onNext(vr); + if (!compareAndSet(1, 0)) { + downstream.onError(errors.terminate()); + return; + } + } + continue; + } else { + active = true; + inner.setSubscription(new SimpleScalarSubscription(vr, inner)); + } + + } else { + active = true; + p.subscribe(inner); + } + } + } + if (wip.decrementAndGet() == 0) { + break; + } + } + } + } + } + + static final class SimpleScalarSubscription + extends AtomicBoolean + implements Subscription { + final Subscriber downstream; + final T value; + + SimpleScalarSubscription(T value, Subscriber downstream) { + this.value = value; + this.downstream = downstream; + } + + @Override + public void request(long n) { + if (n > 0 && compareAndSet(false, true)) { + Subscriber a = downstream; + a.onNext(value); + a.onComplete(); + } + } + + @Override + public void cancel() { + + } + } + + static final class ConcatMapDelayed + extends BaseConcatMapSubscriber { + + private static final long serialVersionUID = -2945777694260521066L; + + final Subscriber downstream; + + final boolean veryEnd; + + ConcatMapDelayed(Subscriber actual, + Function> mapper, + int prefetch, boolean veryEnd) { + super(mapper, prefetch); + this.downstream = actual; + this.veryEnd = veryEnd; + } + + @Override + void subscribeActual() { + downstream.onSubscribe(this); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void innerNext(R value) { + downstream.onNext(value); + } + + @Override + public void innerError(Throwable e) { + if (errors.addThrowable(e)) { + if (!veryEnd) { + upstream.cancel(); + done = true; + } + active = false; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void request(long n) { + inner.request(n); + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + inner.cancel(); + upstream.cancel(); + } + } + + @Override + void drain() { + if (getAndIncrement() == 0) { + + for (;;) { + if (cancelled) { + return; + } + + if (!active) { + + boolean d = done; + + if (d && !veryEnd) { + Throwable ex = errors.get(); + if (ex != null) { + downstream.onError(errors.terminate()); + return; + } + } + + T v; + + try { + v = queue.poll(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + errors.addThrowable(e); + downstream.onError(errors.terminate()); + return; + } + + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (!empty) { + Publisher p; + + try { + p = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null Publisher"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + + upstream.cancel(); + errors.addThrowable(e); + downstream.onError(errors.terminate()); + return; + } + + if (sourceMode != QueueSubscription.SYNC) { + int c = consumed + 1; + if (c == limit) { + consumed = 0; + upstream.request(c); + } else { + consumed = c; + } + } + + if (p instanceof Callable) { + @SuppressWarnings("unchecked") + Callable supplier = (Callable) p; + + R vr; + + try { + vr = supplier.call(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + errors.addThrowable(e); + if (!veryEnd) { + upstream.cancel(); + downstream.onError(errors.terminate()); + return; + } + vr = null; + } + + if (vr == null) { + continue; + } + + if (inner.isUnbounded()) { + downstream.onNext(vr); + continue; + } else { + active = true; + inner.setSubscription(new SimpleScalarSubscription(vr, inner)); + } + } else { + active = true; + p.subscribe(inner); + } + } + } + if (decrementAndGet() == 0) { + break; + } + } + } + } + } + + interface ConcatMapSupport { + + void innerNext(T value); + + void innerComplete(); + + void innerError(Throwable e); + } + + static final class ConcatMapInner + extends SubscriptionArbiter + implements FlowableSubscriber { + + private static final long serialVersionUID = 897683679971470653L; + + final ConcatMapSupport parent; + + long produced; + + ConcatMapInner(ConcatMapSupport parent) { + super(false); + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + setSubscription(s); + } + + @Override + public void onNext(R t) { + produced++; + + parent.innerNext(t); + } + + @Override + public void onError(Throwable t) { + long p = produced; + + if (p != 0L) { + produced = 0L; + produced(p); + } + + parent.innerError(t); + } + + @Override + public void onComplete() { + long p = produced; + + if (p != 0L) { + produced = 0L; + produced(p); + } + + parent.innerComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java new file mode 100755 index 0000000..8acad8a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEager.java @@ -0,0 +1,380 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimpleQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscribers.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableConcatMapEager extends AbstractFlowableWithUpstream { + + final Function> mapper; + + final int maxConcurrency; + + final int prefetch; + + final ErrorMode errorMode; + + public FlowableConcatMapEager(Flowable source, + Function> mapper, + int maxConcurrency, + int prefetch, + ErrorMode errorMode) { + super(source); + this.mapper = mapper; + this.maxConcurrency = maxConcurrency; + this.prefetch = prefetch; + this.errorMode = errorMode; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ConcatMapEagerDelayErrorSubscriber( + s, mapper, maxConcurrency, prefetch, errorMode)); + } + + static final class ConcatMapEagerDelayErrorSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription, InnerQueuedSubscriberSupport { + + private static final long serialVersionUID = -4255299542215038287L; + + final Subscriber downstream; + + final Function> mapper; + + final int maxConcurrency; + + final int prefetch; + + final ErrorMode errorMode; + + final AtomicThrowable errors; + + final AtomicLong requested; + + final SpscLinkedArrayQueue> subscribers; + + Subscription upstream; + + volatile boolean cancelled; + + volatile boolean done; + + volatile InnerQueuedSubscriber current; + + ConcatMapEagerDelayErrorSubscriber(Subscriber actual, + Function> mapper, int maxConcurrency, int prefetch, + ErrorMode errorMode) { + this.downstream = actual; + this.mapper = mapper; + this.maxConcurrency = maxConcurrency; + this.prefetch = prefetch; + this.errorMode = errorMode; + this.subscribers = new SpscLinkedArrayQueue>(Math.min(prefetch, maxConcurrency)); + this.errors = new AtomicThrowable(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(maxConcurrency == Integer.MAX_VALUE ? Long.MAX_VALUE : maxConcurrency); + } + + } + + @Override + public void onNext(T t) { + Publisher p; + + try { + p = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + InnerQueuedSubscriber inner = new InnerQueuedSubscriber(this, prefetch); + + if (cancelled) { + return; + } + + subscribers.offer(inner); + + p.subscribe(inner); + + if (cancelled) { + inner.cancel(); + drainAndCancel(); + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + upstream.cancel(); + + drainAndCancel(); + } + + void drainAndCancel() { + if (getAndIncrement() == 0) { + do { + cancelAll(); + } while (decrementAndGet() != 0); + } + } + + void cancelAll() { + InnerQueuedSubscriber inner = current; + current = null; + + if (inner != null) { + inner.cancel(); + } + + while ((inner = subscribers.poll()) != null) { + inner.cancel(); + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void innerNext(InnerQueuedSubscriber inner, R value) { + if (inner.queue().offer(value)) { + drain(); + } else { + inner.cancel(); + innerError(inner, new MissingBackpressureException()); + } + } + + @Override + public void innerError(InnerQueuedSubscriber inner, Throwable e) { + if (errors.addThrowable(e)) { + inner.setDone(); + if (errorMode != ErrorMode.END) { + upstream.cancel(); + } + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void innerComplete(InnerQueuedSubscriber inner) { + inner.setDone(); + drain(); + } + + @Override + public void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + InnerQueuedSubscriber inner = current; + Subscriber a = downstream; + ErrorMode em = errorMode; + + for (;;) { + long r = requested.get(); + long e = 0L; + + if (inner == null) { + + if (em != ErrorMode.END) { + Throwable ex = errors.get(); + if (ex != null) { + cancelAll(); + + a.onError(errors.terminate()); + return; + } + } + + boolean outerDone = done; + + inner = subscribers.poll(); + + if (outerDone && inner == null) { + Throwable ex = errors.terminate(); + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + + if (inner != null) { + current = inner; + } + } + + boolean continueNextSource = false; + + if (inner != null) { + SimpleQueue q = inner.queue(); + if (q != null) { + while (e != r) { + if (cancelled) { + cancelAll(); + return; + } + + if (em == ErrorMode.IMMEDIATE) { + Throwable ex = errors.get(); + if (ex != null) { + current = null; + inner.cancel(); + cancelAll(); + + a.onError(errors.terminate()); + return; + } + } + + boolean d = inner.isDone(); + + R v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + current = null; + inner.cancel(); + cancelAll(); + a.onError(ex); + return; + } + + boolean empty = v == null; + + if (d && empty) { + inner = null; + current = null; + upstream.request(1); + continueNextSource = true; + break; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + + inner.requestOne(); + } + + if (e == r) { + if (cancelled) { + cancelAll(); + return; + } + + if (em == ErrorMode.IMMEDIATE) { + Throwable ex = errors.get(); + if (ex != null) { + current = null; + inner.cancel(); + cancelAll(); + + a.onError(errors.terminate()); + return; + } + } + + boolean d = inner.isDone(); + + boolean empty = q.isEmpty(); + + if (d && empty) { + inner = null; + current = null; + upstream.request(1); + continueNextSource = true; + } + } + } + } + + if (e != 0L && r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + + if (continueNextSource) { + continue; + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerPublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerPublisher.java new file mode 100755 index 0000000..43785a6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapEagerPublisher.java @@ -0,0 +1,59 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.functions.Function; +import io.reactivex.internal.operators.flowable.FlowableConcatMapEager.ConcatMapEagerDelayErrorSubscriber; +import io.reactivex.internal.util.ErrorMode; + +/** + * ConcatMapEager which works with an arbitrary Publisher source. + *

History: 2.0.7 - experimental + * @param the input value type + * @param the output type + * @since 2.1 + */ +public final class FlowableConcatMapEagerPublisher extends Flowable { + + final Publisher source; + + final Function> mapper; + + final int maxConcurrency; + + final int prefetch; + + final ErrorMode errorMode; + + public FlowableConcatMapEagerPublisher(Publisher source, + Function> mapper, + int maxConcurrency, + int prefetch, + ErrorMode errorMode) { + this.source = source; + this.mapper = mapper; + this.maxConcurrency = maxConcurrency; + this.prefetch = prefetch; + this.errorMode = errorMode; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ConcatMapEagerDelayErrorSubscriber( + s, mapper, maxConcurrency, prefetch, errorMode)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapPublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapPublisher.java new file mode 100755 index 0000000..48040fd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMapPublisher.java @@ -0,0 +1,49 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.functions.Function; +import io.reactivex.internal.util.ErrorMode; + +public final class FlowableConcatMapPublisher extends Flowable { + + final Publisher source; + + final Function> mapper; + + final int prefetch; + + final ErrorMode errorMode; + + public FlowableConcatMapPublisher(Publisher source, + Function> mapper, + int prefetch, ErrorMode errorMode) { + this.source = source; + this.mapper = mapper; + this.prefetch = prefetch; + this.errorMode = errorMode; + } + + @Override + protected void subscribeActual(Subscriber s) { + + if (FlowableScalarXMap.tryScalarXMapSubscribe(source, s, mapper)) { + return; + } + + source.subscribe(FlowableConcatMap.subscribe(s, mapper, prefetch, errorMode)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithCompletable.java new file mode 100755 index 0000000..8928d20 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithCompletable.java @@ -0,0 +1,112 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Subscribe to a main Flowable first, then when it completes normally, subscribe to a Completable + * and terminate when it terminates. + *

History: 2.1.10 - experimental + * @param the element type of the main source and output type + * @since 2.2 + */ +public final class FlowableConcatWithCompletable extends AbstractFlowableWithUpstream { + + final CompletableSource other; + + public FlowableConcatWithCompletable(Flowable source, CompletableSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ConcatWithSubscriber(s, other)); + } + + static final class ConcatWithSubscriber + extends AtomicReference + implements FlowableSubscriber, CompletableObserver, Subscription { + + private static final long serialVersionUID = -7346385463600070225L; + + final Subscriber downstream; + + Subscription upstream; + + CompletableSource other; + + boolean inCompletable; + + ConcatWithSubscriber(Subscriber actual, CompletableSource other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + if (inCompletable) { + downstream.onComplete(); + } else { + inCompletable = true; + upstream = SubscriptionHelper.CANCELLED; + CompletableSource cs = other; + other = null; + cs.subscribe(this); + } + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + DisposableHelper.dispose(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybe.java new file mode 100755 index 0000000..0081d91 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithMaybe.java @@ -0,0 +1,105 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscribers.SinglePostCompleteSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Subscribe to a main Flowable first, then when it completes normally, subscribe to a Maybe, + * signal its success value followed by a completion or signal its error or completion signal as is. + *

History: 2.1.10 - experimental + * @param the element type of the main source and output type + * @since 2.2 + */ +public final class FlowableConcatWithMaybe extends AbstractFlowableWithUpstream { + + final MaybeSource other; + + public FlowableConcatWithMaybe(Flowable source, MaybeSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ConcatWithSubscriber(s, other)); + } + + static final class ConcatWithSubscriber + extends SinglePostCompleteSubscriber + implements MaybeObserver { + + private static final long serialVersionUID = -7346385463600070225L; + + final AtomicReference otherDisposable; + + MaybeSource other; + + boolean inMaybe; + + ConcatWithSubscriber(Subscriber actual, MaybeSource other) { + super(actual); + this.other = other; + this.otherDisposable = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(otherDisposable, d); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onSuccess(T t) { + complete(t); + } + + @Override + public void onComplete() { + if (inMaybe) { + downstream.onComplete(); + } else { + inMaybe = true; + upstream = SubscriptionHelper.CANCELLED; + MaybeSource ms = other; + other = null; + ms.subscribe(this); + } + } + + @Override + public void cancel() { + super.cancel(); + DisposableHelper.dispose(otherDisposable); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithSingle.java new file mode 100755 index 0000000..23c60ea --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatWithSingle.java @@ -0,0 +1,98 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscribers.SinglePostCompleteSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Subscribe to a main Flowable first, then when it completes normally, subscribe to a Single, + * signal its success value followed by a completion or signal its error as is. + *

History: 2.1.10 - experimental + * @param the element type of the main source and output type + * @since 2.2 + */ +public final class FlowableConcatWithSingle extends AbstractFlowableWithUpstream { + + final SingleSource other; + + public FlowableConcatWithSingle(Flowable source, SingleSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ConcatWithSubscriber(s, other)); + } + + static final class ConcatWithSubscriber + extends SinglePostCompleteSubscriber + implements SingleObserver { + + private static final long serialVersionUID = -7346385463600070225L; + + final AtomicReference otherDisposable; + + SingleSource other; + + ConcatWithSubscriber(Subscriber actual, SingleSource other) { + super(actual); + this.other = other; + this.otherDisposable = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(otherDisposable, d); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onSuccess(T t) { + complete(t); + } + + @Override + public void onComplete() { + upstream = SubscriptionHelper.CANCELLED; + SingleSource ss = other; + other = null; + ss.subscribe(this); + } + + @Override + public void cancel() { + super.cancel(); + DisposableHelper.dispose(otherDisposable); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java new file mode 100755 index 0000000..b29e269 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCount.java @@ -0,0 +1,75 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.*; + +public final class FlowableCount extends AbstractFlowableWithUpstream { + + public FlowableCount(Flowable source) { + super(source); + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new CountSubscriber(s)); + } + + static final class CountSubscriber extends DeferredScalarSubscription + implements FlowableSubscriber { + + private static final long serialVersionUID = 4973004223787171406L; + + Subscription upstream; + + long count; + + CountSubscriber(Subscriber downstream) { + super(downstream); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Object t) { + count++; + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + complete(count); + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCountSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCountSingle.java new file mode 100755 index 0000000..c43f031 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCountSingle.java @@ -0,0 +1,91 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableCountSingle extends Single implements FuseToFlowable { + + final Flowable source; + + public FlowableCountSingle(Flowable source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new CountSubscriber(observer)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableCount(source)); + } + + static final class CountSubscriber implements FlowableSubscriber, Disposable { + + final SingleObserver downstream; + + Subscription upstream; + + long count; + + CountSubscriber(SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Object t) { + count++; + } + + @Override + public void onError(Throwable t) { + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(count); + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java new file mode 100755 index 0000000..caf6d31 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableCreate.java @@ -0,0 +1,725 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Cancellable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableCreate extends Flowable { + + final FlowableOnSubscribe source; + + final BackpressureStrategy backpressure; + + public FlowableCreate(FlowableOnSubscribe source, BackpressureStrategy backpressure) { + this.source = source; + this.backpressure = backpressure; + } + + @Override + public void subscribeActual(Subscriber t) { + BaseEmitter emitter; + + switch (backpressure) { + case MISSING: { + emitter = new MissingEmitter(t); + break; + } + case ERROR: { + emitter = new ErrorAsyncEmitter(t); + break; + } + case DROP: { + emitter = new DropAsyncEmitter(t); + break; + } + case LATEST: { + emitter = new LatestAsyncEmitter(t); + break; + } + default: { + emitter = new BufferAsyncEmitter(t, bufferSize()); + break; + } + } + + t.onSubscribe(emitter); + try { + source.subscribe(emitter); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + emitter.onError(ex); + } + } + + /** + * Serializes calls to onNext, onError and onComplete. + * + * @param the value type + */ + static final class SerializedEmitter + extends AtomicInteger + implements FlowableEmitter { + + private static final long serialVersionUID = 4883307006032401862L; + + final BaseEmitter emitter; + + final AtomicThrowable error; + + final SimplePlainQueue queue; + + volatile boolean done; + + SerializedEmitter(BaseEmitter emitter) { + this.emitter = emitter; + this.error = new AtomicThrowable(); + this.queue = new SpscLinkedArrayQueue(16); + } + + @Override + public void onNext(T t) { + if (emitter.isCancelled() || done) { + return; + } + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + if (get() == 0 && compareAndSet(0, 1)) { + emitter.onNext(t); + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue q = queue; + synchronized (q) { + q.offer(t); + } + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { + if (emitter.isCancelled() || done) { + return false; + } + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + if (error.addThrowable(t)) { + done = true; + drain(); + return true; + } + return false; + } + + @Override + public void onComplete() { + if (emitter.isCancelled() || done) { + return; + } + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + BaseEmitter e = emitter; + SimplePlainQueue q = queue; + AtomicThrowable error = this.error; + int missed = 1; + for (;;) { + + for (;;) { + if (e.isCancelled()) { + q.clear(); + return; + } + + if (error.get() != null) { + q.clear(); + e.onError(error.terminate()); + return; + } + + boolean d = done; + + T v = q.poll(); + + boolean empty = v == null; + + if (d && empty) { + e.onComplete(); + return; + } + + if (empty) { + break; + } + + e.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void setDisposable(Disposable d) { + emitter.setDisposable(d); + } + + @Override + public void setCancellable(Cancellable c) { + emitter.setCancellable(c); + } + + @Override + public long requested() { + return emitter.requested(); + } + + @Override + public boolean isCancelled() { + return emitter.isCancelled(); + } + + @Override + public FlowableEmitter serialize() { + return this; + } + + @Override + public String toString() { + return emitter.toString(); + } + } + + abstract static class BaseEmitter + extends AtomicLong + implements FlowableEmitter, Subscription { + private static final long serialVersionUID = 7326289992464377023L; + + final Subscriber downstream; + + final SequentialDisposable serial; + + BaseEmitter(Subscriber downstream) { + this.downstream = downstream; + this.serial = new SequentialDisposable(); + } + + @Override + public void onComplete() { + complete(); + } + + protected void complete() { + if (isCancelled()) { + return; + } + try { + downstream.onComplete(); + } finally { + serial.dispose(); + } + } + + @Override + public final void onError(Throwable e) { + if (!tryOnError(e)) { + RxJavaPlugins.onError(e); + } + } + + @Override + public boolean tryOnError(Throwable e) { + return error(e); + } + + protected boolean error(Throwable e) { + if (e == null) { + e = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + if (isCancelled()) { + return false; + } + try { + downstream.onError(e); + } finally { + serial.dispose(); + } + return true; + } + + @Override + public final void cancel() { + serial.dispose(); + onUnsubscribed(); + } + + void onUnsubscribed() { + // default is no-op + } + + @Override + public final boolean isCancelled() { + return serial.isDisposed(); + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(this, n); + onRequested(); + } + } + + void onRequested() { + // default is no-op + } + + @Override + public final void setDisposable(Disposable d) { + serial.update(d); + } + + @Override + public final void setCancellable(Cancellable c) { + setDisposable(new CancellableDisposable(c)); + } + + @Override + public final long requested() { + return get(); + } + + @Override + public final FlowableEmitter serialize() { + return new SerializedEmitter(this); + } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } + } + + static final class MissingEmitter extends BaseEmitter { + + private static final long serialVersionUID = 3776720187248809713L; + + MissingEmitter(Subscriber downstream) { + super(downstream); + } + + @Override + public void onNext(T t) { + if (isCancelled()) { + return; + } + + if (t != null) { + downstream.onNext(t); + } else { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + + for (;;) { + long r = get(); + if (r == 0L || compareAndSet(r, r - 1)) { + return; + } + } + } + + } + + abstract static class NoOverflowBaseAsyncEmitter extends BaseEmitter { + + private static final long serialVersionUID = 4127754106204442833L; + + NoOverflowBaseAsyncEmitter(Subscriber downstream) { + super(downstream); + } + + @Override + public final void onNext(T t) { + if (isCancelled()) { + return; + } + + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + + if (get() != 0) { + downstream.onNext(t); + BackpressureHelper.produced(this, 1); + } else { + onOverflow(); + } + } + + abstract void onOverflow(); + } + + static final class DropAsyncEmitter extends NoOverflowBaseAsyncEmitter { + + private static final long serialVersionUID = 8360058422307496563L; + + DropAsyncEmitter(Subscriber downstream) { + super(downstream); + } + + @Override + void onOverflow() { + // nothing to do + } + + } + + static final class ErrorAsyncEmitter extends NoOverflowBaseAsyncEmitter { + + private static final long serialVersionUID = 338953216916120960L; + + ErrorAsyncEmitter(Subscriber downstream) { + super(downstream); + } + + @Override + void onOverflow() { + onError(new MissingBackpressureException("create: could not emit value due to lack of requests")); + } + + } + + static final class BufferAsyncEmitter extends BaseEmitter { + + private static final long serialVersionUID = 2427151001689639875L; + + final SpscLinkedArrayQueue queue; + + Throwable error; + volatile boolean done; + + final AtomicInteger wip; + + BufferAsyncEmitter(Subscriber actual, int capacityHint) { + super(actual); + this.queue = new SpscLinkedArrayQueue(capacityHint); + this.wip = new AtomicInteger(); + } + + @Override + public void onNext(T t) { + if (done || isCancelled()) { + return; + } + + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + queue.offer(t); + drain(); + } + + @Override + public boolean tryOnError(Throwable e) { + if (done || isCancelled()) { + return false; + } + + if (e == null) { + e = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + + error = e; + done = true; + drain(); + return true; + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + void onRequested() { + drain(); + } + + @Override + void onUnsubscribed() { + if (wip.getAndIncrement() == 0) { + queue.clear(); + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final Subscriber a = downstream; + final SpscLinkedArrayQueue q = queue; + + for (;;) { + long r = get(); + long e = 0L; + + while (e != r) { + if (isCancelled()) { + q.clear(); + return; + } + + boolean d = done; + + T o = q.poll(); + + boolean empty = o == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + error(ex); + } else { + complete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(o); + + e++; + } + + if (e == r) { + if (isCancelled()) { + q.clear(); + return; + } + + boolean d = done; + + boolean empty = q.isEmpty(); + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + error(ex); + } else { + complete(); + } + return; + } + } + + if (e != 0) { + BackpressureHelper.produced(this, e); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class LatestAsyncEmitter extends BaseEmitter { + + private static final long serialVersionUID = 4023437720691792495L; + + final AtomicReference queue; + + Throwable error; + volatile boolean done; + + final AtomicInteger wip; + + LatestAsyncEmitter(Subscriber downstream) { + super(downstream); + this.queue = new AtomicReference(); + this.wip = new AtomicInteger(); + } + + @Override + public void onNext(T t) { + if (done || isCancelled()) { + return; + } + + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + queue.set(t); + drain(); + } + + @Override + public boolean tryOnError(Throwable e) { + if (done || isCancelled()) { + return false; + } + if (e == null) { + onError(new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources.")); + } + error = e; + done = true; + drain(); + return true; + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + void onRequested() { + drain(); + } + + @Override + void onUnsubscribed() { + if (wip.getAndIncrement() == 0) { + queue.lazySet(null); + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final Subscriber a = downstream; + final AtomicReference q = queue; + + for (;;) { + long r = get(); + long e = 0L; + + while (e != r) { + if (isCancelled()) { + q.lazySet(null); + return; + } + + boolean d = done; + + T o = q.getAndSet(null); + + boolean empty = o == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + error(ex); + } else { + complete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(o); + + e++; + } + + if (e == r) { + if (isCancelled()) { + q.lazySet(null); + return; + } + + boolean d = done; + + boolean empty = q.get() == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + error(ex); + } else { + complete(); + } + return; + } + } + + if (e != 0) { + BackpressureHelper.produced(this, e); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java new file mode 100755 index 0000000..143e61e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java @@ -0,0 +1,207 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.*; + +public final class FlowableDebounce extends AbstractFlowableWithUpstream { + final Function> debounceSelector; + + public FlowableDebounce(Flowable source, Function> debounceSelector) { + super(source); + this.debounceSelector = debounceSelector; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new DebounceSubscriber(new SerializedSubscriber(s), debounceSelector)); + } + + static final class DebounceSubscriber extends AtomicLong + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 6725975399620862591L; + final Subscriber downstream; + final Function> debounceSelector; + + Subscription upstream; + + final AtomicReference debouncer = new AtomicReference(); + + volatile long index; + + boolean done; + + DebounceSubscriber(Subscriber actual, + Function> debounceSelector) { + this.downstream = actual; + this.debounceSelector = debounceSelector; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + long idx = index + 1; + index = idx; + + Disposable d = debouncer.get(); + if (d != null) { + d.dispose(); + } + + Publisher p; + + try { + p = ObjectHelper.requireNonNull(debounceSelector.apply(t), "The publisher supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + downstream.onError(e); + return; + } + + DebounceInnerSubscriber dis = new DebounceInnerSubscriber(this, idx, t); + + if (debouncer.compareAndSet(d, dis)) { + p.subscribe(dis); + } + } + + @Override + public void onError(Throwable t) { + DisposableHelper.dispose(debouncer); + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + Disposable d = debouncer.get(); + if (!DisposableHelper.isDisposed(d)) { + @SuppressWarnings("unchecked") + DebounceInnerSubscriber dis = (DebounceInnerSubscriber)d; + if (dis != null) { + dis.emit(); + } + DisposableHelper.dispose(debouncer); + downstream.onComplete(); + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(this, n); + } + } + + @Override + public void cancel() { + upstream.cancel(); + DisposableHelper.dispose(debouncer); + } + + void emit(long idx, T value) { + if (idx == index) { + long r = get(); + if (r != 0L) { + downstream.onNext(value); + BackpressureHelper.produced(this, 1); + } else { + cancel(); + downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); + } + } + } + + static final class DebounceInnerSubscriber extends DisposableSubscriber { + final DebounceSubscriber parent; + final long index; + final T value; + + boolean done; + + final AtomicBoolean once = new AtomicBoolean(); + + DebounceInnerSubscriber(DebounceSubscriber parent, long index, T value) { + this.parent = parent; + this.index = index; + this.value = value; + } + + @Override + public void onNext(U t) { + if (done) { + return; + } + done = true; + cancel(); + emit(); + } + + void emit() { + if (once.compareAndSet(false, true)) { + parent.emit(index, value); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + parent.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + emit(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java new file mode 100755 index 0000000..dc6a493 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounceTimed.java @@ -0,0 +1,209 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableDebounceTimed extends AbstractFlowableWithUpstream { + final long timeout; + final TimeUnit unit; + final Scheduler scheduler; + + public FlowableDebounceTimed(Flowable source, long timeout, TimeUnit unit, Scheduler scheduler) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new DebounceTimedSubscriber( + new SerializedSubscriber(s), + timeout, unit, scheduler.createWorker())); + } + + static final class DebounceTimedSubscriber extends AtomicLong + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -9102637559663639004L; + final Subscriber downstream; + final long timeout; + final TimeUnit unit; + final Worker worker; + + Subscription upstream; + + Disposable timer; + + volatile long index; + + boolean done; + + DebounceTimedSubscriber(Subscriber actual, long timeout, TimeUnit unit, Worker worker) { + this.downstream = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long idx = index + 1; + index = idx; + + Disposable d = timer; + if (d != null) { + d.dispose(); + } + + DebounceEmitter de = new DebounceEmitter(t, idx, this); + timer = de; + d = worker.schedule(de, timeout, unit); + de.setResource(d); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + Disposable d = timer; + if (d != null) { + d.dispose(); + } + downstream.onError(t); + worker.dispose(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + Disposable d = timer; + if (d != null) { + d.dispose(); + } + + @SuppressWarnings("unchecked") + DebounceEmitter de = (DebounceEmitter)d; + if (de != null) { + de.emit(); + } + + downstream.onComplete(); + worker.dispose(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(this, n); + } + } + + @Override + public void cancel() { + upstream.cancel(); + worker.dispose(); + } + + void emit(long idx, T t, DebounceEmitter emitter) { + if (idx == index) { + long r = get(); + if (r != 0L) { + downstream.onNext(t); + BackpressureHelper.produced(this, 1); + + emitter.dispose(); + } else { + cancel(); + downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); + } + } + } + } + + static final class DebounceEmitter extends AtomicReference implements Runnable, Disposable { + + private static final long serialVersionUID = 6812032969491025141L; + + final T value; + final long idx; + final DebounceTimedSubscriber parent; + + final AtomicBoolean once = new AtomicBoolean(); + + DebounceEmitter(T value, long idx, DebounceTimedSubscriber parent) { + this.value = value; + this.idx = idx; + this.parent = parent; + } + + @Override + public void run() { + emit(); + } + + void emit() { + if (once.compareAndSet(false, true)) { + parent.emit(idx, value, this); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + + public void setResource(Disposable d) { + DisposableHelper.replace(this, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java new file mode 100755 index 0000000..7ac2660 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDefer.java @@ -0,0 +1,44 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.EmptySubscription; + +public final class FlowableDefer extends Flowable { + final Callable> supplier; + public FlowableDefer(Callable> supplier) { + this.supplier = supplier; + } + + @Override + public void subscribeActual(Subscriber s) { + Publisher pub; + try { + pub = ObjectHelper.requireNonNull(supplier.call(), "The publisher supplied is null"); + } catch (Throwable t) { + Exceptions.throwIfFatal(t); + EmptySubscription.error(t, s); + return; + } + + pub.subscribe(s); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelay.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelay.java new file mode 100755 index 0000000..81ca0b4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelay.java @@ -0,0 +1,146 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableDelay extends AbstractFlowableWithUpstream { + final long delay; + final TimeUnit unit; + final Scheduler scheduler; + final boolean delayError; + + public FlowableDelay(Flowable source, long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) { + super(source); + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + this.delayError = delayError; + } + + @Override + protected void subscribeActual(Subscriber t) { + Subscriber downstream; + if (delayError) { + downstream = t; + } else { + downstream = new SerializedSubscriber(t); + } + + Worker w = scheduler.createWorker(); + + source.subscribe(new DelaySubscriber(downstream, delay, unit, w, delayError)); + } + + static final class DelaySubscriber implements FlowableSubscriber, Subscription { + final Subscriber downstream; + final long delay; + final TimeUnit unit; + final Worker w; + final boolean delayError; + + Subscription upstream; + + DelaySubscriber(Subscriber actual, long delay, TimeUnit unit, Worker w, boolean delayError) { + super(); + this.downstream = actual; + this.delay = delay; + this.unit = unit; + this.w = w; + this.delayError = delayError; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(final T t) { + w.schedule(new OnNext(t), delay, unit); + } + + @Override + public void onError(final Throwable t) { + w.schedule(new OnError(t), delayError ? delay : 0, unit); + } + + @Override + public void onComplete() { + w.schedule(new OnComplete(), delay, unit); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + w.dispose(); + } + + final class OnNext implements Runnable { + private final T t; + + OnNext(T t) { + this.t = t; + } + + @Override + public void run() { + downstream.onNext(t); + } + } + + final class OnError implements Runnable { + private final Throwable t; + + OnError(Throwable t) { + this.t = t; + } + + @Override + public void run() { + try { + downstream.onError(t); + } finally { + w.dispose(); + } + } + } + + final class OnComplete implements Runnable { + @Override + public void run() { + try { + downstream.onComplete(); + } finally { + w.dispose(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java new file mode 100755 index 0000000..ea52987 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelaySubscriptionOther.java @@ -0,0 +1,141 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Delays the subscription to the main source until the other + * observable fires an event or completes. + * @param the main type + * @param the other value type, ignored + */ +public final class FlowableDelaySubscriptionOther extends Flowable { + final Publisher main; + final Publisher other; + + public FlowableDelaySubscriptionOther(Publisher main, Publisher other) { + this.main = main; + this.other = other; + } + + @Override + public void subscribeActual(final Subscriber child) { + MainSubscriber parent = new MainSubscriber(child, main); + child.onSubscribe(parent); + other.subscribe(parent.other); + } + + static final class MainSubscriber extends AtomicLong implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 2259811067697317255L; + + final Subscriber downstream; + + final Publisher main; + + final OtherSubscriber other; + + final AtomicReference upstream; + + MainSubscriber(Subscriber downstream, Publisher main) { + this.downstream = downstream; + this.main = main; + this.other = new OtherSubscriber(); + this.upstream = new AtomicReference(); + } + + void next() { + main.subscribe(this); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + SubscriptionHelper.deferredRequest(upstream, this, n); + } + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(other); + SubscriptionHelper.cancel(upstream); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(upstream, this, s); + } + + final class OtherSubscriber extends AtomicReference implements FlowableSubscriber { + + private static final long serialVersionUID = -3892798459447644106L; + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Object t) { + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + s.cancel(); + next(); + } + } + + @Override + public void onError(Throwable t) { + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + next(); + } + } + } +} +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java new file mode 100755 index 0000000..5fe5211 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDematerialize.java @@ -0,0 +1,126 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableDematerialize extends AbstractFlowableWithUpstream { + + final Function> selector; + + public FlowableDematerialize(Flowable source, Function> selector) { + super(source); + this.selector = selector; + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + source.subscribe(new DematerializeSubscriber(subscriber, selector)); + } + + static final class DematerializeSubscriber implements FlowableSubscriber, Subscription { + + final Subscriber downstream; + + final Function> selector; + + boolean done; + + Subscription upstream; + + DematerializeSubscriber(Subscriber downstream, Function> selector) { + this.downstream = downstream; + this.selector = selector; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T item) { + if (done) { + if (item instanceof Notification) { + Notification notification = (Notification)item; + if (notification.isOnError()) { + RxJavaPlugins.onError(notification.getError()); + } + } + return; + } + + Notification notification; + + try { + notification = ObjectHelper.requireNonNull(selector.apply(item), "The selector returned a null Notification"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + if (notification.isOnError()) { + upstream.cancel(); + onError(notification.getError()); + } else if (notification.isOnComplete()) { + upstream.cancel(); + onComplete(); + } else { + downstream.onNext(notification.getValue()); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + downstream.onComplete(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDetach.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDetach.java new file mode 100755 index 0000000..7679ee1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDetach.java @@ -0,0 +1,87 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.EmptyComponent; + +public final class FlowableDetach extends AbstractFlowableWithUpstream { + + public FlowableDetach(Flowable source) { + super(source); + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new DetachSubscriber(s)); + } + + static final class DetachSubscriber implements FlowableSubscriber, Subscription { + + Subscriber downstream; + + Subscription upstream; + + DetachSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + Subscription s = this.upstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asSubscriber(); + s.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + Subscriber a = downstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asSubscriber(); + a.onError(t); + } + + @Override + public void onComplete() { + Subscriber a = downstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asSubscriber(); + a.onComplete(); + } + } +} + diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java new file mode 100755 index 0000000..4e9bc5e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinct.java @@ -0,0 +1,144 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.QueueFuseable; +import io.reactivex.internal.subscribers.BasicFuseableSubscriber; +import io.reactivex.internal.subscriptions.EmptySubscription; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableDistinct extends AbstractFlowableWithUpstream { + + final Function keySelector; + + final Callable> collectionSupplier; + + public FlowableDistinct(Flowable source, Function keySelector, Callable> collectionSupplier) { + super(source); + this.keySelector = keySelector; + this.collectionSupplier = collectionSupplier; + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + Collection collection; + + try { + collection = ObjectHelper.requireNonNull(collectionSupplier.call(), "The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, subscriber); + return; + } + + source.subscribe(new DistinctSubscriber(subscriber, keySelector, collection)); + } + + static final class DistinctSubscriber extends BasicFuseableSubscriber { + + final Collection collection; + + final Function keySelector; + + DistinctSubscriber(Subscriber actual, Function keySelector, Collection collection) { + super(actual); + this.keySelector = keySelector; + this.collection = collection; + } + + @Override + public void onNext(T value) { + if (done) { + return; + } + if (sourceMode == NONE) { + K key; + boolean b; + + try { + key = ObjectHelper.requireNonNull(keySelector.apply(value), "The keySelector returned a null key"); + b = collection.add(key); + } catch (Throwable ex) { + fail(ex); + return; + } + + if (b) { + downstream.onNext(value); + } else { + upstream.request(1); + } + } else { + downstream.onNext(null); + } + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaPlugins.onError(e); + } else { + done = true; + collection.clear(); + downstream.onError(e); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + collection.clear(); + downstream.onComplete(); + } + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + for (;;) { + T v = qs.poll(); + + if (v == null || collection.add(ObjectHelper.requireNonNull(keySelector.apply(v), "The keySelector returned a null key"))) { + return v; + } else { + if (sourceMode == QueueFuseable.ASYNC) { + upstream.request(1); + } + } + } + } + + @Override + public void clear() { + collection.clear(); + super.clear(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java new file mode 100755 index 0000000..c1cf546 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChanged.java @@ -0,0 +1,227 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; +import io.reactivex.functions.*; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscribers.*; + +public final class FlowableDistinctUntilChanged extends AbstractFlowableWithUpstream { + + final Function keySelector; + + final BiPredicate comparer; + + public FlowableDistinctUntilChanged(Flowable source, Function keySelector, BiPredicate comparer) { + super(source); + this.keySelector = keySelector; + this.comparer = comparer; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (s instanceof ConditionalSubscriber) { + ConditionalSubscriber cs = (ConditionalSubscriber) s; + source.subscribe(new DistinctUntilChangedConditionalSubscriber(cs, keySelector, comparer)); + } else { + source.subscribe(new DistinctUntilChangedSubscriber(s, keySelector, comparer)); + } + } + + static final class DistinctUntilChangedSubscriber extends BasicFuseableSubscriber + implements ConditionalSubscriber { + + final Function keySelector; + + final BiPredicate comparer; + + K last; + + boolean hasValue; + + DistinctUntilChangedSubscriber(Subscriber actual, + Function keySelector, + BiPredicate comparer) { + super(actual); + this.keySelector = keySelector; + this.comparer = comparer; + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t)) { + upstream.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + if (sourceMode != NONE) { + downstream.onNext(t); + return true; + } + + K key; + + try { + key = keySelector.apply(t); + if (hasValue) { + boolean equal = comparer.test(last, key); + last = key; + if (equal) { + return false; + } + } else { + hasValue = true; + last = key; + } + } catch (Throwable ex) { + fail(ex); + return true; + } + + downstream.onNext(t); + return true; + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + for (;;) { + T v = qs.poll(); + if (v == null) { + return null; + } + K key = keySelector.apply(v); + if (!hasValue) { + hasValue = true; + last = key; + return v; + } + + if (!comparer.test(last, key)) { + last = key; + return v; + } + last = key; + if (sourceMode != SYNC) { + upstream.request(1); + } + } + } + + } + + static final class DistinctUntilChangedConditionalSubscriber extends BasicFuseableConditionalSubscriber { + + final Function keySelector; + + final BiPredicate comparer; + + K last; + + boolean hasValue; + + DistinctUntilChangedConditionalSubscriber(ConditionalSubscriber actual, + Function keySelector, + BiPredicate comparer) { + super(actual); + this.keySelector = keySelector; + this.comparer = comparer; + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t)) { + upstream.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + if (sourceMode != NONE) { + return downstream.tryOnNext(t); + } + + K key; + + try { + key = keySelector.apply(t); + if (hasValue) { + boolean equal = comparer.test(last, key); + last = key; + if (equal) { + return false; + } + } else { + hasValue = true; + last = key; + } + } catch (Throwable ex) { + fail(ex); + return true; + } + + downstream.onNext(t); + return true; + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + for (;;) { + T v = qs.poll(); + if (v == null) { + return null; + } + K key = keySelector.apply(v); + if (!hasValue) { + hasValue = true; + last = key; + return v; + } + + if (!comparer.test(last, key)) { + last = key; + return v; + } + last = key; + if (sourceMode != SYNC) { + upstream.request(1); + } + } + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNext.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNext.java new file mode 100755 index 0000000..968c589 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoAfterNext.java @@ -0,0 +1,137 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.*; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscribers.*; + +/** + * Calls a consumer after pushing the current item to the downstream. + *

History: 2.0.1 - experimental + * @param the value type + * @since 2.1 + */ +public final class FlowableDoAfterNext extends AbstractFlowableWithUpstream { + + final Consumer onAfterNext; + + public FlowableDoAfterNext(Flowable source, Consumer onAfterNext) { + super(source); + this.onAfterNext = onAfterNext; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (s instanceof ConditionalSubscriber) { + source.subscribe(new DoAfterConditionalSubscriber((ConditionalSubscriber)s, onAfterNext)); + } else { + source.subscribe(new DoAfterSubscriber(s, onAfterNext)); + } + } + + static final class DoAfterSubscriber extends BasicFuseableSubscriber { + + final Consumer onAfterNext; + + DoAfterSubscriber(Subscriber actual, Consumer onAfterNext) { + super(actual); + this.onAfterNext = onAfterNext; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + downstream.onNext(t); + + if (sourceMode == NONE) { + try { + onAfterNext.accept(t); + } catch (Throwable ex) { + fail(ex); + } + } + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + T v = qs.poll(); + if (v != null) { + onAfterNext.accept(v); + } + return v; + } + } + + static final class DoAfterConditionalSubscriber extends BasicFuseableConditionalSubscriber { + + final Consumer onAfterNext; + + DoAfterConditionalSubscriber(ConditionalSubscriber actual, Consumer onAfterNext) { + super(actual); + this.onAfterNext = onAfterNext; + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + + if (sourceMode == NONE) { + try { + onAfterNext.accept(t); + } catch (Throwable ex) { + fail(ex); + } + } + } + + @Override + public boolean tryOnNext(T t) { + boolean b = downstream.tryOnNext(t); + try { + onAfterNext.accept(t); + } catch (Throwable ex) { + fail(ex); + } + return b; + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + T v = qs.poll(); + if (v != null) { + onAfterNext.accept(v); + } + return v; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoFinally.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoFinally.java new file mode 100755 index 0000000..023c55b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoFinally.java @@ -0,0 +1,264 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.annotations.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Execute an action after an onError, onComplete or a cancel event. + *

History: 2.0.1 - experimental + * @param the value type + * @since 2.1 + */ +public final class FlowableDoFinally extends AbstractFlowableWithUpstream { + + final Action onFinally; + + public FlowableDoFinally(Flowable source, Action onFinally) { + super(source); + this.onFinally = onFinally; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (s instanceof ConditionalSubscriber) { + source.subscribe(new DoFinallyConditionalSubscriber((ConditionalSubscriber)s, onFinally)); + } else { + source.subscribe(new DoFinallySubscriber(s, onFinally)); + } + } + + static final class DoFinallySubscriber extends BasicIntQueueSubscription implements FlowableSubscriber { + + private static final long serialVersionUID = 4109457741734051389L; + + final Subscriber downstream; + + final Action onFinally; + + Subscription upstream; + + QueueSubscription qs; + + boolean syncFused; + + DoFinallySubscriber(Subscriber actual, Action onFinally) { + this.downstream = actual; + this.onFinally = onFinally; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + if (s instanceof QueueSubscription) { + this.qs = (QueueSubscription)s; + } + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + runFinally(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + runFinally(); + } + + @Override + public void cancel() { + upstream.cancel(); + runFinally(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public int requestFusion(int mode) { + QueueSubscription qs = this.qs; + if (qs != null && (mode & BOUNDARY) == 0) { + int m = qs.requestFusion(mode); + if (m != NONE) { + syncFused = m == SYNC; + } + return m; + } + return NONE; + } + + @Override + public void clear() { + qs.clear(); + } + + @Override + public boolean isEmpty() { + return qs.isEmpty(); + } + + @Nullable + @Override + public T poll() throws Exception { + T v = qs.poll(); + if (v == null && syncFused) { + runFinally(); + } + return v; + } + + void runFinally() { + if (compareAndSet(0, 1)) { + try { + onFinally.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + } + + static final class DoFinallyConditionalSubscriber extends BasicIntQueueSubscription implements ConditionalSubscriber { + + private static final long serialVersionUID = 4109457741734051389L; + + final ConditionalSubscriber downstream; + + final Action onFinally; + + Subscription upstream; + + QueueSubscription qs; + + boolean syncFused; + + DoFinallyConditionalSubscriber(ConditionalSubscriber actual, Action onFinally) { + this.downstream = actual; + this.onFinally = onFinally; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + if (s instanceof QueueSubscription) { + this.qs = (QueueSubscription)s; + } + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public boolean tryOnNext(T t) { + return downstream.tryOnNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + runFinally(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + runFinally(); + } + + @Override + public void cancel() { + upstream.cancel(); + runFinally(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public int requestFusion(int mode) { + QueueSubscription qs = this.qs; + if (qs != null && (mode & BOUNDARY) == 0) { + int m = qs.requestFusion(mode); + if (m != NONE) { + syncFused = m == SYNC; + } + return m; + } + return NONE; + } + + @Override + public void clear() { + qs.clear(); + } + + @Override + public boolean isEmpty() { + return qs.isEmpty(); + } + + @Nullable + @Override + public T poll() throws Exception { + T v = qs.poll(); + if (v == null && syncFused) { + runFinally(); + } + return v; + } + + void runFinally() { + if (compareAndSet(0, 1)) { + try { + onFinally.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnEach.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnEach.java new file mode 100755 index 0000000..e913675 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnEach.java @@ -0,0 +1,348 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscribers.*; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableDoOnEach extends AbstractFlowableWithUpstream { + final Consumer onNext; + final Consumer onError; + final Action onComplete; + final Action onAfterTerminate; + + public FlowableDoOnEach(Flowable source, Consumer onNext, + Consumer onError, + Action onComplete, + Action onAfterTerminate) { + super(source); + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + this.onAfterTerminate = onAfterTerminate; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (s instanceof ConditionalSubscriber) { + source.subscribe(new DoOnEachConditionalSubscriber( + (ConditionalSubscriber)s, onNext, onError, onComplete, onAfterTerminate)); + } else { + source.subscribe(new DoOnEachSubscriber( + s, onNext, onError, onComplete, onAfterTerminate)); + } + } + + static final class DoOnEachSubscriber extends BasicFuseableSubscriber { + final Consumer onNext; + final Consumer onError; + final Action onComplete; + final Action onAfterTerminate; + + DoOnEachSubscriber( + Subscriber actual, + Consumer onNext, + Consumer onError, + Action onComplete, + Action onAfterTerminate) { + super(actual); + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + this.onAfterTerminate = onAfterTerminate; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + if (sourceMode != NONE) { + downstream.onNext(null); + return; + } + + try { + onNext.accept(t); + } catch (Throwable e) { + fail(e); + return; + } + + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + boolean relay = true; + try { + onError.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + relay = false; + } + if (relay) { + downstream.onError(t); + } + + try { + onAfterTerminate.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + try { + onComplete.run(); + } catch (Throwable e) { + fail(e); + return; + } + + done = true; + downstream.onComplete(); + + try { + onAfterTerminate.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + T v; + + try { + v = qs.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + try { + onError.accept(ex); + } catch (Throwable exc) { + throw new CompositeException(ex, exc); + } + throw ExceptionHelper.throwIfThrowable(ex); + } + + if (v != null) { + try { + try { + onNext.accept(v); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + try { + onError.accept(ex); + } catch (Throwable exc) { + throw new CompositeException(ex, exc); + } + throw ExceptionHelper.throwIfThrowable(ex); + } + } finally { + onAfterTerminate.run(); + } + } else { + if (sourceMode == SYNC) { + onComplete.run(); + + onAfterTerminate.run(); + } + } + return v; + } + } + + static final class DoOnEachConditionalSubscriber extends BasicFuseableConditionalSubscriber { + final Consumer onNext; + final Consumer onError; + final Action onComplete; + final Action onAfterTerminate; + + DoOnEachConditionalSubscriber( + ConditionalSubscriber actual, + Consumer onNext, + Consumer onError, + Action onComplete, + Action onAfterTerminate) { + super(actual); + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + this.onAfterTerminate = onAfterTerminate; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + if (sourceMode != NONE) { + downstream.onNext(null); + return; + } + + try { + onNext.accept(t); + } catch (Throwable e) { + fail(e); + return; + } + + downstream.onNext(t); + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + + try { + onNext.accept(t); + } catch (Throwable e) { + fail(e); + return false; + } + + return downstream.tryOnNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + boolean relay = true; + try { + onError.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + relay = false; + } + if (relay) { + downstream.onError(t); + } + + try { + onAfterTerminate.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + try { + onComplete.run(); + } catch (Throwable e) { + fail(e); + return; + } + + done = true; + downstream.onComplete(); + + try { + onAfterTerminate.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + T v; + + try { + v = qs.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + try { + onError.accept(ex); + } catch (Throwable exc) { + throw new CompositeException(ex, exc); + } + throw ExceptionHelper.throwIfThrowable(ex); + } + + if (v != null) { + try { + try { + onNext.accept(v); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + try { + onError.accept(ex); + } catch (Throwable exc) { + throw new CompositeException(ex, exc); + } + throw ExceptionHelper.throwIfThrowable(ex); + } + } finally { + onAfterTerminate.run(); + } + } else { + if (sourceMode == SYNC) { + onComplete.run(); + + onAfterTerminate.run(); + } + } + return v; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java new file mode 100755 index 0000000..0c979d2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDoOnLifecycle.java @@ -0,0 +1,124 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableDoOnLifecycle extends AbstractFlowableWithUpstream { + private final Consumer onSubscribe; + private final LongConsumer onRequest; + private final Action onCancel; + + public FlowableDoOnLifecycle(Flowable source, Consumer onSubscribe, + LongConsumer onRequest, Action onCancel) { + super(source); + this.onSubscribe = onSubscribe; + this.onRequest = onRequest; + this.onCancel = onCancel; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new SubscriptionLambdaSubscriber(s, onSubscribe, onRequest, onCancel)); + } + + static final class SubscriptionLambdaSubscriber implements FlowableSubscriber, Subscription { + final Subscriber downstream; + final Consumer onSubscribe; + final LongConsumer onRequest; + final Action onCancel; + + Subscription upstream; + + SubscriptionLambdaSubscriber(Subscriber actual, + Consumer onSubscribe, + LongConsumer onRequest, + Action onCancel) { + this.downstream = actual; + this.onSubscribe = onSubscribe; + this.onCancel = onCancel; + this.onRequest = onRequest; + } + + @Override + public void onSubscribe(Subscription s) { + // this way, multiple calls to onSubscribe can show up in tests that use doOnSubscribe to validate behavior + try { + onSubscribe.accept(s); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + s.cancel(); + this.upstream = SubscriptionHelper.CANCELLED; + EmptySubscription.error(e, downstream); + return; + } + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (upstream != SubscriptionHelper.CANCELLED) { + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (upstream != SubscriptionHelper.CANCELLED) { + downstream.onComplete(); + } + } + + @Override + public void request(long n) { + try { + onRequest.accept(n); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + upstream.request(n); + } + + @Override + public void cancel() { + Subscription s = upstream; + if (s != SubscriptionHelper.CANCELLED) { + upstream = SubscriptionHelper.CANCELLED; + try { + onCancel.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + s.cancel(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAt.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAt.java new file mode 100755 index 0000000..9d3ead4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAt.java @@ -0,0 +1,119 @@ +/** + * 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.internal.operators.flowable; + +import java.util.NoSuchElementException; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableElementAt extends AbstractFlowableWithUpstream { + final long index; + final T defaultValue; + final boolean errorOnFewer; + + public FlowableElementAt(Flowable source, long index, T defaultValue, boolean errorOnFewer) { + super(source); + this.index = index; + this.defaultValue = defaultValue; + this.errorOnFewer = errorOnFewer; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ElementAtSubscriber(s, index, defaultValue, errorOnFewer)); + } + + static final class ElementAtSubscriber extends DeferredScalarSubscription implements FlowableSubscriber { + + private static final long serialVersionUID = 4066607327284737757L; + + final long index; + final T defaultValue; + final boolean errorOnFewer; + + Subscription upstream; + + long count; + + boolean done; + + ElementAtSubscriber(Subscriber actual, long index, T defaultValue, boolean errorOnFewer) { + super(actual); + this.index = index; + this.defaultValue = defaultValue; + this.errorOnFewer = errorOnFewer; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long c = count; + if (c == index) { + done = true; + upstream.cancel(); + complete(t); + return; + } + count = c + 1; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + T v = defaultValue; + if (v == null) { + if (errorOnFewer) { + downstream.onError(new NoSuchElementException()); + } else { + downstream.onComplete(); + } + } else { + complete(v); + } + } + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtMaybe.java new file mode 100755 index 0000000..4d41199 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtMaybe.java @@ -0,0 +1,118 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableElementAtMaybe extends Maybe implements FuseToFlowable { + final Flowable source; + + final long index; + + public FlowableElementAtMaybe(Flowable source, long index) { + this.source = source; + this.index = index; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new ElementAtSubscriber(observer, index)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableElementAt(source, index, null, false)); + } + + static final class ElementAtSubscriber implements FlowableSubscriber, Disposable { + + final MaybeObserver downstream; + + final long index; + + Subscription upstream; + + long count; + + boolean done; + + ElementAtSubscriber(MaybeObserver actual, long index) { + this.downstream = actual; + this.index = index; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long c = count; + if (c == index) { + done = true; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(t); + return; + } + count = c + 1; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = SubscriptionHelper.CANCELLED; + if (!done) { + done = true; + downstream.onComplete(); + } + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtSingle.java new file mode 100755 index 0000000..7cd542d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableElementAtSingle.java @@ -0,0 +1,131 @@ +/** + * 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.internal.operators.flowable; + +import java.util.NoSuchElementException; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableElementAtSingle extends Single implements FuseToFlowable { + final Flowable source; + + final long index; + + final T defaultValue; + + public FlowableElementAtSingle(Flowable source, long index, T defaultValue) { + this.source = source; + this.index = index; + this.defaultValue = defaultValue; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new ElementAtSubscriber(observer, index, defaultValue)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableElementAt(source, index, defaultValue, true)); + } + + static final class ElementAtSubscriber implements FlowableSubscriber, Disposable { + + final SingleObserver downstream; + + final long index; + final T defaultValue; + + Subscription upstream; + + long count; + + boolean done; + + ElementAtSubscriber(SingleObserver actual, long index, T defaultValue) { + this.downstream = actual; + this.index = index; + this.defaultValue = defaultValue; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long c = count; + if (c == index) { + done = true; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(t); + return; + } + count = c + 1; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = SubscriptionHelper.CANCELLED; + if (!done) { + done = true; + + T v = defaultValue; + + if (v != null) { + downstream.onSuccess(v); + } else { + downstream.onError(new NoSuchElementException()); + } + } + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableEmpty.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableEmpty.java new file mode 100755 index 0000000..dafd417 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableEmpty.java @@ -0,0 +1,41 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.internal.fuseable.ScalarCallable; +import io.reactivex.internal.subscriptions.EmptySubscription; + +/** + * A source Flowable that signals an onSubscribe() + onComplete() only. + */ +public final class FlowableEmpty extends Flowable implements ScalarCallable { + + public static final Flowable INSTANCE = new FlowableEmpty(); + + private FlowableEmpty() { + } + + @Override + public void subscribeActual(Subscriber s) { + EmptySubscription.complete(s); + } + + @Override + public Object call() { + return null; // null scalar is interpreted as being empty + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java new file mode 100755 index 0000000..dc88f01 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableError.java @@ -0,0 +1,42 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.EmptySubscription; + +public final class FlowableError extends Flowable { + final Callable errorSupplier; + public FlowableError(Callable errorSupplier) { + this.errorSupplier = errorSupplier; + } + + @Override + public void subscribeActual(Subscriber s) { + Throwable error; + try { + error = ObjectHelper.requireNonNull(errorSupplier.call(), "Callable returned null throwable. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable t) { + Exceptions.throwIfFatal(t); + error = t; + } + EmptySubscription.error(error, s); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java new file mode 100755 index 0000000..dd90b2c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFilter.java @@ -0,0 +1,169 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscribers.*; + +public final class FlowableFilter extends AbstractFlowableWithUpstream { + final Predicate predicate; + public FlowableFilter(Flowable source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (s instanceof ConditionalSubscriber) { + source.subscribe(new FilterConditionalSubscriber( + (ConditionalSubscriber)s, predicate)); + } else { + source.subscribe(new FilterSubscriber(s, predicate)); + } + } + + static final class FilterSubscriber extends BasicFuseableSubscriber + implements ConditionalSubscriber { + final Predicate filter; + + FilterSubscriber(Subscriber actual, Predicate filter) { + super(actual); + this.filter = filter; + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t)) { + upstream.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + if (sourceMode != NONE) { + downstream.onNext(null); + return true; + } + boolean b; + try { + b = filter.test(t); + } catch (Throwable e) { + fail(e); + return true; + } + if (b) { + downstream.onNext(t); + } + return b; + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + QueueSubscription qs = this.qs; + Predicate f = filter; + + for (;;) { + T t = qs.poll(); + if (t == null) { + return null; + } + + if (f.test(t)) { + return t; + } + + if (sourceMode == ASYNC) { + qs.request(1); + } + } + } + } + + static final class FilterConditionalSubscriber extends BasicFuseableConditionalSubscriber { + final Predicate filter; + + FilterConditionalSubscriber(ConditionalSubscriber actual, Predicate filter) { + super(actual); + this.filter = filter; + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t)) { + upstream.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + + if (sourceMode != NONE) { + return downstream.tryOnNext(null); + } + + boolean b; + try { + b = filter.test(t); + } catch (Throwable e) { + fail(e); + return true; + } + return b && downstream.tryOnNext(t); + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + QueueSubscription qs = this.qs; + Predicate f = filter; + + for (;;) { + T t = qs.poll(); + if (t == null) { + return null; + } + + if (f.test(t)) { + return t; + } + + if (sourceMode == ASYNC) { + qs.request(1); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java new file mode 100755 index 0000000..a7631de --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java @@ -0,0 +1,708 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableFlatMap extends AbstractFlowableWithUpstream { + final Function> mapper; + final boolean delayErrors; + final int maxConcurrency; + final int bufferSize; + + public FlowableFlatMap(Flowable source, + Function> mapper, + boolean delayErrors, int maxConcurrency, int bufferSize) { + super(source); + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.bufferSize = bufferSize; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (FlowableScalarXMap.tryScalarXMapSubscribe(source, s, mapper)) { + return; + } + source.subscribe(subscribe(s, mapper, delayErrors, maxConcurrency, bufferSize)); + } + + public static FlowableSubscriber subscribe(Subscriber s, + Function> mapper, + boolean delayErrors, int maxConcurrency, int bufferSize) { + return new MergeSubscriber(s, mapper, delayErrors, maxConcurrency, bufferSize); + } + + static final class MergeSubscriber extends AtomicInteger implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -2117620485640801370L; + + final Subscriber downstream; + final Function> mapper; + final boolean delayErrors; + final int maxConcurrency; + final int bufferSize; + + volatile SimplePlainQueue queue; + + volatile boolean done; + + final AtomicThrowable errs = new AtomicThrowable(); + + volatile boolean cancelled; + + final AtomicReference[]> subscribers = new AtomicReference[]>(); + + static final InnerSubscriber[] EMPTY = new InnerSubscriber[0]; + + static final InnerSubscriber[] CANCELLED = new InnerSubscriber[0]; + + final AtomicLong requested = new AtomicLong(); + + Subscription upstream; + + long uniqueId; + long lastId; + int lastIndex; + + int scalarEmitted; + final int scalarLimit; + + MergeSubscriber(Subscriber actual, Function> mapper, + boolean delayErrors, int maxConcurrency, int bufferSize) { + this.downstream = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.bufferSize = bufferSize; + this.scalarLimit = Math.max(1, maxConcurrency >> 1); + subscribers.lazySet(EMPTY); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + if (!cancelled) { + if (maxConcurrency == Integer.MAX_VALUE) { + s.request(Long.MAX_VALUE); + } else { + s.request(maxConcurrency); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void onNext(T t) { + // safeguard against misbehaving sources + if (done) { + return; + } + Publisher p; + try { + p = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + onError(e); + return; + } + if (p instanceof Callable) { + U u; + + try { + u = ((Callable)p).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errs.addThrowable(ex); + drain(); + return; + } + + if (u != null) { + tryEmitScalar(u); + } else { + if (maxConcurrency != Integer.MAX_VALUE && !cancelled + && ++scalarEmitted == scalarLimit) { + scalarEmitted = 0; + upstream.request(scalarLimit); + } + } + } else { + InnerSubscriber inner = new InnerSubscriber(this, uniqueId++); + if (addInner(inner)) { + p.subscribe(inner); + } + } + } + + boolean addInner(InnerSubscriber inner) { + for (;;) { + InnerSubscriber[] a = subscribers.get(); + if (a == CANCELLED) { + inner.dispose(); + return false; + } + int n = a.length; + InnerSubscriber[] b = new InnerSubscriber[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + void removeInner(InnerSubscriber inner) { + for (;;) { + InnerSubscriber[] a = subscribers.get(); + int n = a.length; + if (n == 0) { + return; + } + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + if (j < 0) { + return; + } + InnerSubscriber[] b; + if (n == 1) { + b = EMPTY; + } else { + b = new InnerSubscriber[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (subscribers.compareAndSet(a, b)) { + return; + } + } + } + + SimpleQueue getMainQueue() { + SimplePlainQueue q = queue; + if (q == null) { + if (maxConcurrency == Integer.MAX_VALUE) { + q = new SpscLinkedArrayQueue(bufferSize); + } else { + q = new SpscArrayQueue(maxConcurrency); + } + queue = q; + } + return q; + } + + void tryEmitScalar(U value) { + if (get() == 0 && compareAndSet(0, 1)) { + long r = requested.get(); + SimpleQueue q = queue; + if (r != 0L && (q == null || q.isEmpty())) { + downstream.onNext(value); + if (r != Long.MAX_VALUE) { + requested.decrementAndGet(); + } + if (maxConcurrency != Integer.MAX_VALUE && !cancelled + && ++scalarEmitted == scalarLimit) { + scalarEmitted = 0; + upstream.request(scalarLimit); + } + } else { + if (q == null) { + q = getMainQueue(); + } + if (!q.offer(value)) { + onError(new IllegalStateException("Scalar queue full?!")); + return; + } + } + if (decrementAndGet() == 0) { + return; + } + } else { + SimpleQueue q = getMainQueue(); + if (!q.offer(value)) { + onError(new IllegalStateException("Scalar queue full?!")); + return; + } + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + SimpleQueue getInnerQueue(InnerSubscriber inner) { + SimpleQueue q = inner.queue; + if (q == null) { + q = new SpscArrayQueue(bufferSize); + inner.queue = q; + } + return q; + } + + void tryEmit(U value, InnerSubscriber inner) { + if (get() == 0 && compareAndSet(0, 1)) { + long r = requested.get(); + SimpleQueue q = inner.queue; + if (r != 0L && (q == null || q.isEmpty())) { + downstream.onNext(value); + if (r != Long.MAX_VALUE) { + requested.decrementAndGet(); + } + inner.requestMore(1); + } else { + if (q == null) { + q = getInnerQueue(inner); + } + if (!q.offer(value)) { + onError(new MissingBackpressureException("Inner queue full?!")); + return; + } + } + if (decrementAndGet() == 0) { + return; + } + } else { + SimpleQueue q = inner.queue; + if (q == null) { + q = new SpscArrayQueue(bufferSize); + inner.queue = q; + } + if (!q.offer(value)) { + onError(new MissingBackpressureException("Inner queue full?!")); + return; + } + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + // safeguard against misbehaving sources + if (done) { + RxJavaPlugins.onError(t); + return; + } + if (errs.addThrowable(t)) { + done = true; + if (!delayErrors) { + for (InnerSubscriber a : subscribers.getAndSet(CANCELLED)) { + a.dispose(); + } + } + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + // safeguard against misbehaving sources + if (done) { + return; + } + done = true; + drain(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + upstream.cancel(); + disposeAll(); + if (getAndIncrement() == 0) { + SimpleQueue q = queue; + if (q != null) { + q.clear(); + } + } + } + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + final Subscriber child = this.downstream; + int missed = 1; + for (;;) { + if (checkTerminate()) { + return; + } + SimplePlainQueue svq = queue; + + long r = requested.get(); + boolean unbounded = r == Long.MAX_VALUE; + + long replenishMain = 0; + + if (svq != null) { + for (;;) { + long scalarEmission = 0; + U o = null; + while (r != 0L) { + o = svq.poll(); + + if (checkTerminate()) { + return; + } + if (o == null) { + break; + } + + child.onNext(o); + + replenishMain++; + scalarEmission++; + r--; + } + if (scalarEmission != 0L) { + if (unbounded) { + r = Long.MAX_VALUE; + } else { + r = requested.addAndGet(-scalarEmission); + } + } + if (r == 0L || o == null) { + break; + } + } + } + + boolean d = done; + svq = queue; + InnerSubscriber[] inner = subscribers.get(); + int n = inner.length; + + if (d && (svq == null || svq.isEmpty()) && n == 0) { + Throwable ex = errs.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + if (ex == null) { + child.onComplete(); + } else { + child.onError(ex); + } + } + return; + } + + boolean innerCompleted = false; + if (n != 0) { + long startId = lastId; + int index = lastIndex; + + if (n <= index || inner[index].id != startId) { + if (n <= index) { + index = 0; + } + int j = index; + for (int i = 0; i < n; i++) { + if (inner[j].id == startId) { + break; + } + j++; + if (j == n) { + j = 0; + } + } + index = j; + lastIndex = j; + lastId = inner[j].id; + } + + int j = index; + sourceLoop: + for (int i = 0; i < n; i++) { + if (checkTerminate()) { + return; + } + + @SuppressWarnings("unchecked") + InnerSubscriber is = (InnerSubscriber)inner[j]; + + U o = null; + for (;;) { + if (checkTerminate()) { + return; + } + SimpleQueue q = is.queue; + if (q == null) { + break; + } + long produced = 0; + while (r != 0L) { + + try { + o = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + is.dispose(); + errs.addThrowable(ex); + if (!delayErrors) { + upstream.cancel(); + } + if (checkTerminate()) { + return; + } + removeInner(is); + innerCompleted = true; + i++; + continue sourceLoop; + } + if (o == null) { + break; + } + + child.onNext(o); + + if (checkTerminate()) { + return; + } + + r--; + produced++; + } + if (produced != 0L) { + if (!unbounded) { + r = requested.addAndGet(-produced); + } else { + r = Long.MAX_VALUE; + } + is.requestMore(produced); + } + if (r == 0 || o == null) { + break; + } + } + boolean innerDone = is.done; + SimpleQueue innerQueue = is.queue; + if (innerDone && (innerQueue == null || innerQueue.isEmpty())) { + removeInner(is); + if (checkTerminate()) { + return; + } + replenishMain++; + innerCompleted = true; + } + if (r == 0L) { + break; + } + + j++; + if (j == n) { + j = 0; + } + } + lastIndex = j; + lastId = inner[j].id; + } + + if (replenishMain != 0L && !cancelled) { + upstream.request(replenishMain); + } + if (innerCompleted) { + continue; + } + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminate() { + if (cancelled) { + clearScalarQueue(); + return true; + } + if (!delayErrors && errs.get() != null) { + clearScalarQueue(); + Throwable ex = errs.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + return true; + } + return false; + } + + void clearScalarQueue() { + SimpleQueue q = queue; + if (q != null) { + q.clear(); + } + } + + void disposeAll() { + InnerSubscriber[] a = subscribers.get(); + if (a != CANCELLED) { + a = subscribers.getAndSet(CANCELLED); + if (a != CANCELLED) { + for (InnerSubscriber inner : a) { + inner.dispose(); + } + Throwable ex = errs.terminate(); + if (ex != null && ex != ExceptionHelper.TERMINATED) { + RxJavaPlugins.onError(ex); + } + } + } + } + + void innerError(InnerSubscriber inner, Throwable t) { + if (errs.addThrowable(t)) { + inner.done = true; + if (!delayErrors) { + upstream.cancel(); + for (InnerSubscriber a : subscribers.getAndSet(CANCELLED)) { + a.dispose(); + } + } + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + } + + static final class InnerSubscriber extends AtomicReference + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = -4606175640614850599L; + final long id; + final MergeSubscriber parent; + final int limit; + final int bufferSize; + + volatile boolean done; + volatile SimpleQueue queue; + long produced; + int fusionMode; + + InnerSubscriber(MergeSubscriber parent, long id) { + this.id = id; + this.parent = parent; + this.bufferSize = parent.bufferSize; + this.limit = bufferSize >> 2; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); + if (m == QueueSubscription.SYNC) { + fusionMode = m; + queue = qs; + done = true; + parent.drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + fusionMode = m; + queue = qs; + } + + } + + s.request(bufferSize); + } + } + + @Override + public void onNext(U t) { + if (fusionMode != QueueSubscription.ASYNC) { + parent.tryEmit(t, this); + } else { + parent.drain(); + } + } + + @Override + public void onError(Throwable t) { + lazySet(SubscriptionHelper.CANCELLED); + parent.innerError(this, t); + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + + void requestMore(long n) { + if (fusionMode != QueueSubscription.SYNC) { + long p = produced + n; + if (p >= limit) { + produced = 0; + get().request(p); + } else { + produced = p; + } + } + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletable.java new file mode 100755 index 0000000..86311bc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletable.java @@ -0,0 +1,239 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps a sequence of values into CompletableSources and awaits their termination. + * @param the value type + */ +public final class FlowableFlatMapCompletable extends AbstractFlowableWithUpstream { + + final Function mapper; + + final int maxConcurrency; + + final boolean delayErrors; + + public FlowableFlatMapCompletable(Flowable source, + Function mapper, boolean delayErrors, + int maxConcurrency) { + super(source); + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + source.subscribe(new FlatMapCompletableMainSubscriber(subscriber, mapper, delayErrors, maxConcurrency)); + } + + static final class FlatMapCompletableMainSubscriber extends BasicIntQueueSubscription + implements FlowableSubscriber { + private static final long serialVersionUID = 8443155186132538303L; + + final Subscriber downstream; + + final AtomicThrowable errors; + + final Function mapper; + + final boolean delayErrors; + + final CompositeDisposable set; + + final int maxConcurrency; + + Subscription upstream; + + volatile boolean cancelled; + + FlatMapCompletableMainSubscriber(Subscriber subscriber, + Function mapper, boolean delayErrors, + int maxConcurrency) { + this.downstream = subscriber; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.set = new CompositeDisposable(); + this.maxConcurrency = maxConcurrency; + this.lazySet(1); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + int m = maxConcurrency; + if (m == Integer.MAX_VALUE) { + s.request(Long.MAX_VALUE); + } else { + s.request(m); + } + } + } + + @Override + public void onNext(T value) { + CompletableSource cs; + + try { + cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + getAndIncrement(); + + InnerConsumer inner = new InnerConsumer(); + + if (!cancelled && set.add(inner)) { + cs.subscribe(inner); + } + } + + @Override + public void onError(Throwable e) { + if (errors.addThrowable(e)) { + if (delayErrors) { + if (decrementAndGet() == 0) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } else { + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } + } else { + cancel(); + if (getAndSet(0) > 0) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (decrementAndGet() == 0) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + } else { + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + set.dispose(); + } + + @Override + public void request(long n) { + // ignored, no values emitted + } + + @Nullable + @Override + public T poll() throws Exception { + return null; // always empty + } + + @Override + public boolean isEmpty() { + return true; // always empty + } + + @Override + public void clear() { + // nothing to clear + } + + @Override + public int requestFusion(int mode) { + return mode & ASYNC; + } + + void innerComplete(InnerConsumer inner) { + set.delete(inner); + onComplete(); + } + + void innerError(InnerConsumer inner, Throwable e) { + set.delete(inner); + onError(e); + } + + final class InnerConsumer extends AtomicReference implements CompletableObserver, Disposable { + private static final long serialVersionUID = 8606673141535671828L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onComplete() { + innerComplete(this); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableCompletable.java new file mode 100755 index 0000000..ad54342 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapCompletableCompletable.java @@ -0,0 +1,225 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps a sequence of values into CompletableSources and awaits their termination. + * @param the value type + */ +public final class FlowableFlatMapCompletableCompletable extends Completable implements FuseToFlowable { + + final Flowable source; + + final Function mapper; + + final int maxConcurrency; + + final boolean delayErrors; + + public FlowableFlatMapCompletableCompletable(Flowable source, + Function mapper, boolean delayErrors, + int maxConcurrency) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new FlatMapCompletableMainSubscriber(observer, mapper, delayErrors, maxConcurrency)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableFlatMapCompletable(source, mapper, delayErrors, maxConcurrency)); + } + + static final class FlatMapCompletableMainSubscriber extends AtomicInteger + implements FlowableSubscriber, Disposable { + private static final long serialVersionUID = 8443155186132538303L; + + final CompletableObserver downstream; + + final AtomicThrowable errors; + + final Function mapper; + + final boolean delayErrors; + + final CompositeDisposable set; + + final int maxConcurrency; + + Subscription upstream; + + volatile boolean disposed; + + FlatMapCompletableMainSubscriber(CompletableObserver observer, + Function mapper, boolean delayErrors, + int maxConcurrency) { + this.downstream = observer; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.set = new CompositeDisposable(); + this.maxConcurrency = maxConcurrency; + this.lazySet(1); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + int m = maxConcurrency; + if (m == Integer.MAX_VALUE) { + s.request(Long.MAX_VALUE); + } else { + s.request(m); + } + } + } + + @Override + public void onNext(T value) { + CompletableSource cs; + + try { + cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + getAndIncrement(); + + InnerObserver inner = new InnerObserver(); + + if (!disposed && set.add(inner)) { + cs.subscribe(inner); + } + } + + @Override + public void onError(Throwable e) { + if (errors.addThrowable(e)) { + if (delayErrors) { + if (decrementAndGet() == 0) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } else { + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } + } else { + dispose(); + if (getAndSet(0) > 0) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (decrementAndGet() == 0) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + } else { + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } + } + + @Override + public void dispose() { + disposed = true; + upstream.cancel(); + set.dispose(); + } + + @Override + public boolean isDisposed() { + return set.isDisposed(); + } + + void innerComplete(InnerObserver inner) { + set.delete(inner); + onComplete(); + } + + void innerError(InnerObserver inner, Throwable e) { + set.delete(inner); + onError(e); + } + + final class InnerObserver extends AtomicReference implements CompletableObserver, Disposable { + private static final long serialVersionUID = 8606673141535671828L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onComplete() { + innerComplete(this); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybe.java new file mode 100755 index 0000000..ab2950e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapMaybe.java @@ -0,0 +1,421 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps upstream values into MaybeSources and merges their signals into one sequence. + * @param the source value type + * @param the result value type + */ +public final class FlowableFlatMapMaybe extends AbstractFlowableWithUpstream { + + final Function> mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + public FlowableFlatMapMaybe(Flowable source, Function> mapper, + boolean delayError, int maxConcurrency) { + super(source); + this.mapper = mapper; + this.delayErrors = delayError; + this.maxConcurrency = maxConcurrency; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new FlatMapMaybeSubscriber(s, mapper, delayErrors, maxConcurrency)); + } + + static final class FlatMapMaybeSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 8600231336733376951L; + + final Subscriber downstream; + + final boolean delayErrors; + + final int maxConcurrency; + + final AtomicLong requested; + + final CompositeDisposable set; + + final AtomicInteger active; + + final AtomicThrowable errors; + + final Function> mapper; + + final AtomicReference> queue; + + Subscription upstream; + + volatile boolean cancelled; + + FlatMapMaybeSubscriber(Subscriber actual, + Function> mapper, boolean delayErrors, int maxConcurrency) { + this.downstream = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.requested = new AtomicLong(); + this.set = new CompositeDisposable(); + this.errors = new AtomicThrowable(); + this.active = new AtomicInteger(1); + this.queue = new AtomicReference>(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + int m = maxConcurrency; + if (m == Integer.MAX_VALUE) { + s.request(Long.MAX_VALUE); + } else { + s.request(maxConcurrency); + } + } + } + + @Override + public void onNext(T t) { + MaybeSource ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + active.getAndIncrement(); + + InnerObserver inner = new InnerObserver(); + + if (!cancelled && set.add(inner)) { + ms.subscribe(inner); + } + } + + @Override + public void onError(Throwable t) { + active.decrementAndGet(); + if (errors.addThrowable(t)) { + if (!delayErrors) { + set.dispose(); + } + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + active.decrementAndGet(); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + set.dispose(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + void innerSuccess(InnerObserver inner, R value) { + set.delete(inner); + if (get() == 0 && compareAndSet(0, 1)) { + boolean d = active.decrementAndGet() == 0; + if (requested.get() != 0) { + downstream.onNext(value); + + SpscLinkedArrayQueue q = queue.get(); + + if (d && (q == null || q.isEmpty())) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + BackpressureHelper.produced(requested, 1); + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } else { + SpscLinkedArrayQueue q = getOrCreateQueue(); + synchronized (q) { + q.offer(value); + } + } + if (decrementAndGet() == 0) { + return; + } + } else { + SpscLinkedArrayQueue q = getOrCreateQueue(); + synchronized (q) { + q.offer(value); + } + active.decrementAndGet(); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + SpscLinkedArrayQueue getOrCreateQueue() { + for (;;) { + SpscLinkedArrayQueue current = queue.get(); + if (current != null) { + return current; + } + current = new SpscLinkedArrayQueue(Flowable.bufferSize()); + if (queue.compareAndSet(null, current)) { + return current; + } + } + } + + void innerError(InnerObserver inner, Throwable e) { + set.delete(inner); + if (errors.addThrowable(e)) { + if (!delayErrors) { + upstream.cancel(); + set.dispose(); + } else { + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } + active.decrementAndGet(); + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + void innerComplete(InnerObserver inner) { + set.delete(inner); + + if (get() == 0 && compareAndSet(0, 1)) { + boolean d = active.decrementAndGet() == 0; + SpscLinkedArrayQueue q = queue.get(); + + if (d && (q == null || q.isEmpty())) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + if (decrementAndGet() == 0) { + return; + } + drainLoop(); + } else { + active.decrementAndGet(); + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + drain(); + } + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void clear() { + SpscLinkedArrayQueue q = queue.get(); + if (q != null) { + q.clear(); + } + } + + void drainLoop() { + int missed = 1; + Subscriber a = downstream; + AtomicInteger n = active; + AtomicReference> qr = queue; + + for (;;) { + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + clear(); + return; + } + + if (!delayErrors) { + Throwable ex = errors.get(); + if (ex != null) { + ex = errors.terminate(); + clear(); + a.onError(ex); + return; + } + } + + boolean d = n.get() == 0; + SpscLinkedArrayQueue q = qr.get(); + R v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e == r) { + if (cancelled) { + clear(); + return; + } + + if (!delayErrors) { + Throwable ex = errors.get(); + if (ex != null) { + ex = errors.terminate(); + clear(); + a.onError(ex); + return; + } + } + + boolean d = n.get() == 0; + SpscLinkedArrayQueue q = qr.get(); + boolean empty = q == null || q.isEmpty(); + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + final class InnerObserver extends AtomicReference + implements MaybeObserver, Disposable { + private static final long serialVersionUID = -502562646270949838L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R value) { + innerSuccess(this, value); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public void onComplete() { + innerComplete(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapPublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapPublisher.java new file mode 100755 index 0000000..b5e0f18 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapPublisher.java @@ -0,0 +1,45 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.functions.Function; + +public final class FlowableFlatMapPublisher extends Flowable { + final Publisher source; + final Function> mapper; + final boolean delayErrors; + final int maxConcurrency; + final int bufferSize; + + public FlowableFlatMapPublisher(Publisher source, + Function> mapper, + boolean delayErrors, int maxConcurrency, int bufferSize) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.bufferSize = bufferSize; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (FlowableScalarXMap.tryScalarXMapSubscribe(source, s, mapper)) { + return; + } + source.subscribe(FlowableFlatMap.subscribe(s, mapper, delayErrors, maxConcurrency, bufferSize)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingle.java new file mode 100755 index 0000000..0633248 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMapSingle.java @@ -0,0 +1,383 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps upstream values into SingleSources and merges their signals into one sequence. + * @param the source value type + * @param the result value type + */ +public final class FlowableFlatMapSingle extends AbstractFlowableWithUpstream { + + final Function> mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + public FlowableFlatMapSingle(Flowable source, Function> mapper, + boolean delayError, int maxConcurrency) { + super(source); + this.mapper = mapper; + this.delayErrors = delayError; + this.maxConcurrency = maxConcurrency; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new FlatMapSingleSubscriber(s, mapper, delayErrors, maxConcurrency)); + } + + static final class FlatMapSingleSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 8600231336733376951L; + + final Subscriber downstream; + + final boolean delayErrors; + + final int maxConcurrency; + + final AtomicLong requested; + + final CompositeDisposable set; + + final AtomicInteger active; + + final AtomicThrowable errors; + + final Function> mapper; + + final AtomicReference> queue; + + Subscription upstream; + + volatile boolean cancelled; + + FlatMapSingleSubscriber(Subscriber actual, + Function> mapper, boolean delayErrors, int maxConcurrency) { + this.downstream = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.requested = new AtomicLong(); + this.set = new CompositeDisposable(); + this.errors = new AtomicThrowable(); + this.active = new AtomicInteger(1); + this.queue = new AtomicReference>(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + int m = maxConcurrency; + if (m == Integer.MAX_VALUE) { + s.request(Long.MAX_VALUE); + } else { + s.request(maxConcurrency); + } + } + } + + @Override + public void onNext(T t) { + SingleSource ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + active.getAndIncrement(); + + InnerObserver inner = new InnerObserver(); + + if (!cancelled && set.add(inner)) { + ms.subscribe(inner); + } + } + + @Override + public void onError(Throwable t) { + active.decrementAndGet(); + if (errors.addThrowable(t)) { + if (!delayErrors) { + set.dispose(); + } + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + active.decrementAndGet(); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + set.dispose(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + void innerSuccess(InnerObserver inner, R value) { + set.delete(inner); + if (get() == 0 && compareAndSet(0, 1)) { + boolean d = active.decrementAndGet() == 0; + if (requested.get() != 0) { + downstream.onNext(value); + + SpscLinkedArrayQueue q = queue.get(); + + if (d && (q == null || q.isEmpty())) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + BackpressureHelper.produced(requested, 1); + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } else { + SpscLinkedArrayQueue q = getOrCreateQueue(); + synchronized (q) { + q.offer(value); + } + } + if (decrementAndGet() == 0) { + return; + } + } else { + SpscLinkedArrayQueue q = getOrCreateQueue(); + synchronized (q) { + q.offer(value); + } + active.decrementAndGet(); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + SpscLinkedArrayQueue getOrCreateQueue() { + for (;;) { + SpscLinkedArrayQueue current = queue.get(); + if (current != null) { + return current; + } + current = new SpscLinkedArrayQueue(Flowable.bufferSize()); + if (queue.compareAndSet(null, current)) { + return current; + } + } + } + + void innerError(InnerObserver inner, Throwable e) { + set.delete(inner); + if (errors.addThrowable(e)) { + if (!delayErrors) { + upstream.cancel(); + set.dispose(); + } else { + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(1); + } + } + active.decrementAndGet(); + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void clear() { + SpscLinkedArrayQueue q = queue.get(); + if (q != null) { + q.clear(); + } + } + + void drainLoop() { + int missed = 1; + Subscriber a = downstream; + AtomicInteger n = active; + AtomicReference> qr = queue; + + for (;;) { + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + clear(); + return; + } + + if (!delayErrors) { + Throwable ex = errors.get(); + if (ex != null) { + ex = errors.terminate(); + clear(); + a.onError(ex); + return; + } + } + + boolean d = n.get() == 0; + SpscLinkedArrayQueue q = qr.get(); + R v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e == r) { + if (cancelled) { + clear(); + return; + } + + if (!delayErrors) { + Throwable ex = errors.get(); + if (ex != null) { + ex = errors.terminate(); + clear(); + a.onError(ex); + return; + } + } + + boolean d = n.get() == 0; + SpscLinkedArrayQueue q = qr.get(); + boolean empty = q == null || q.isEmpty(); + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + if (maxConcurrency != Integer.MAX_VALUE) { + upstream.request(e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + final class InnerObserver extends AtomicReference + implements SingleObserver, Disposable { + private static final long serialVersionUID = -502562646270949838L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R value) { + innerSuccess(this, value); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java new file mode 100755 index 0000000..01398b9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlattenIterable.java @@ -0,0 +1,454 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Iterator; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableFlattenIterable extends AbstractFlowableWithUpstream { + + final Function> mapper; + + final int prefetch; + + public FlowableFlattenIterable(Flowable source, + Function> mapper, int prefetch) { + super(source); + this.mapper = mapper; + this.prefetch = prefetch; + } + + @SuppressWarnings("unchecked") + @Override + public void subscribeActual(Subscriber s) { + if (source instanceof Callable) { + T v; + + try { + v = ((Callable)source).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, s); + return; + } + + if (v == null) { + EmptySubscription.complete(s); + return; + } + + Iterator it; + + try { + Iterable iterable = mapper.apply(v); + + it = iterable.iterator(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, s); + return; + } + + FlowableFromIterable.subscribe(s, it); + + return; + } + source.subscribe(new FlattenIterableSubscriber(s, mapper, prefetch)); + } + + static final class FlattenIterableSubscriber + extends BasicIntQueueSubscription + implements FlowableSubscriber { + + private static final long serialVersionUID = -3096000382929934955L; + + final Subscriber downstream; + + final Function> mapper; + + final int prefetch; + + final int limit; + + final AtomicLong requested; + + Subscription upstream; + + SimpleQueue queue; + + volatile boolean done; + + volatile boolean cancelled; + + final AtomicReference error; + + Iterator current; + + int consumed; + + int fusionMode; + + FlattenIterableSubscriber(Subscriber actual, + Function> mapper, int prefetch) { + this.downstream = actual; + this.mapper = mapper; + this.prefetch = prefetch; + this.limit = prefetch - (prefetch >> 2); + this.error = new AtomicReference(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(ANY); + + if (m == SYNC) { + fusionMode = m; + this.queue = qs; + done = true; + + downstream.onSubscribe(this); + + return; + } + if (m == ASYNC) { + fusionMode = m; + this.queue = qs; + + downstream.onSubscribe(this); + + s.request(prefetch); + return; + } + } + + queue = new SpscArrayQueue(prefetch); + + downstream.onSubscribe(this); + + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (fusionMode == NONE && !queue.offer(t)) { + onError(new MissingBackpressureException("Queue is full?!")); + return; + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (!done && ExceptionHelper.addThrowable(error, t)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + drain(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + upstream.cancel(); + + if (getAndIncrement() == 0) { + queue.clear(); + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + final Subscriber a = downstream; + final SimpleQueue q = queue; + final boolean replenish = fusionMode != SYNC; + + int missed = 1; + + Iterator it = current; + + for (;;) { + + if (it == null) { + + boolean d = done; + + T t; + + try { + t = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + ExceptionHelper.addThrowable(error, ex); + ex = ExceptionHelper.terminate(error); + + current = null; + q.clear(); + + a.onError(ex); + return; + } + + boolean empty = t == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (t != null) { + Iterable iterable; + + boolean b; + + try { + iterable = mapper.apply(t); + + it = iterable.iterator(); + + b = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + ExceptionHelper.addThrowable(error, ex); + ex = ExceptionHelper.terminate(error); + a.onError(ex); + return; + } + + if (!b) { + it = null; + consumedOne(replenish); + continue; + } + + current = it; + } + } + + if (it != null) { + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (checkTerminated(done, false, a, q)) { + return; + } + + R v; + + try { + v = ObjectHelper.requireNonNull(it.next(), "The iterator returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + current = null; + upstream.cancel(); + ExceptionHelper.addThrowable(error, ex); + ex = ExceptionHelper.terminate(error); + a.onError(ex); + return; + } + + a.onNext(v); + + if (checkTerminated(done, false, a, q)) { + return; + } + + e++; + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + current = null; + upstream.cancel(); + ExceptionHelper.addThrowable(error, ex); + ex = ExceptionHelper.terminate(error); + a.onError(ex); + return; + } + + if (!b) { + consumedOne(replenish); + it = null; + current = null; + break; + } + } + + if (e == r) { + boolean d = done; + boolean empty = q.isEmpty() && it == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + } + + if (e != 0L) { + if (r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + } + + if (it == null) { + continue; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void consumedOne(boolean enabled) { + if (enabled) { + int c = consumed + 1; + if (c == limit) { + consumed = 0; + upstream.request(c); + } else { + consumed = c; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, SimpleQueue q) { + if (cancelled) { + current = null; + q.clear(); + return true; + } + if (d) { + Throwable ex = error.get(); + if (ex != null) { + ex = ExceptionHelper.terminate(error); + + current = null; + q.clear(); + + a.onError(ex); + return true; + } else if (empty) { + a.onComplete(); + return true; + } + } + return false; + } + + @Override + public void clear() { + current = null; + queue.clear(); + } + + @Override + public boolean isEmpty() { + return current == null && queue.isEmpty(); + } + + @Nullable + @Override + public R poll() throws Exception { + Iterator it = current; + for (;;) { + if (it == null) { + T v = queue.poll(); + if (v == null) { + return null; + } + + it = mapper.apply(v).iterator(); + + if (!it.hasNext()) { + it = null; + continue; + } + current = it; + } + + R r = ObjectHelper.requireNonNull(it.next(), "The iterator returned a null value"); + + if (!it.hasNext()) { + current = null; + } + + return r; + } + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & SYNC) != 0 && fusionMode == SYNC) { + return SYNC; + } + return NONE; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java new file mode 100755 index 0000000..d54fb15 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java @@ -0,0 +1,272 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; + +public final class FlowableFromArray extends Flowable { + final T[] array; + + public FlowableFromArray(T[] array) { + this.array = array; + } + + @Override + public void subscribeActual(Subscriber s) { + if (s instanceof ConditionalSubscriber) { + s.onSubscribe(new ArrayConditionalSubscription( + (ConditionalSubscriber)s, array)); + } else { + s.onSubscribe(new ArraySubscription(s, array)); + } + } + + abstract static class BaseArraySubscription extends BasicQueueSubscription { + private static final long serialVersionUID = -2252972430506210021L; + + final T[] array; + + int index; + + volatile boolean cancelled; + + BaseArraySubscription(T[] array) { + this.array = array; + } + + @Override + public final int requestFusion(int mode) { + return mode & SYNC; + } + + @Nullable + @Override + public final T poll() { + int i = index; + T[] arr = array; + if (i == arr.length) { + return null; + } + + index = i + 1; + return ObjectHelper.requireNonNull(arr[i], "array element is null"); + } + + @Override + public final boolean isEmpty() { + return index == array.length; + } + + @Override + public final void clear() { + index = array.length; + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + if (BackpressureHelper.add(this, n) == 0L) { + if (n == Long.MAX_VALUE) { + fastPath(); + } else { + slowPath(n); + } + } + } + } + + @Override + public final void cancel() { + cancelled = true; + } + + abstract void fastPath(); + + abstract void slowPath(long r); + } + + static final class ArraySubscription extends BaseArraySubscription { + + private static final long serialVersionUID = 2587302975077663557L; + + final Subscriber downstream; + + ArraySubscription(Subscriber actual, T[] array) { + super(array); + this.downstream = actual; + } + + @Override + void fastPath() { + T[] arr = array; + int f = arr.length; + Subscriber a = downstream; + + for (int i = index; i != f; i++) { + if (cancelled) { + return; + } + T t = arr[i]; + if (t == null) { + a.onError(new NullPointerException("The element at index " + i + " is null")); + return; + } else { + a.onNext(t); + } + } + if (cancelled) { + return; + } + a.onComplete(); + } + + @Override + void slowPath(long r) { + long e = 0; + T[] arr = array; + int f = arr.length; + int i = index; + Subscriber a = downstream; + + for (;;) { + + while (e != r && i != f) { + if (cancelled) { + return; + } + + T t = arr[i]; + + if (t == null) { + a.onError(new NullPointerException("The element at index " + i + " is null")); + return; + } else { + a.onNext(t); + } + + e++; + i++; + } + + if (i == f) { + if (!cancelled) { + a.onComplete(); + } + return; + } + + r = get(); + if (e == r) { + index = i; + r = addAndGet(-e); + if (r == 0L) { + return; + } + e = 0L; + } + } + } + } + + static final class ArrayConditionalSubscription extends BaseArraySubscription { + + private static final long serialVersionUID = 2587302975077663557L; + + final ConditionalSubscriber downstream; + + ArrayConditionalSubscription(ConditionalSubscriber actual, T[] array) { + super(array); + this.downstream = actual; + } + + @Override + void fastPath() { + T[] arr = array; + int f = arr.length; + ConditionalSubscriber a = downstream; + + for (int i = index; i != f; i++) { + if (cancelled) { + return; + } + T t = arr[i]; + if (t == null) { + a.onError(new NullPointerException("The element at index " + i + " is null")); + return; + } else { + a.tryOnNext(t); + } + } + if (cancelled) { + return; + } + a.onComplete(); + } + + @Override + void slowPath(long r) { + long e = 0; + T[] arr = array; + int f = arr.length; + int i = index; + ConditionalSubscriber a = downstream; + + for (;;) { + + while (e != r && i != f) { + if (cancelled) { + return; + } + + T t = arr[i]; + + if (t == null) { + a.onError(new NullPointerException("The element at index " + i + " is null")); + return; + } else { + if (a.tryOnNext(t)) { + e++; + } + + i++; + } + } + + if (i == f) { + if (!cancelled) { + a.onComplete(); + } + return; + } + + r = get(); + if (e == r) { + index = i; + r = addAndGet(-e); + if (r == 0L) { + return; + } + e = 0L; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java new file mode 100755 index 0000000..6dcb226 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromCallable.java @@ -0,0 +1,57 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.DeferredScalarSubscription; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableFromCallable extends Flowable implements Callable { + final Callable callable; + public FlowableFromCallable(Callable callable) { + this.callable = callable; + } + + @Override + public void subscribeActual(Subscriber s) { + DeferredScalarSubscription deferred = new DeferredScalarSubscription(s); + s.onSubscribe(deferred); + + T t; + try { + t = ObjectHelper.requireNonNull(callable.call(), "The callable returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (deferred.isCancelled()) { + RxJavaPlugins.onError(ex); + } else { + s.onError(ex); + } + return; + } + + deferred.complete(t); + } + + @Override + public T call() throws Exception { + return ObjectHelper.requireNonNull(callable.call(), "The callable returned a null value"); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromFuture.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromFuture.java new file mode 100755 index 0000000..102fdf8 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromFuture.java @@ -0,0 +1,56 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.*; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.subscriptions.DeferredScalarSubscription; + +public final class FlowableFromFuture extends Flowable { + final Future future; + final long timeout; + final TimeUnit unit; + + public FlowableFromFuture(Future future, long timeout, TimeUnit unit) { + this.future = future; + this.timeout = timeout; + this.unit = unit; + } + + @Override + public void subscribeActual(Subscriber s) { + DeferredScalarSubscription deferred = new DeferredScalarSubscription(s); + s.onSubscribe(deferred); + + T v; + try { + v = unit != null ? future.get(timeout, unit) : future.get(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (!deferred.isCancelled()) { + s.onError(ex); + } + return; + } + if (v == null) { + s.onError(new NullPointerException("The future returned null")); + } else { + deferred.complete(v); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java new file mode 100755 index 0000000..e893dca --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromIterable.java @@ -0,0 +1,414 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Iterator; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; + +public final class FlowableFromIterable extends Flowable { + + final Iterable source; + + public FlowableFromIterable(Iterable source) { + this.source = source; + } + + @Override + public void subscribeActual(Subscriber s) { + Iterator it; + try { + it = source.iterator(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + + subscribe(s, it); + } + + public static void subscribe(Subscriber s, Iterator it) { + boolean hasNext; + try { + hasNext = it.hasNext(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + + if (!hasNext) { + EmptySubscription.complete(s); + return; + } + + if (s instanceof ConditionalSubscriber) { + s.onSubscribe(new IteratorConditionalSubscription( + (ConditionalSubscriber)s, it)); + } else { + s.onSubscribe(new IteratorSubscription(s, it)); + } + } + + abstract static class BaseRangeSubscription extends BasicQueueSubscription { + private static final long serialVersionUID = -2252972430506210021L; + + Iterator it; + + volatile boolean cancelled; + + boolean once; + + BaseRangeSubscription(Iterator it) { + this.it = it; + } + + @Override + public final int requestFusion(int mode) { + return mode & SYNC; + } + + @Nullable + @Override + public final T poll() { + if (it == null) { + return null; + } + if (!once) { + once = true; + } else { + if (!it.hasNext()) { + return null; + } + } + return ObjectHelper.requireNonNull(it.next(), "Iterator.next() returned a null value"); + } + + @Override + public final boolean isEmpty() { + return it == null || !it.hasNext(); + } + + @Override + public final void clear() { + it = null; + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + if (BackpressureHelper.add(this, n) == 0L) { + if (n == Long.MAX_VALUE) { + fastPath(); + } else { + slowPath(n); + } + } + } + } + + @Override + public final void cancel() { + cancelled = true; + } + + abstract void fastPath(); + + abstract void slowPath(long r); + } + + static final class IteratorSubscription extends BaseRangeSubscription { + + private static final long serialVersionUID = -6022804456014692607L; + + final Subscriber downstream; + + IteratorSubscription(Subscriber actual, Iterator it) { + super(it); + this.downstream = actual; + } + + @Override + void fastPath() { + Iterator it = this.it; + Subscriber a = downstream; + for (;;) { + if (cancelled) { + return; + } + + T t; + + try { + t = it.next(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (cancelled) { + return; + } + + if (t == null) { + a.onError(new NullPointerException("Iterator.next() returned a null value")); + return; + } else { + a.onNext(t); + } + + if (cancelled) { + return; + } + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!b) { + if (!cancelled) { + a.onComplete(); + } + return; + } + } + } + + @Override + void slowPath(long r) { + long e = 0L; + Iterator it = this.it; + Subscriber a = downstream; + + for (;;) { + + while (e != r) { + + if (cancelled) { + return; + } + + T t; + + try { + t = it.next(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (cancelled) { + return; + } + + if (t == null) { + a.onError(new NullPointerException("Iterator.next() returned a null value")); + return; + } else { + a.onNext(t); + } + + if (cancelled) { + return; + } + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!b) { + if (!cancelled) { + a.onComplete(); + } + return; + } + + e++; + } + + r = get(); + if (e == r) { + r = addAndGet(-e); + if (r == 0L) { + return; + } + e = 0L; + } + } + } + + } + + static final class IteratorConditionalSubscription extends BaseRangeSubscription { + + private static final long serialVersionUID = -6022804456014692607L; + + final ConditionalSubscriber downstream; + + IteratorConditionalSubscription(ConditionalSubscriber actual, Iterator it) { + super(it); + this.downstream = actual; + } + + @Override + void fastPath() { + Iterator it = this.it; + ConditionalSubscriber a = downstream; + for (;;) { + if (cancelled) { + return; + } + + T t; + + try { + t = it.next(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (cancelled) { + return; + } + + if (t == null) { + a.onError(new NullPointerException("Iterator.next() returned a null value")); + return; + } else { + a.tryOnNext(t); + } + + if (cancelled) { + return; + } + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!b) { + if (!cancelled) { + a.onComplete(); + } + return; + } + } + } + + @Override + void slowPath(long r) { + long e = 0L; + Iterator it = this.it; + ConditionalSubscriber a = downstream; + + for (;;) { + + while (e != r) { + + if (cancelled) { + return; + } + + T t; + + try { + t = it.next(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (cancelled) { + return; + } + + boolean b; + if (t == null) { + a.onError(new NullPointerException("Iterator.next() returned a null value")); + return; + } else { + b = a.tryOnNext(t); + } + + if (cancelled) { + return; + } + + boolean hasNext; + + try { + hasNext = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!hasNext) { + if (!cancelled) { + a.onComplete(); + } + return; + } + + if (b) { + e++; + } + } + + r = get(); + if (e == r) { + r = addAndGet(-e); + if (r == 0L) { + return; + } + e = 0L; + } + } + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromObservable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromObservable.java new file mode 100755 index 0000000..7ad4edb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromObservable.java @@ -0,0 +1,72 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; + +public final class FlowableFromObservable extends Flowable { + private final Observable upstream; + + public FlowableFromObservable(Observable upstream) { + this.upstream = upstream; + } + + @Override + protected void subscribeActual(Subscriber s) { + upstream.subscribe(new SubscriberObserver(s)); + } + + static final class SubscriberObserver implements Observer, Subscription { + + final Subscriber downstream; + + Disposable upstream; + + SubscriberObserver(Subscriber s) { + this.downstream = s; + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onNext(T value) { + downstream.onNext(value); + } + + @Override + public void onSubscribe(Disposable d) { + this.upstream = d; + downstream.onSubscribe(this); + } + + @Override public void cancel() { + upstream.dispose(); + } + + @Override + public void request(long n) { + // no backpressure so nothing we can do about this + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromPublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromPublisher.java new file mode 100755 index 0000000..65dbb4e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromPublisher.java @@ -0,0 +1,31 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; + +public final class FlowableFromPublisher extends Flowable { + final Publisher publisher; + + public FlowableFromPublisher(Publisher publisher) { + this.publisher = publisher; + } + + @Override + protected void subscribeActual(Subscriber s) { + publisher.subscribe(s); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGenerate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGenerate.java new file mode 100755 index 0000000..17bdd79 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGenerate.java @@ -0,0 +1,201 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableGenerate extends Flowable { + final Callable stateSupplier; + final BiFunction, S> generator; + final Consumer disposeState; + + public FlowableGenerate(Callable stateSupplier, BiFunction, S> generator, + Consumer disposeState) { + this.stateSupplier = stateSupplier; + this.generator = generator; + this.disposeState = disposeState; + } + + @Override + public void subscribeActual(Subscriber s) { + S state; + + try { + state = stateSupplier.call(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + + s.onSubscribe(new GeneratorSubscription(s, generator, disposeState, state)); + } + + static final class GeneratorSubscription + extends AtomicLong + implements Emitter, Subscription { + + private static final long serialVersionUID = 7565982551505011832L; + + final Subscriber downstream; + final BiFunction, S> generator; + final Consumer disposeState; + + S state; + + volatile boolean cancelled; + + boolean terminate; + + boolean hasNext; + + GeneratorSubscription(Subscriber actual, + BiFunction, S> generator, + Consumer disposeState, S initialState) { + this.downstream = actual; + this.generator = generator; + this.disposeState = disposeState; + this.state = initialState; + } + + @Override + public void request(long n) { + if (!SubscriptionHelper.validate(n)) { + return; + } + if (BackpressureHelper.add(this, n) != 0L) { + return; + } + + long e = 0L; + + S s = state; + + final BiFunction, S> f = generator; + + for (;;) { + while (e != n) { + + if (cancelled) { + state = null; + dispose(s); + return; + } + + hasNext = false; + + try { + s = f.apply(s, this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelled = true; + state = null; + onError(ex); + dispose(s); + return; + } + + if (terminate) { + cancelled = true; + state = null; + dispose(s); + return; + } + + e++; + } + + n = get(); + if (e == n) { + state = s; + n = addAndGet(-e); + if (n == 0L) { + break; + } + e = 0L; + } + } + } + + private void dispose(S s) { + try { + disposeState.accept(s); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + // if there are no running requests, just dispose the state + if (BackpressureHelper.add(this, 1) == 0) { + S s = state; + state = null; + dispose(s); + } + } + } + + @Override + public void onNext(T t) { + if (!terminate) { + if (hasNext) { + onError(new IllegalStateException("onNext already called in this generate turn")); + } else { + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + } else { + hasNext = true; + downstream.onNext(t); + } + } + } + } + + @Override + public void onError(Throwable t) { + if (terminate) { + RxJavaPlugins.onError(t); + } else { + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + terminate = true; + downstream.onError(t); + } + } + + @Override + public void onComplete() { + if (!terminate) { + terminate = true; + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java new file mode 100755 index 0000000..99a63c7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java @@ -0,0 +1,749 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.flowables.GroupedFlowable; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.internal.util.EmptyComponent; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableGroupBy extends AbstractFlowableWithUpstream> { + final Function keySelector; + final Function valueSelector; + final int bufferSize; + final boolean delayError; + final Function, ? extends Map> mapFactory; + + public FlowableGroupBy(Flowable source, Function keySelector, Function valueSelector, + int bufferSize, boolean delayError, Function, ? extends Map> mapFactory) { + super(source); + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + this.mapFactory = mapFactory; + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected void subscribeActual(Subscriber> s) { + + final Map> groups; + final Queue> evictedGroups; + + try { + if (mapFactory == null) { + evictedGroups = null; + groups = new ConcurrentHashMap>(); + } else { + evictedGroups = new ConcurrentLinkedQueue>(); + Consumer evictionAction = (Consumer) new EvictionAction(evictedGroups); + groups = (Map) mapFactory.apply(evictionAction); + } + } catch (Exception e) { + Exceptions.throwIfFatal(e); + s.onSubscribe(EmptyComponent.INSTANCE); + s.onError(e); + return; + } + GroupBySubscriber subscriber = + new GroupBySubscriber(s, keySelector, valueSelector, bufferSize, delayError, groups, evictedGroups); + source.subscribe(subscriber); + } + + public static final class GroupBySubscriber + extends BasicIntQueueSubscription> + implements FlowableSubscriber { + + private static final long serialVersionUID = -3688291656102519502L; + + final Subscriber> downstream; + final Function keySelector; + final Function valueSelector; + final int bufferSize; + final boolean delayError; + final Map> groups; + final SpscLinkedArrayQueue> queue; + final Queue> evictedGroups; + + static final Object NULL_KEY = new Object(); + + Subscription upstream; + + final AtomicBoolean cancelled = new AtomicBoolean(); + + final AtomicLong requested = new AtomicLong(); + + final AtomicInteger groupCount = new AtomicInteger(1); + + Throwable error; + volatile boolean finished; + boolean done; + + boolean outputFused; + + public GroupBySubscriber(Subscriber> actual, Function keySelector, + Function valueSelector, int bufferSize, boolean delayError, + Map> groups, Queue> evictedGroups) { + this.downstream = actual; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + this.groups = groups; + this.evictedGroups = evictedGroups; + this.queue = new SpscLinkedArrayQueue>(bufferSize); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(bufferSize); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + final SpscLinkedArrayQueue> q = this.queue; + + K key; + try { + key = keySelector.apply(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + boolean newGroup = false; + Object mapKey = key != null ? key : NULL_KEY; + GroupedUnicast group = groups.get(mapKey); + if (group == null) { + // if the main has been cancelled, stop creating groups + // and skip this value + if (cancelled.get()) { + return; + } + + group = GroupedUnicast.createWith(key, bufferSize, this, delayError); + groups.put(mapKey, group); + + groupCount.getAndIncrement(); + + newGroup = true; + } + + V v; + try { + v = ObjectHelper.requireNonNull(valueSelector.apply(t), "The valueSelector returned null"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + group.onNext(v); + + completeEvictions(); + + if (newGroup) { + q.offer(group); + drain(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + for (GroupedUnicast g : groups.values()) { + g.onError(t); + } + groups.clear(); + if (evictedGroups != null) { + evictedGroups.clear(); + } + error = t; + finished = true; + drain(); + } + + @Override + public void onComplete() { + if (!done) { + for (GroupedUnicast g : groups.values()) { + g.onComplete(); + } + groups.clear(); + if (evictedGroups != null) { + evictedGroups.clear(); + } + done = true; + finished = true; + drain(); + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + // cancelling the main source means we don't want any more groups + // but running groups still require new values + if (cancelled.compareAndSet(false, true)) { + completeEvictions(); + if (groupCount.decrementAndGet() == 0) { + upstream.cancel(); + } + } + } + + private void completeEvictions() { + if (evictedGroups != null) { + int count = 0; + GroupedUnicast evictedGroup; + while ((evictedGroup = evictedGroups.poll()) != null) { + evictedGroup.onComplete(); + count++; + } + if (count != 0) { + groupCount.addAndGet(-count); + } + } + } + + public void cancel(K key) { + Object mapKey = key != null ? key : NULL_KEY; + groups.remove(mapKey); + if (groupCount.decrementAndGet() == 0) { + upstream.cancel(); + + if (!outputFused && getAndIncrement() == 0) { + queue.clear(); + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + if (outputFused) { + drainFused(); + } else { + drainNormal(); + } + } + + void drainFused() { + int missed = 1; + + final SpscLinkedArrayQueue> q = this.queue; + final Subscriber> a = this.downstream; + + for (;;) { + if (cancelled.get()) { + return; + } + + boolean d = finished; + + if (d && !delayError) { + Throwable ex = error; + if (ex != null) { + q.clear(); + a.onError(ex); + return; + } + } + + a.onNext(null); + + if (d) { + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + + missed = addAndGet(-missed); + if (missed == 0) { + return; + } + } + } + + void drainNormal() { + int missed = 1; + + final SpscLinkedArrayQueue> q = this.queue; + final Subscriber> a = this.downstream; + + for (;;) { + + long r = requested.get(); + long e = 0L; + + while (e != r) { + boolean d = finished; + + GroupedFlowable t = q.poll(); + + boolean empty = t == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + e++; + } + + if (e == r && checkTerminated(finished, q.isEmpty(), a, q)) { + return; + } + + if (e != 0L) { + if (r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + upstream.request(e); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, SpscLinkedArrayQueue q) { + if (cancelled.get()) { + q.clear(); + return true; + } + + if (delayError) { + if (d && empty) { + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return true; + } + } else { + if (d) { + Throwable ex = error; + if (ex != null) { + q.clear(); + a.onError(ex); + return true; + } else if (empty) { + a.onComplete(); + return true; + } + } + } + + return false; + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Nullable + @Override + public GroupedFlowable poll() { + return queue.poll(); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + } + + static final class EvictionAction implements Consumer> { + + final Queue> evictedGroups; + + EvictionAction(Queue> evictedGroups) { + this.evictedGroups = evictedGroups; + } + + @Override + public void accept(GroupedUnicast value) { + evictedGroups.offer(value); + } + } + + static final class GroupedUnicast extends GroupedFlowable { + + final State state; + + public static GroupedUnicast createWith(K key, int bufferSize, GroupBySubscriber parent, boolean delayError) { + State state = new State(bufferSize, parent, key, delayError); + return new GroupedUnicast(key, state); + } + + protected GroupedUnicast(K key, State state) { + super(key); + this.state = state; + } + + @Override + protected void subscribeActual(Subscriber s) { + state.subscribe(s); + } + + public void onNext(T t) { + state.onNext(t); + } + + public void onError(Throwable e) { + state.onError(e); + } + + public void onComplete() { + state.onComplete(); + } + } + + static final class State extends BasicIntQueueSubscription implements Publisher { + + private static final long serialVersionUID = -3852313036005250360L; + + final K key; + final SpscLinkedArrayQueue queue; + final GroupBySubscriber parent; + final boolean delayError; + + final AtomicLong requested = new AtomicLong(); + + volatile boolean done; + Throwable error; + + final AtomicBoolean cancelled = new AtomicBoolean(); + + final AtomicReference> actual = new AtomicReference>(); + + final AtomicBoolean once = new AtomicBoolean(); + + boolean outputFused; + + int produced; + + State(int bufferSize, GroupBySubscriber parent, K key, boolean delayError) { + this.queue = new SpscLinkedArrayQueue(bufferSize); + this.parent = parent; + this.key = key; + this.delayError = delayError; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (cancelled.compareAndSet(false, true)) { + parent.cancel(key); + drain(); + } + } + + @Override + public void subscribe(Subscriber s) { + if (once.compareAndSet(false, true)) { + s.onSubscribe(this); + actual.lazySet(s); + drain(); + } else { + EmptySubscription.error(new IllegalStateException("Only one Subscriber allowed!"), s); + } + } + + public void onNext(T t) { + queue.offer(t); + drain(); + } + + public void onError(Throwable e) { + error = e; + done = true; + drain(); + } + + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + if (outputFused) { + drainFused(); + } else { + drainNormal(); + } + } + + void drainFused() { + int missed = 1; + + final SpscLinkedArrayQueue q = this.queue; + Subscriber a = this.actual.get(); + + for (;;) { + if (a != null) { + if (cancelled.get()) { + return; + } + + boolean d = done; + + if (d && !delayError) { + Throwable ex = error; + if (ex != null) { + q.clear(); + a.onError(ex); + return; + } + } + + a.onNext(null); + + if (d) { + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + return; + } + + if (a == null) { + a = this.actual.get(); + } + } + } + + void drainNormal() { + int missed = 1; + + final SpscLinkedArrayQueue q = queue; + final boolean delayError = this.delayError; + Subscriber a = actual.get(); + for (;;) { + if (a != null) { + long r = requested.get(); + long e = 0; + + while (e != r) { + boolean d = done; + T v = q.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, delayError, e)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e == r && checkTerminated(done, q.isEmpty(), a, delayError, e)) { + return; + } + + if (e != 0L) { + if (r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + parent.upstream.request(e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + if (a == null) { + a = actual.get(); + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, boolean delayError, long emitted) { + if (cancelled.get()) { + // make sure buffered items can get replenished + while (queue.poll() != null) { + emitted++; + } + if (emitted != 0) { + parent.upstream.request(emitted); + } + return true; + } + + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + return true; + } + } else { + Throwable e = error; + if (e != null) { + queue.clear(); + a.onError(e); + return true; + } else + if (empty) { + a.onComplete(); + return true; + } + } + } + + return false; + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Nullable + @Override + public T poll() { + T v = queue.poll(); + if (v != null) { + produced++; + return v; + } + tryReplenish(); + return null; + } + + @Override + public boolean isEmpty() { + if (queue.isEmpty()) { + tryReplenish(); + return true; + } + return false; + } + + void tryReplenish() { + int p = produced; + if (p != 0) { + produced = 0; + parent.upstream.request(p); + } + } + + @Override + public void clear() { + // make sure buffered items can get replenished + SpscLinkedArrayQueue q = queue; + while (q.poll() != null) { + produced++; + } + tryReplenish(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java new file mode 100755 index 0000000..7cf3adb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupJoin.java @@ -0,0 +1,492 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimpleQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.UnicastProcessor; + +public final class FlowableGroupJoin extends AbstractFlowableWithUpstream { + + final Publisher other; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction, ? extends R> resultSelector; + + public FlowableGroupJoin( + Flowable source, + Publisher other, + Function> leftEnd, + Function> rightEnd, + BiFunction, ? extends R> resultSelector) { + super(source); + this.other = other; + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + } + + @Override + protected void subscribeActual(Subscriber s) { + + GroupJoinSubscription parent = + new GroupJoinSubscription(s, leftEnd, rightEnd, resultSelector); + + s.onSubscribe(parent); + + LeftRightSubscriber left = new LeftRightSubscriber(parent, true); + parent.disposables.add(left); + LeftRightSubscriber right = new LeftRightSubscriber(parent, false); + parent.disposables.add(right); + + source.subscribe(left); + other.subscribe(right); + } + + interface JoinSupport { + + void innerError(Throwable ex); + + void innerComplete(LeftRightSubscriber sender); + + void innerValue(boolean isLeft, Object o); + + void innerClose(boolean isLeft, LeftRightEndSubscriber index); + + void innerCloseError(Throwable ex); + } + + static final class GroupJoinSubscription + extends AtomicInteger implements Subscription, JoinSupport { + + private static final long serialVersionUID = -6071216598687999801L; + + final Subscriber downstream; + + final AtomicLong requested; + + final SpscLinkedArrayQueue queue; + + final CompositeDisposable disposables; + + final Map> lefts; + + final Map rights; + + final AtomicReference error; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction, ? extends R> resultSelector; + + final AtomicInteger active; + + int leftIndex; + + int rightIndex; + + volatile boolean cancelled; + + static final Integer LEFT_VALUE = 1; + + static final Integer RIGHT_VALUE = 2; + + static final Integer LEFT_CLOSE = 3; + + static final Integer RIGHT_CLOSE = 4; + + GroupJoinSubscription(Subscriber actual, Function> leftEnd, + Function> rightEnd, + BiFunction, ? extends R> resultSelector) { + this.downstream = actual; + this.requested = new AtomicLong(); + this.disposables = new CompositeDisposable(); + this.queue = new SpscLinkedArrayQueue(bufferSize()); + this.lefts = new LinkedHashMap>(); + this.rights = new LinkedHashMap(); + this.error = new AtomicReference(); + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + this.active = new AtomicInteger(2); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + cancelAll(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + + void cancelAll() { + disposables.dispose(); + } + + void errorAll(Subscriber a) { + Throwable ex = ExceptionHelper.terminate(error); + + for (UnicastProcessor up : lefts.values()) { + up.onError(ex); + } + + lefts.clear(); + rights.clear(); + + a.onError(ex); + } + + void fail(Throwable exc, Subscriber a, SimpleQueue q) { + Exceptions.throwIfFatal(exc); + ExceptionHelper.addThrowable(error, exc); + q.clear(); + cancelAll(); + errorAll(a); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + SpscLinkedArrayQueue q = queue; + Subscriber a = downstream; + + for (;;) { + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + boolean d = active.get() == 0; + + Integer mode = (Integer)q.poll(); + + boolean empty = mode == null; + + if (d && empty) { + for (UnicastProcessor up : lefts.values()) { + up.onComplete(); + } + + lefts.clear(); + rights.clear(); + disposables.dispose(); + + a.onComplete(); + return; + } + + if (empty) { + break; + } + + Object val = q.poll(); + + if (mode == LEFT_VALUE) { + @SuppressWarnings("unchecked") + TLeft left = (TLeft)val; + + UnicastProcessor up = UnicastProcessor.create(); + int idx = leftIndex++; + lefts.put(idx, up); + + Publisher p; + + try { + p = ObjectHelper.requireNonNull(leftEnd.apply(left), "The leftEnd returned a null Publisher"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + LeftRightEndSubscriber end = new LeftRightEndSubscriber(this, true, idx); + disposables.add(end); + + p.subscribe(end); + + ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + R w; + + try { + w = ObjectHelper.requireNonNull(resultSelector.apply(left, up), "The resultSelector returned a null value"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + // TODO since only left emission calls the actual, it is possible to link downstream backpressure with left's source and not error out + if (requested.get() != 0L) { + a.onNext(w); + BackpressureHelper.produced(requested, 1); + } else { + fail(new MissingBackpressureException("Could not emit value due to lack of requests"), a, q); + return; + } + + for (TRight right : rights.values()) { + up.onNext(right); + } + } + else if (mode == RIGHT_VALUE) { + @SuppressWarnings("unchecked") + TRight right = (TRight)val; + + int idx = rightIndex++; + + rights.put(idx, right); + + Publisher p; + + try { + p = ObjectHelper.requireNonNull(rightEnd.apply(right), "The rightEnd returned a null Publisher"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + LeftRightEndSubscriber end = new LeftRightEndSubscriber(this, false, idx); + disposables.add(end); + + p.subscribe(end); + + ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + for (UnicastProcessor up : lefts.values()) { + up.onNext(right); + } + } + else if (mode == LEFT_CLOSE) { + LeftRightEndSubscriber end = (LeftRightEndSubscriber)val; + + UnicastProcessor up = lefts.remove(end.index); + disposables.remove(end); + if (up != null) { + up.onComplete(); + } + } + else if (mode == RIGHT_CLOSE) { + LeftRightEndSubscriber end = (LeftRightEndSubscriber)val; + + rights.remove(end.index); + disposables.remove(end); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void innerError(Throwable ex) { + if (ExceptionHelper.addThrowable(error, ex)) { + active.decrementAndGet(); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void innerComplete(LeftRightSubscriber sender) { + disposables.delete(sender); + active.decrementAndGet(); + drain(); + } + + @Override + public void innerValue(boolean isLeft, Object o) { + synchronized (this) { + queue.offer(isLeft ? LEFT_VALUE : RIGHT_VALUE, o); + } + drain(); + } + + @Override + public void innerClose(boolean isLeft, LeftRightEndSubscriber index) { + synchronized (this) { + queue.offer(isLeft ? LEFT_CLOSE : RIGHT_CLOSE, index); + } + drain(); + } + + @Override + public void innerCloseError(Throwable ex) { + if (ExceptionHelper.addThrowable(error, ex)) { + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + } + + static final class LeftRightSubscriber + extends AtomicReference + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = 1883890389173668373L; + + final JoinSupport parent; + + final boolean isLeft; + + LeftRightSubscriber(JoinSupport parent, boolean isLeft) { + this.parent = parent; + this.isLeft = isLeft; + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + parent.innerValue(isLeft, t); + } + + @Override + public void onError(Throwable t) { + parent.innerError(t); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + } + + static final class LeftRightEndSubscriber + extends AtomicReference + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = 1883890389173668373L; + + final JoinSupport parent; + + final boolean isLeft; + + final int index; + + LeftRightEndSubscriber(JoinSupport parent, + boolean isLeft, int index) { + this.parent = parent; + this.isLeft = isLeft; + this.index = index; + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + if (SubscriptionHelper.cancel(this)) { + parent.innerClose(isLeft, this); + } + } + + @Override + public void onError(Throwable t) { + parent.innerCloseError(t); + } + + @Override + public void onComplete() { + parent.innerClose(isLeft, this); + } + + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableHide.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableHide.java new file mode 100755 index 0000000..0e5f7cc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableHide.java @@ -0,0 +1,81 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Hides the identity of the wrapped Flowable and its Subscription. + * @param the value type + * + * @since 2.0 + */ +public final class FlowableHide extends AbstractFlowableWithUpstream { + + public FlowableHide(Flowable source) { + super(source); + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new HideSubscriber(s)); + } + + static final class HideSubscriber implements FlowableSubscriber, Subscription { + + final Subscriber downstream; + + Subscription upstream; + + HideSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElements.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElements.java new file mode 100755 index 0000000..443baf3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElements.java @@ -0,0 +1,108 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +public final class FlowableIgnoreElements extends AbstractFlowableWithUpstream { + + public FlowableIgnoreElements(Flowable source) { + super(source); + } + + @Override + protected void subscribeActual(final Subscriber t) { + source.subscribe(new IgnoreElementsSubscriber(t)); + } + + static final class IgnoreElementsSubscriber implements FlowableSubscriber, QueueSubscription { + final Subscriber downstream; + + Subscription upstream; + + IgnoreElementsSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + // deliberately ignored + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public boolean offer(T e) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public boolean offer(T v1, T v2) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Nullable + @Override + public T poll() { + return null; // empty, always + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + // always empty + } + + @Override + public void request(long n) { + // never emits a value + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public int requestFusion(int mode) { + return mode & ASYNC; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsCompletable.java new file mode 100755 index 0000000..fc59ae9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIgnoreElementsCompletable.java @@ -0,0 +1,88 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableIgnoreElementsCompletable extends Completable implements FuseToFlowable { + + final Flowable source; + + public FlowableIgnoreElementsCompletable(Flowable source) { + this.source = source; + } + + @Override + protected void subscribeActual(final CompletableObserver t) { + source.subscribe(new IgnoreElementsSubscriber(t)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableIgnoreElements(source)); + } + + static final class IgnoreElementsSubscriber implements FlowableSubscriber, Disposable { + final CompletableObserver downstream; + + Subscription upstream; + + IgnoreElementsSubscriber(CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + // deliberately ignored + } + + @Override + public void onError(Throwable t) { + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = SubscriptionHelper.CANCELLED; + downstream.onComplete(); + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableInternalHelper.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableInternalHelper.java new file mode 100755 index 0000000..d516b02 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableInternalHelper.java @@ -0,0 +1,325 @@ +/** + * 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.internal.operators.flowable; + +import java.util.List; +import java.util.concurrent.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; + +/** + * Helper utility class to support Flowable with inner classes. + */ +public final class FlowableInternalHelper { + + /** Utility class. */ + private FlowableInternalHelper() { + throw new IllegalStateException("No instances!"); + } + + static final class SimpleGenerator implements BiFunction, S> { + final Consumer> consumer; + + SimpleGenerator(Consumer> consumer) { + this.consumer = consumer; + } + + @Override + public S apply(S t1, Emitter t2) throws Exception { + consumer.accept(t2); + return t1; + } + } + + public static BiFunction, S> simpleGenerator(Consumer> consumer) { + return new SimpleGenerator(consumer); + } + + static final class SimpleBiGenerator implements BiFunction, S> { + final BiConsumer> consumer; + + SimpleBiGenerator(BiConsumer> consumer) { + this.consumer = consumer; + } + + @Override + public S apply(S t1, Emitter t2) throws Exception { + consumer.accept(t1, t2); + return t1; + } + } + + public static BiFunction, S> simpleBiGenerator(BiConsumer> consumer) { + return new SimpleBiGenerator(consumer); + } + + static final class ItemDelayFunction implements Function> { + final Function> itemDelay; + + ItemDelayFunction(Function> itemDelay) { + this.itemDelay = itemDelay; + } + + @Override + public Publisher apply(final T v) throws Exception { + Publisher p = ObjectHelper.requireNonNull(itemDelay.apply(v), "The itemDelay returned a null Publisher"); + return new FlowableTakePublisher(p, 1).map(Functions.justFunction(v)).defaultIfEmpty(v); + } + } + + public static Function> itemDelay(final Function> itemDelay) { + return new ItemDelayFunction(itemDelay); + } + + static final class SubscriberOnNext implements Consumer { + final Subscriber subscriber; + + SubscriberOnNext(Subscriber subscriber) { + this.subscriber = subscriber; + } + + @Override + public void accept(T v) throws Exception { + subscriber.onNext(v); + } + } + + static final class SubscriberOnError implements Consumer { + final Subscriber subscriber; + + SubscriberOnError(Subscriber subscriber) { + this.subscriber = subscriber; + } + + @Override + public void accept(Throwable v) throws Exception { + subscriber.onError(v); + } + } + + static final class SubscriberOnComplete implements Action { + final Subscriber subscriber; + + SubscriberOnComplete(Subscriber subscriber) { + this.subscriber = subscriber; + } + + @Override + public void run() throws Exception { + subscriber.onComplete(); + } + } + + public static Consumer subscriberOnNext(Subscriber subscriber) { + return new SubscriberOnNext(subscriber); + } + + public static Consumer subscriberOnError(Subscriber subscriber) { + return new SubscriberOnError(subscriber); + } + + public static Action subscriberOnComplete(Subscriber subscriber) { + return new SubscriberOnComplete(subscriber); + } + + static final class FlatMapWithCombinerInner implements Function { + private final BiFunction combiner; + private final T t; + + FlatMapWithCombinerInner(BiFunction combiner, T t) { + this.combiner = combiner; + this.t = t; + } + + @Override + public R apply(U w) throws Exception { + return combiner.apply(t, w); + } + } + + static final class FlatMapWithCombinerOuter implements Function> { + private final BiFunction combiner; + private final Function> mapper; + + FlatMapWithCombinerOuter(BiFunction combiner, + Function> mapper) { + this.combiner = combiner; + this.mapper = mapper; + } + + @Override + public Publisher apply(final T t) throws Exception { + @SuppressWarnings("unchecked") + Publisher u = (Publisher)ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + return new FlowableMapPublisher(u, new FlatMapWithCombinerInner(combiner, t)); + } + } + + public static Function> flatMapWithCombiner( + final Function> mapper, + final BiFunction combiner) { + return new FlatMapWithCombinerOuter(combiner, mapper); + } + + static final class FlatMapIntoIterable implements Function> { + private final Function> mapper; + + FlatMapIntoIterable(Function> mapper) { + this.mapper = mapper; + } + + @Override + public Publisher apply(T t) throws Exception { + return new FlowableFromIterable(ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Iterable")); + } + } + + public static Function> flatMapIntoIterable(final Function> mapper) { + return new FlatMapIntoIterable(mapper); + } + + public static Callable> replayCallable(final Flowable parent) { + return new ReplayCallable(parent); + } + + public static Callable> replayCallable(final Flowable parent, final int bufferSize) { + return new BufferedReplayCallable(parent, bufferSize); + } + + public static Callable> replayCallable(final Flowable parent, final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { + return new BufferedTimedReplay(parent, bufferSize, time, unit, scheduler); + } + + public static Callable> replayCallable(final Flowable parent, final long time, final TimeUnit unit, final Scheduler scheduler) { + return new TimedReplay(parent, time, unit, scheduler); + } + + public static Function, Publisher> replayFunction(final Function, ? extends Publisher> selector, final Scheduler scheduler) { + return new ReplayFunction(selector, scheduler); + } + + public enum RequestMax implements Consumer { + INSTANCE; + @Override + public void accept(Subscription t) throws Exception { + t.request(Long.MAX_VALUE); + } + } + + static final class ZipIterableFunction + implements Function>, Publisher> { + private final Function zipper; + + ZipIterableFunction(Function zipper) { + this.zipper = zipper; + } + + @Override + public Publisher apply(List> list) { + return Flowable.zipIterable(list, zipper, false, Flowable.bufferSize()); + } + } + + public static Function>, Publisher> zipIterable(final Function zipper) { + return new ZipIterableFunction(zipper); + } + + static final class ReplayCallable implements Callable> { + private final Flowable parent; + + ReplayCallable(Flowable parent) { + this.parent = parent; + } + + @Override + public ConnectableFlowable call() { + return parent.replay(); + } + } + + static final class BufferedReplayCallable implements Callable> { + private final Flowable parent; + private final int bufferSize; + + BufferedReplayCallable(Flowable parent, int bufferSize) { + this.parent = parent; + this.bufferSize = bufferSize; + } + + @Override + public ConnectableFlowable call() { + return parent.replay(bufferSize); + } + } + + static final class BufferedTimedReplay implements Callable> { + private final Flowable parent; + private final int bufferSize; + private final long time; + private final TimeUnit unit; + private final Scheduler scheduler; + + BufferedTimedReplay(Flowable parent, int bufferSize, long time, TimeUnit unit, Scheduler scheduler) { + this.parent = parent; + this.bufferSize = bufferSize; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public ConnectableFlowable call() { + return parent.replay(bufferSize, time, unit, scheduler); + } + } + + static final class TimedReplay implements Callable> { + private final Flowable parent; + private final long time; + private final TimeUnit unit; + private final Scheduler scheduler; + + TimedReplay(Flowable parent, long time, TimeUnit unit, Scheduler scheduler) { + this.parent = parent; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public ConnectableFlowable call() { + return parent.replay(time, unit, scheduler); + } + } + + static final class ReplayFunction implements Function, Publisher> { + private final Function, ? extends Publisher> selector; + private final Scheduler scheduler; + + ReplayFunction(Function, ? extends Publisher> selector, Scheduler scheduler) { + this.selector = selector; + this.scheduler = scheduler; + } + + @Override + public Publisher apply(Flowable t) throws Exception { + Publisher p = ObjectHelper.requireNonNull(selector.apply(t), "The selector returned a null Publisher"); + return Flowable.fromPublisher(p).observeOn(scheduler); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableInterval.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableInterval.java new file mode 100755 index 0000000..35a0937 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableInterval.java @@ -0,0 +1,106 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.schedulers.TrampolineScheduler; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +public final class FlowableInterval extends Flowable { + final Scheduler scheduler; + final long initialDelay; + final long period; + final TimeUnit unit; + + public FlowableInterval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + this.initialDelay = initialDelay; + this.period = period; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void subscribeActual(Subscriber s) { + IntervalSubscriber is = new IntervalSubscriber(s); + s.onSubscribe(is); + + Scheduler sch = scheduler; + + if (sch instanceof TrampolineScheduler) { + Worker worker = sch.createWorker(); + is.setResource(worker); + worker.schedulePeriodically(is, initialDelay, period, unit); + } else { + Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit); + is.setResource(d); + } + } + + static final class IntervalSubscriber extends AtomicLong + implements Subscription, Runnable { + + private static final long serialVersionUID = -2809475196591179431L; + + final Subscriber downstream; + + long count; + + final AtomicReference resource = new AtomicReference(); + + IntervalSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(this, n); + } + } + + @Override + public void cancel() { + DisposableHelper.dispose(resource); + } + + @Override + public void run() { + if (resource.get() != DisposableHelper.DISPOSED) { + long r = get(); + + if (r != 0L) { + downstream.onNext(count++); + BackpressureHelper.produced(this, 1); + } else { + downstream.onError(new MissingBackpressureException("Can't deliver value " + count + " due to lack of requests")); + DisposableHelper.dispose(resource); + } + } + } + + public void setResource(Disposable d) { + DisposableHelper.setOnce(resource, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableIntervalRange.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIntervalRange.java new file mode 100755 index 0000000..7394608 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableIntervalRange.java @@ -0,0 +1,127 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.schedulers.TrampolineScheduler; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +public final class FlowableIntervalRange extends Flowable { + final Scheduler scheduler; + final long start; + final long end; + final long initialDelay; + final long period; + final TimeUnit unit; + + public FlowableIntervalRange(long start, long end, long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + this.initialDelay = initialDelay; + this.period = period; + this.unit = unit; + this.scheduler = scheduler; + this.start = start; + this.end = end; + } + + @Override + public void subscribeActual(Subscriber s) { + IntervalRangeSubscriber is = new IntervalRangeSubscriber(s, start, end); + s.onSubscribe(is); + + Scheduler sch = scheduler; + + if (sch instanceof TrampolineScheduler) { + Worker worker = sch.createWorker(); + is.setResource(worker); + worker.schedulePeriodically(is, initialDelay, period, unit); + } else { + Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit); + is.setResource(d); + } + } + + static final class IntervalRangeSubscriber extends AtomicLong + implements Subscription, Runnable { + + private static final long serialVersionUID = -2809475196591179431L; + + final Subscriber downstream; + final long end; + + long count; + + final AtomicReference resource = new AtomicReference(); + + IntervalRangeSubscriber(Subscriber actual, long start, long end) { + this.downstream = actual; + this.count = start; + this.end = end; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(this, n); + } + } + + @Override + public void cancel() { + DisposableHelper.dispose(resource); + } + + @Override + public void run() { + if (resource.get() != DisposableHelper.DISPOSED) { + long r = get(); + + if (r != 0L) { + long c = count; + downstream.onNext(c); + + if (c == end) { + if (resource.get() != DisposableHelper.DISPOSED) { + downstream.onComplete(); + } + DisposableHelper.dispose(resource); + return; + } + + count = c + 1; + + if (r != Long.MAX_VALUE) { + decrementAndGet(); + } + } else { + downstream.onError(new MissingBackpressureException("Can't deliver value " + count + " due to lack of requests")); + DisposableHelper.dispose(resource); + } + } + } + + public void setResource(Disposable d) { + DisposableHelper.setOnce(resource, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java new file mode 100755 index 0000000..2bd33b6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableJoin.java @@ -0,0 +1,400 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimpleQueue; +import io.reactivex.internal.operators.flowable.FlowableGroupJoin.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableJoin extends AbstractFlowableWithUpstream { + + final Publisher other; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction resultSelector; + + public FlowableJoin( + Flowable source, + Publisher other, + Function> leftEnd, + Function> rightEnd, + BiFunction resultSelector) { + super(source); + this.other = other; + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + } + + @Override + protected void subscribeActual(Subscriber s) { + + JoinSubscription parent = + new JoinSubscription(s, leftEnd, rightEnd, resultSelector); + + s.onSubscribe(parent); + + LeftRightSubscriber left = new LeftRightSubscriber(parent, true); + parent.disposables.add(left); + LeftRightSubscriber right = new LeftRightSubscriber(parent, false); + parent.disposables.add(right); + + source.subscribe(left); + other.subscribe(right); + } + + static final class JoinSubscription + extends AtomicInteger implements Subscription, JoinSupport { + + private static final long serialVersionUID = -6071216598687999801L; + + final Subscriber downstream; + + final AtomicLong requested; + + final SpscLinkedArrayQueue queue; + + final CompositeDisposable disposables; + + final Map lefts; + + final Map rights; + + final AtomicReference error; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction resultSelector; + + final AtomicInteger active; + + int leftIndex; + + int rightIndex; + + volatile boolean cancelled; + + static final Integer LEFT_VALUE = 1; + + static final Integer RIGHT_VALUE = 2; + + static final Integer LEFT_CLOSE = 3; + + static final Integer RIGHT_CLOSE = 4; + + JoinSubscription(Subscriber actual, Function> leftEnd, + Function> rightEnd, + BiFunction resultSelector) { + this.downstream = actual; + this.requested = new AtomicLong(); + this.disposables = new CompositeDisposable(); + this.queue = new SpscLinkedArrayQueue(bufferSize()); + this.lefts = new LinkedHashMap(); + this.rights = new LinkedHashMap(); + this.error = new AtomicReference(); + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + this.active = new AtomicInteger(2); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + cancelAll(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + + void cancelAll() { + disposables.dispose(); + } + + void errorAll(Subscriber a) { + Throwable ex = ExceptionHelper.terminate(error); + + lefts.clear(); + rights.clear(); + + a.onError(ex); + } + + void fail(Throwable exc, Subscriber a, SimpleQueue q) { + Exceptions.throwIfFatal(exc); + ExceptionHelper.addThrowable(error, exc); + q.clear(); + cancelAll(); + errorAll(a); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + SpscLinkedArrayQueue q = queue; + Subscriber a = downstream; + + for (;;) { + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + boolean d = active.get() == 0; + + Integer mode = (Integer)q.poll(); + + boolean empty = mode == null; + + if (d && empty) { + + lefts.clear(); + rights.clear(); + disposables.dispose(); + + a.onComplete(); + return; + } + + if (empty) { + break; + } + + Object val = q.poll(); + + if (mode == LEFT_VALUE) { + @SuppressWarnings("unchecked") + TLeft left = (TLeft)val; + + int idx = leftIndex++; + lefts.put(idx, left); + + Publisher p; + + try { + p = ObjectHelper.requireNonNull(leftEnd.apply(left), "The leftEnd returned a null Publisher"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + LeftRightEndSubscriber end = new LeftRightEndSubscriber(this, true, idx); + disposables.add(end); + + p.subscribe(end); + + ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + long r = requested.get(); + long e = 0L; + + for (TRight right : rights.values()) { + + R w; + + try { + w = ObjectHelper.requireNonNull(resultSelector.apply(left, right), "The resultSelector returned a null value"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + if (e != r) { + a.onNext(w); + + e++; + } else { + ExceptionHelper.addThrowable(error, new MissingBackpressureException("Could not emit value due to lack of requests")); + q.clear(); + cancelAll(); + errorAll(a); + return; + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + } + } + else if (mode == RIGHT_VALUE) { + @SuppressWarnings("unchecked") + TRight right = (TRight)val; + + int idx = rightIndex++; + + rights.put(idx, right); + + Publisher p; + + try { + p = ObjectHelper.requireNonNull(rightEnd.apply(right), "The rightEnd returned a null Publisher"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + LeftRightEndSubscriber end = new LeftRightEndSubscriber(this, false, idx); + disposables.add(end); + + p.subscribe(end); + + ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + long r = requested.get(); + long e = 0L; + + for (TLeft left : lefts.values()) { + + R w; + + try { + w = ObjectHelper.requireNonNull(resultSelector.apply(left, right), "The resultSelector returned a null value"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + if (e != r) { + a.onNext(w); + + e++; + } else { + ExceptionHelper.addThrowable(error, new MissingBackpressureException("Could not emit value due to lack of requests")); + q.clear(); + cancelAll(); + errorAll(a); + return; + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + } + } + else if (mode == LEFT_CLOSE) { + LeftRightEndSubscriber end = (LeftRightEndSubscriber)val; + + lefts.remove(end.index); + disposables.remove(end); + } + else if (mode == RIGHT_CLOSE) { + LeftRightEndSubscriber end = (LeftRightEndSubscriber)val; + + rights.remove(end.index); + disposables.remove(end); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void innerError(Throwable ex) { + if (ExceptionHelper.addThrowable(error, ex)) { + active.decrementAndGet(); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void innerComplete(LeftRightSubscriber sender) { + disposables.delete(sender); + active.decrementAndGet(); + drain(); + } + + @Override + public void innerValue(boolean isLeft, Object o) { + synchronized (this) { + queue.offer(isLeft ? LEFT_VALUE : RIGHT_VALUE, o); + } + drain(); + } + + @Override + public void innerClose(boolean isLeft, LeftRightEndSubscriber index) { + synchronized (this) { + queue.offer(isLeft ? LEFT_CLOSE : RIGHT_CLOSE, index); + } + drain(); + } + + @Override + public void innerCloseError(Throwable ex) { + if (ExceptionHelper.addThrowable(error, ex)) { + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableJust.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableJust.java new file mode 100755 index 0000000..5a07771 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableJust.java @@ -0,0 +1,41 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.internal.fuseable.ScalarCallable; +import io.reactivex.internal.subscriptions.ScalarSubscription; + +/** + * Represents a constant scalar value. + * @param the value type + */ +public final class FlowableJust extends Flowable implements ScalarCallable { + private final T value; + public FlowableJust(final T value) { + this.value = value; + } + + @Override + protected void subscribeActual(Subscriber s) { + s.onSubscribe(new ScalarSubscription(s, value)); + } + + @Override + public T call() { + return value; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastMaybe.java new file mode 100755 index 0000000..e27efeb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastMaybe.java @@ -0,0 +1,100 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Consumes the source Publisher and emits its last item or completes. + * + * @param the value type + */ +public final class FlowableLastMaybe extends Maybe { + + final Publisher source; + + public FlowableLastMaybe(Publisher source) { + this.source = source; + } + + // TODO fuse back to Flowable + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new LastSubscriber(observer)); + } + + static final class LastSubscriber implements FlowableSubscriber, Disposable { + + final MaybeObserver downstream; + + Subscription upstream; + + T item; + + LastSubscriber(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + item = t; + } + + @Override + public void onError(Throwable t) { + upstream = SubscriptionHelper.CANCELLED; + item = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = SubscriptionHelper.CANCELLED; + T v = item; + if (v != null) { + item = null; + downstream.onSuccess(v); + } else { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastSingle.java new file mode 100755 index 0000000..344c6f3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLastSingle.java @@ -0,0 +1,115 @@ +/** + * 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.internal.operators.flowable; + +import java.util.NoSuchElementException; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Consumes the source Publisher and emits its last item or the defaultItem + * if empty. + * + * @param the value type + */ +public final class FlowableLastSingle extends Single { + + final Publisher source; + + final T defaultItem; + + public FlowableLastSingle(Publisher source, T defaultItem) { + this.source = source; + this.defaultItem = defaultItem; + } + + // TODO fuse back to Flowable + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new LastSubscriber(observer, defaultItem)); + } + + static final class LastSubscriber implements FlowableSubscriber, Disposable { + + final SingleObserver downstream; + + final T defaultItem; + + Subscription upstream; + + T item; + + LastSubscriber(SingleObserver actual, T defaultItem) { + this.downstream = actual; + this.defaultItem = defaultItem; + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + item = t; + } + + @Override + public void onError(Throwable t) { + upstream = SubscriptionHelper.CANCELLED; + item = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = SubscriptionHelper.CANCELLED; + T v = item; + if (v != null) { + item = null; + downstream.onSuccess(v); + } else { + v = defaultItem; + + if (v != null) { + downstream.onSuccess(v); + } else { + downstream.onError(new NoSuchElementException()); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableLift.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLift.java new file mode 100755 index 0000000..5828b3d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLift.java @@ -0,0 +1,63 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Allows lifting operators into a chain of Publishers. + * + *

By having a concrete Publisher as lift, operator fusing can now identify + * both the source and the operation inside it via casting, unlike the lambda version of this. + * + * @param the upstream value type + * @param the downstream parameter type + */ +public final class FlowableLift extends AbstractFlowableWithUpstream { + /** The actual operator. */ + final FlowableOperator operator; + + public FlowableLift(Flowable source, FlowableOperator operator) { + super(source); + this.operator = operator; + } + + @Override + public void subscribeActual(Subscriber s) { + try { + Subscriber st = operator.apply(s); + + if (st == null) { + throw new NullPointerException("Operator " + operator + " returned a null Subscriber"); + } + + source.subscribe(st); + } catch (NullPointerException e) { // NOPMD + throw e; + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because no way to know if a Subscription has been set or not + // can't call onSubscribe because the call might have set a Subscription already + RxJavaPlugins.onError(e); + + NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS"); + npe.initCause(e); + throw npe; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableLimit.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLimit.java new file mode 100755 index 0000000..58004d0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableLimit.java @@ -0,0 +1,135 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Limits both the total request amount and items received from the upstream. + *

History: 2.1.6 - experimental + * @param the source and output value type + * @since 2.2 + */ +public final class FlowableLimit extends AbstractFlowableWithUpstream { + + final long n; + + public FlowableLimit(Flowable source, long n) { + super(source); + this.n = n; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new LimitSubscriber(s, n)); + } + + static final class LimitSubscriber + extends AtomicLong + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 2288246011222124525L; + + final Subscriber downstream; + + long remaining; + + Subscription upstream; + + LimitSubscriber(Subscriber actual, long remaining) { + this.downstream = actual; + this.remaining = remaining; + lazySet(remaining); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + if (remaining == 0L) { + s.cancel(); + EmptySubscription.complete(downstream); + } else { + this.upstream = s; + downstream.onSubscribe(this); + } + } + } + + @Override + public void onNext(T t) { + long r = remaining; + if (r > 0L) { + remaining = --r; + downstream.onNext(t); + if (r == 0L) { + upstream.cancel(); + downstream.onComplete(); + } + } + } + + @Override + public void onError(Throwable t) { + if (remaining > 0L) { + remaining = 0L; + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (remaining > 0L) { + remaining = 0L; + downstream.onComplete(); + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + for (;;) { + long r = get(); + if (r == 0L) { + break; + } + long toRequest; + if (r <= n) { + toRequest = r; + } else { + toRequest = n; + } + long u = r - toRequest; + if (compareAndSet(r, u)) { + upstream.request(toRequest); + break; + } + } + } + } + + @Override + public void cancel() { + upstream.cancel(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java new file mode 100755 index 0000000..7b6632d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMap.java @@ -0,0 +1,144 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscribers.*; + +public final class FlowableMap extends AbstractFlowableWithUpstream { + final Function mapper; + public FlowableMap(Flowable source, Function mapper) { + super(source); + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (s instanceof ConditionalSubscriber) { + source.subscribe(new MapConditionalSubscriber((ConditionalSubscriber)s, mapper)); + } else { + source.subscribe(new MapSubscriber(s, mapper)); + } + } + + static final class MapSubscriber extends BasicFuseableSubscriber { + final Function mapper; + + MapSubscriber(Subscriber actual, Function mapper) { + super(actual); + this.mapper = mapper; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + if (sourceMode != NONE) { + downstream.onNext(null); + return; + } + + U v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value."); + } catch (Throwable ex) { + fail(ex); + return; + } + downstream.onNext(v); + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public U poll() throws Exception { + T t = qs.poll(); + return t != null ? ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value.") : null; + } + } + + static final class MapConditionalSubscriber extends BasicFuseableConditionalSubscriber { + final Function mapper; + + MapConditionalSubscriber(ConditionalSubscriber actual, Function function) { + super(actual); + this.mapper = function; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + if (sourceMode != NONE) { + downstream.onNext(null); + return; + } + + U v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value."); + } catch (Throwable ex) { + fail(ex); + return; + } + downstream.onNext(v); + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + + U v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value."); + } catch (Throwable ex) { + fail(ex); + return true; + } + return downstream.tryOnNext(v); + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public U poll() throws Exception { + T t = qs.poll(); + return t != null ? ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value.") : null; + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapNotification.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapNotification.java new file mode 100755 index 0000000..4b90d60 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapNotification.java @@ -0,0 +1,112 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscribers.SinglePostCompleteSubscriber; + +public final class FlowableMapNotification extends AbstractFlowableWithUpstream { + + final Function onNextMapper; + final Function onErrorMapper; + final Callable onCompleteSupplier; + + public FlowableMapNotification( + Flowable source, + Function onNextMapper, + Function onErrorMapper, + Callable onCompleteSupplier) { + super(source); + this.onNextMapper = onNextMapper; + this.onErrorMapper = onErrorMapper; + this.onCompleteSupplier = onCompleteSupplier; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new MapNotificationSubscriber(s, onNextMapper, onErrorMapper, onCompleteSupplier)); + } + + static final class MapNotificationSubscriber + extends SinglePostCompleteSubscriber { + + private static final long serialVersionUID = 2757120512858778108L; + final Function onNextMapper; + final Function onErrorMapper; + final Callable onCompleteSupplier; + + MapNotificationSubscriber(Subscriber actual, + Function onNextMapper, + Function onErrorMapper, + Callable onCompleteSupplier) { + super(actual); + this.onNextMapper = onNextMapper; + this.onErrorMapper = onErrorMapper; + this.onCompleteSupplier = onCompleteSupplier; + } + + @Override + public void onNext(T t) { + R p; + + try { + p = ObjectHelper.requireNonNull(onNextMapper.apply(t), "The onNext publisher returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + + produced++; + downstream.onNext(p); + } + + @Override + public void onError(Throwable t) { + R p; + + try { + p = ObjectHelper.requireNonNull(onErrorMapper.apply(t), "The onError publisher returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + return; + } + + complete(p); + } + + @Override + public void onComplete() { + R p; + + try { + p = ObjectHelper.requireNonNull(onCompleteSupplier.call(), "The onComplete publisher returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + + complete(p); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java new file mode 100755 index 0000000..3378af4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMapPublisher.java @@ -0,0 +1,43 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.functions.Function; +import io.reactivex.internal.operators.flowable.FlowableMap.MapSubscriber; + +/** + * Map working with an arbitrary Publisher source. + *

History: 2.0.7 - experimental + * @param the input value type + * @param the output value type + * @since 2.1 + */ +public final class FlowableMapPublisher extends Flowable { + + final Publisher source; + + final Function mapper; + public FlowableMapPublisher(Publisher source, Function mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new MapSubscriber(s, mapper)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMaterialize.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMaterialize.java new file mode 100755 index 0000000..91f5810 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMaterialize.java @@ -0,0 +1,64 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.internal.subscribers.SinglePostCompleteSubscriber; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableMaterialize extends AbstractFlowableWithUpstream> { + + public FlowableMaterialize(Flowable source) { + super(source); + } + + @Override + protected void subscribeActual(Subscriber> s) { + source.subscribe(new MaterializeSubscriber(s)); + } + + static final class MaterializeSubscriber extends SinglePostCompleteSubscriber> { + + private static final long serialVersionUID = -3740826063558713822L; + + MaterializeSubscriber(Subscriber> downstream) { + super(downstream); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(Notification.createOnNext(t)); + } + + @Override + public void onError(Throwable t) { + complete(Notification.createOnError(t)); + } + + @Override + public void onComplete() { + complete(Notification.createOnComplete()); + } + + @Override + protected void onDrop(Notification n) { + if (n.isOnError()) { + RxJavaPlugins.onError(n.getError()); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java new file mode 100755 index 0000000..c65386b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java @@ -0,0 +1,151 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; + +/** + * Merges a Flowable and a Completable by emitting the items of the Flowable and waiting until + * both the Flowable and Completable complete normally. + *

History: 2.1.10 - experimental + * @param the element type of the Flowable + * @since 2.2 + */ +public final class FlowableMergeWithCompletable extends AbstractFlowableWithUpstream { + + final CompletableSource other; + + public FlowableMergeWithCompletable(Flowable source, CompletableSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + MergeWithSubscriber parent = new MergeWithSubscriber(subscriber); + subscriber.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithSubscriber extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -4592979584110982903L; + + final Subscriber downstream; + + final AtomicReference mainSubscription; + + final OtherObserver otherObserver; + + final AtomicThrowable error; + + final AtomicLong requested; + + volatile boolean mainDone; + + volatile boolean otherDone; + + MergeWithSubscriber(Subscriber downstream) { + this.downstream = downstream; + this.mainSubscription = new AtomicReference(); + this.otherObserver = new OtherObserver(this); + this.error = new AtomicThrowable(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(mainSubscription, requested, s); + } + + @Override + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); + } + + @Override + public void onError(Throwable ex) { + DisposableHelper.dispose(otherObserver); + HalfSerializer.onError(downstream, ex, this, error); + } + + @Override + public void onComplete() { + mainDone = true; + if (otherDone) { + HalfSerializer.onComplete(downstream, this, error); + } + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(mainSubscription, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(mainSubscription); + DisposableHelper.dispose(otherObserver); + } + + void otherError(Throwable ex) { + SubscriptionHelper.cancel(mainSubscription); + HalfSerializer.onError(downstream, ex, this, error); + } + + void otherComplete() { + otherDone = true; + if (mainDone) { + HalfSerializer.onComplete(downstream, this, error); + } + } + + static final class OtherObserver extends AtomicReference + implements CompletableObserver { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithSubscriber parent; + + OtherObserver(MergeWithSubscriber parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java new file mode 100755 index 0000000..1787d5f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java @@ -0,0 +1,357 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Merges an Observable and a Maybe by emitting the items of the Observable and the success + * value of the Maybe and waiting until both the Observable and Maybe terminate normally. + *

History: 2.1.10 - experimental + * @param the element type of the Observable + * @since 2.2 + */ +public final class FlowableMergeWithMaybe extends AbstractFlowableWithUpstream { + + final MaybeSource other; + + public FlowableMergeWithMaybe(Flowable source, MaybeSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + MergeWithObserver parent = new MergeWithObserver(subscriber); + subscriber.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -4592979584110982903L; + + final Subscriber downstream; + + final AtomicReference mainSubscription; + + final OtherObserver otherObserver; + + final AtomicThrowable error; + + final AtomicLong requested; + + final int prefetch; + + final int limit; + + volatile SimplePlainQueue queue; + + T singleItem; + + volatile boolean cancelled; + + volatile boolean mainDone; + + volatile int otherState; + + long emitted; + + int consumed; + + static final int OTHER_STATE_HAS_VALUE = 1; + + static final int OTHER_STATE_CONSUMED_OR_EMPTY = 2; + + MergeWithObserver(Subscriber downstream) { + this.downstream = downstream; + this.mainSubscription = new AtomicReference(); + this.otherObserver = new OtherObserver(this); + this.error = new AtomicThrowable(); + this.requested = new AtomicLong(); + this.prefetch = bufferSize(); + this.limit = prefetch - (prefetch >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(mainSubscription, s, prefetch); + } + + @Override + public void onNext(T t) { + if (compareAndSet(0, 1)) { + long e = emitted; + if (requested.get() != e) { + SimplePlainQueue q = queue; + if (q == null || q.isEmpty()) { + + emitted = e + 1; + downstream.onNext(t); + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + mainSubscription.get().request(c); + } else { + consumed = c; + } + } else { + q.offer(t); + } + } else { + SimplePlainQueue q = getOrCreateQueue(); + q.offer(t); + } + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue q = getOrCreateQueue(); + q.offer(t); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(otherObserver); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onComplete() { + mainDone = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + SubscriptionHelper.cancel(mainSubscription); + DisposableHelper.dispose(otherObserver); + if (getAndIncrement() == 0) { + queue = null; + singleItem = null; + } + } + + void otherSuccess(T value) { + if (compareAndSet(0, 1)) { + long e = emitted; + if (requested.get() != e) { + + emitted = e + 1; + downstream.onNext(value); + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (decrementAndGet() == 0) { + return; + } + } + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + void otherError(Throwable ex) { + if (error.addThrowable(ex)) { + SubscriptionHelper.cancel(mainSubscription); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void otherComplete() { + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + drain(); + } + + SimplePlainQueue getOrCreateQueue() { + SimplePlainQueue q = queue; + if (q == null) { + q = new SpscArrayQueue(bufferSize()); + queue = q; + } + return q; + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + Subscriber actual = this.downstream; + int missed = 1; + long e = emitted; + int c = consumed; + int lim = limit; + for (;;) { + + long r = requested.get(); + + while (e != r) { + if (cancelled) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + int os = otherState; + if (os == OTHER_STATE_HAS_VALUE) { + T v = singleItem; + singleItem = null; + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + os = OTHER_STATE_CONSUMED_OR_EMPTY; + actual.onNext(v); + + e++; + continue; + } + + boolean d = mainDone; + SimplePlainQueue q = queue; + T v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty && os == OTHER_STATE_CONSUMED_OR_EMPTY) { + queue = null; + actual.onComplete(); + return; + } + + if (empty) { + break; + } + + actual.onNext(v); + + e++; + + if (++c == lim) { + c = 0; + mainSubscription.get().request(lim); + } + } + + if (e == r) { + if (cancelled) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + boolean d = mainDone; + SimplePlainQueue q = queue; + boolean empty = q == null || q.isEmpty(); + + if (d && empty && otherState == 2) { + queue = null; + actual.onComplete(); + return; + } + } + + emitted = e; + consumed = c; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class OtherObserver extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver parent; + + OtherObserver(MergeWithObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + parent.otherSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java new file mode 100755 index 0000000..486cb73 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java @@ -0,0 +1,347 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Merges an Observable and a Maybe by emitting the items of the Observable and the success + * value of the Maybe and waiting until both the Observable and Maybe terminate normally. + *

History: 2.1.10 - experimental + * @param the element type of the Observable + * @since 2.2 + */ +public final class FlowableMergeWithSingle extends AbstractFlowableWithUpstream { + + final SingleSource other; + + public FlowableMergeWithSingle(Flowable source, SingleSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + MergeWithObserver parent = new MergeWithObserver(subscriber); + subscriber.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -4592979584110982903L; + + final Subscriber downstream; + + final AtomicReference mainSubscription; + + final OtherObserver otherObserver; + + final AtomicThrowable error; + + final AtomicLong requested; + + final int prefetch; + + final int limit; + + volatile SimplePlainQueue queue; + + T singleItem; + + volatile boolean cancelled; + + volatile boolean mainDone; + + volatile int otherState; + + long emitted; + + int consumed; + + static final int OTHER_STATE_HAS_VALUE = 1; + + static final int OTHER_STATE_CONSUMED_OR_EMPTY = 2; + + MergeWithObserver(Subscriber downstream) { + this.downstream = downstream; + this.mainSubscription = new AtomicReference(); + this.otherObserver = new OtherObserver(this); + this.error = new AtomicThrowable(); + this.requested = new AtomicLong(); + this.prefetch = bufferSize(); + this.limit = prefetch - (prefetch >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(mainSubscription, s, prefetch); + } + + @Override + public void onNext(T t) { + if (compareAndSet(0, 1)) { + long e = emitted; + if (requested.get() != e) { + SimplePlainQueue q = queue; + if (q == null || q.isEmpty()) { + + emitted = e + 1; + downstream.onNext(t); + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + mainSubscription.get().request(c); + } else { + consumed = c; + } + } else { + q.offer(t); + } + } else { + SimplePlainQueue q = getOrCreateQueue(); + q.offer(t); + } + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue q = getOrCreateQueue(); + q.offer(t); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(otherObserver); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onComplete() { + mainDone = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + SubscriptionHelper.cancel(mainSubscription); + DisposableHelper.dispose(otherObserver); + if (getAndIncrement() == 0) { + queue = null; + singleItem = null; + } + } + + void otherSuccess(T value) { + if (compareAndSet(0, 1)) { + long e = emitted; + if (requested.get() != e) { + + emitted = e + 1; + downstream.onNext(value); + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (decrementAndGet() == 0) { + return; + } + } + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + void otherError(Throwable ex) { + if (error.addThrowable(ex)) { + SubscriptionHelper.cancel(mainSubscription); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + SimplePlainQueue getOrCreateQueue() { + SimplePlainQueue q = queue; + if (q == null) { + q = new SpscArrayQueue(bufferSize()); + queue = q; + } + return q; + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + Subscriber actual = this.downstream; + int missed = 1; + long e = emitted; + int c = consumed; + int lim = limit; + for (;;) { + + long r = requested.get(); + + while (e != r) { + if (cancelled) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + int os = otherState; + if (os == OTHER_STATE_HAS_VALUE) { + T v = singleItem; + singleItem = null; + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + os = OTHER_STATE_CONSUMED_OR_EMPTY; + actual.onNext(v); + + e++; + continue; + } + + boolean d = mainDone; + SimplePlainQueue q = queue; + T v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty && os == OTHER_STATE_CONSUMED_OR_EMPTY) { + queue = null; + actual.onComplete(); + return; + } + + if (empty) { + break; + } + + actual.onNext(v); + + e++; + + if (++c == lim) { + c = 0; + mainSubscription.get().request(lim); + } + } + + if (e == r) { + if (cancelled) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + boolean d = mainDone; + SimplePlainQueue q = queue; + boolean empty = q == null || q.isEmpty(); + + if (d && empty && otherState == 2) { + queue = null; + actual.onComplete(); + return; + } + } + + emitted = e; + consumed = c; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class OtherObserver extends AtomicReference + implements SingleObserver { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver parent; + + OtherObserver(MergeWithObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + parent.otherSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableNever.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableNever.java new file mode 100755 index 0000000..2565fb3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableNever.java @@ -0,0 +1,30 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.internal.subscriptions.EmptySubscription; + +public final class FlowableNever extends Flowable { + public static final Flowable INSTANCE = new FlowableNever(); + + private FlowableNever() { + } + + @Override + public void subscribeActual(Subscriber s) { + s.onSubscribe(EmptySubscription.INSTANCE); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java new file mode 100755 index 0000000..3431f3a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java @@ -0,0 +1,729 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.*; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableObserveOn extends AbstractFlowableWithUpstream { +final Scheduler scheduler; + + final boolean delayError; + + final int prefetch; + + public FlowableObserveOn( + Flowable source, + Scheduler scheduler, + boolean delayError, + int prefetch) { + super(source); + this.scheduler = scheduler; + this.delayError = delayError; + this.prefetch = prefetch; + } + + @Override + public void subscribeActual(Subscriber s) { + Worker worker = scheduler.createWorker(); + + if (s instanceof ConditionalSubscriber) { + source.subscribe(new ObserveOnConditionalSubscriber( + (ConditionalSubscriber) s, worker, delayError, prefetch)); + } else { + source.subscribe(new ObserveOnSubscriber(s, worker, delayError, prefetch)); + } + } + + abstract static class BaseObserveOnSubscriber + extends BasicIntQueueSubscription + implements FlowableSubscriber, Runnable { + private static final long serialVersionUID = -8241002408341274697L; + + final Worker worker; + + final boolean delayError; + + final int prefetch; + + final int limit; + + final AtomicLong requested; + + Subscription upstream; + + SimpleQueue queue; + + volatile boolean cancelled; + + volatile boolean done; + + Throwable error; + + int sourceMode; + + long produced; + + boolean outputFused; + + BaseObserveOnSubscriber( + Worker worker, + boolean delayError, + int prefetch) { + this.worker = worker; + this.delayError = delayError; + this.prefetch = prefetch; + this.requested = new AtomicLong(); + this.limit = prefetch - (prefetch >> 2); + } + + @Override + public final void onNext(T t) { + if (done) { + return; + } + if (sourceMode == ASYNC) { + trySchedule(); + return; + } + if (!queue.offer(t)) { + upstream.cancel(); + + error = new MissingBackpressureException("Queue is full?!"); + done = true; + } + trySchedule(); + } + + @Override + public final void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + trySchedule(); + } + + @Override + public final void onComplete() { + if (!done) { + done = true; + trySchedule(); + } + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + trySchedule(); + } + } + + @Override + public final void cancel() { + if (cancelled) { + return; + } + + cancelled = true; + upstream.cancel(); + worker.dispose(); + + if (!outputFused && getAndIncrement() == 0) { + queue.clear(); + } + } + + final void trySchedule() { + if (getAndIncrement() != 0) { + return; + } + worker.schedule(this); + } + + @Override + public final void run() { + if (outputFused) { + runBackfused(); + } else if (sourceMode == SYNC) { + runSync(); + } else { + runAsync(); + } + } + + abstract void runBackfused(); + + abstract void runSync(); + + abstract void runAsync(); + + final boolean checkTerminated(boolean d, boolean empty, Subscriber a) { + if (cancelled) { + clear(); + return true; + } + if (d) { + if (delayError) { + if (empty) { + cancelled = true; + Throwable e = error; + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + worker.dispose(); + return true; + } + } else { + Throwable e = error; + if (e != null) { + cancelled = true; + clear(); + a.onError(e); + worker.dispose(); + return true; + } else + if (empty) { + cancelled = true; + a.onComplete(); + worker.dispose(); + return true; + } + } + } + + return false; + } + + @Override + public final int requestFusion(int requestedMode) { + if ((requestedMode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Override + public final void clear() { + queue.clear(); + } + + @Override + public final boolean isEmpty() { + return queue.isEmpty(); + } + } + + static final class ObserveOnSubscriber extends BaseObserveOnSubscriber + implements FlowableSubscriber { + + private static final long serialVersionUID = -4547113800637756442L; + + final Subscriber downstream; + + ObserveOnSubscriber( + Subscriber actual, + Worker worker, + boolean delayError, + int prefetch) { + super(worker, delayError, prefetch); + this.downstream = actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription f = (QueueSubscription) s; + + int m = f.requestFusion(ANY | BOUNDARY); + + if (m == SYNC) { + sourceMode = SYNC; + queue = f; + done = true; + + downstream.onSubscribe(this); + return; + } else + if (m == ASYNC) { + sourceMode = ASYNC; + queue = f; + + downstream.onSubscribe(this); + + s.request(prefetch); + + return; + } + } + + queue = new SpscArrayQueue(prefetch); + + downstream.onSubscribe(this); + + s.request(prefetch); + } + } + + @Override + void runSync() { + int missed = 1; + + final Subscriber a = downstream; + final SimpleQueue q = queue; + + long e = produced; + + for (;;) { + + long r = requested.get(); + + while (e != r) { + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelled = true; + upstream.cancel(); + a.onError(ex); + worker.dispose(); + return; + } + + if (cancelled) { + return; + } + if (v == null) { + cancelled = true; + a.onComplete(); + worker.dispose(); + return; + } + + a.onNext(v); + + e++; + } + + if (cancelled) { + return; + } + + if (q.isEmpty()) { + cancelled = true; + a.onComplete(); + worker.dispose(); + return; + } + + int w = get(); + if (missed == w) { + produced = e; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + + @Override + void runAsync() { + int missed = 1; + + final Subscriber a = downstream; + final SimpleQueue q = queue; + + long e = produced; + + for (;;) { + + long r = requested.get(); + + while (e != r) { + boolean d = done; + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + cancelled = true; + upstream.cancel(); + q.clear(); + + a.onError(ex); + worker.dispose(); + return; + } + + boolean empty = v == null; + + if (checkTerminated(d, empty, a)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + if (e == limit) { + if (r != Long.MAX_VALUE) { + r = requested.addAndGet(-e); + } + upstream.request(e); + e = 0L; + } + } + + if (e == r && checkTerminated(done, q.isEmpty(), a)) { + return; + } + + int w = get(); + if (missed == w) { + produced = e; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + + @Override + void runBackfused() { + int missed = 1; + + for (;;) { + + if (cancelled) { + return; + } + + boolean d = done; + + downstream.onNext(null); + + if (d) { + cancelled = true; + Throwable e = error; + if (e != null) { + downstream.onError(e); + } else { + downstream.onComplete(); + } + worker.dispose(); + return; + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Nullable + @Override + public T poll() throws Exception { + T v = queue.poll(); + if (v != null && sourceMode != SYNC) { + long p = produced + 1; + if (p == limit) { + produced = 0; + upstream.request(p); + } else { + produced = p; + } + } + return v; + } + + } + + static final class ObserveOnConditionalSubscriber + extends BaseObserveOnSubscriber { + + private static final long serialVersionUID = 644624475404284533L; + + final ConditionalSubscriber downstream; + + long consumed; + + ObserveOnConditionalSubscriber( + ConditionalSubscriber actual, + Worker worker, + boolean delayError, + int prefetch) { + super(worker, delayError, prefetch); + this.downstream = actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription f = (QueueSubscription) s; + + int m = f.requestFusion(ANY | BOUNDARY); + + if (m == SYNC) { + sourceMode = SYNC; + queue = f; + done = true; + + downstream.onSubscribe(this); + return; + } else + if (m == ASYNC) { + sourceMode = ASYNC; + queue = f; + + downstream.onSubscribe(this); + + s.request(prefetch); + + return; + } + } + + queue = new SpscArrayQueue(prefetch); + + downstream.onSubscribe(this); + + s.request(prefetch); + } + } + + @Override + void runSync() { + int missed = 1; + + final ConditionalSubscriber a = downstream; + final SimpleQueue q = queue; + + long e = produced; + + for (;;) { + + long r = requested.get(); + + while (e != r) { + T v; + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelled = true; + upstream.cancel(); + a.onError(ex); + worker.dispose(); + return; + } + + if (cancelled) { + return; + } + if (v == null) { + cancelled = true; + a.onComplete(); + worker.dispose(); + return; + } + + if (a.tryOnNext(v)) { + e++; + } + } + + if (cancelled) { + return; + } + + if (q.isEmpty()) { + cancelled = true; + a.onComplete(); + worker.dispose(); + return; + } + + int w = get(); + if (missed == w) { + produced = e; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + + @Override + void runAsync() { + int missed = 1; + + final ConditionalSubscriber a = downstream; + final SimpleQueue q = queue; + + long emitted = produced; + long polled = consumed; + + for (;;) { + + long r = requested.get(); + + while (emitted != r) { + boolean d = done; + T v; + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + cancelled = true; + upstream.cancel(); + q.clear(); + + a.onError(ex); + worker.dispose(); + return; + } + boolean empty = v == null; + + if (checkTerminated(d, empty, a)) { + return; + } + + if (empty) { + break; + } + + if (a.tryOnNext(v)) { + emitted++; + } + + polled++; + + if (polled == limit) { + upstream.request(polled); + polled = 0L; + } + } + + if (emitted == r && checkTerminated(done, q.isEmpty(), a)) { + return; + } + + int w = get(); + if (missed == w) { + produced = emitted; + consumed = polled; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + + } + + @Override + void runBackfused() { + int missed = 1; + + for (;;) { + + if (cancelled) { + return; + } + + boolean d = done; + + downstream.onNext(null); + + if (d) { + cancelled = true; + Throwable e = error; + if (e != null) { + downstream.onError(e); + } else { + downstream.onComplete(); + } + worker.dispose(); + return; + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Nullable + @Override + public T poll() throws Exception { + T v = queue.poll(); + if (v != null && sourceMode != SYNC) { + long p = consumed + 1; + if (p == limit) { + consumed = 0; + upstream.request(p); + } else { + consumed = p; + } + } + return v; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java new file mode 100755 index 0000000..8cbb73e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java @@ -0,0 +1,272 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Action; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; + +public final class FlowableOnBackpressureBuffer extends AbstractFlowableWithUpstream { + final int bufferSize; + final boolean unbounded; + final boolean delayError; + final Action onOverflow; + + public FlowableOnBackpressureBuffer(Flowable source, int bufferSize, boolean unbounded, + boolean delayError, Action onOverflow) { + super(source); + this.bufferSize = bufferSize; + this.unbounded = unbounded; + this.delayError = delayError; + this.onOverflow = onOverflow; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new BackpressureBufferSubscriber(s, bufferSize, unbounded, delayError, onOverflow)); + } + + static final class BackpressureBufferSubscriber extends BasicIntQueueSubscription implements FlowableSubscriber { + + private static final long serialVersionUID = -2514538129242366402L; + + final Subscriber downstream; + final SimplePlainQueue queue; + final boolean delayError; + final Action onOverflow; + + Subscription upstream; + + volatile boolean cancelled; + + volatile boolean done; + Throwable error; + + final AtomicLong requested = new AtomicLong(); + + boolean outputFused; + + BackpressureBufferSubscriber(Subscriber actual, int bufferSize, + boolean unbounded, boolean delayError, Action onOverflow) { + this.downstream = actual; + this.onOverflow = onOverflow; + this.delayError = delayError; + + SimplePlainQueue q; + + if (unbounded) { + q = new SpscLinkedArrayQueue(bufferSize); + } else { + q = new SpscArrayQueue(bufferSize); + } + + this.queue = q; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (!queue.offer(t)) { + upstream.cancel(); + MissingBackpressureException ex = new MissingBackpressureException("Buffer is full"); + try { + onOverflow.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + ex.initCause(e); + } + onError(ex); + return; + } + if (outputFused) { + downstream.onNext(null); + } else { + drain(); + } + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + if (outputFused) { + downstream.onError(t); + } else { + drain(); + } + } + + @Override + public void onComplete() { + done = true; + if (outputFused) { + downstream.onComplete(); + } else { + drain(); + } + } + + @Override + public void request(long n) { + if (!outputFused) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + upstream.cancel(); + + if (!outputFused && getAndIncrement() == 0) { + queue.clear(); + } + } + } + + void drain() { + if (getAndIncrement() == 0) { + int missed = 1; + final SimplePlainQueue q = queue; + final Subscriber a = downstream; + for (;;) { + + if (checkTerminated(done, q.isEmpty(), a)) { + return; + } + + long r = requested.get(); + + long e = 0L; + + while (e != r) { + boolean d = done; + T v = q.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e == r) { + boolean d = done; + boolean empty = q.isEmpty(); + + if (checkTerminated(d, empty, a)) { + return; + } + } + + if (e != 0L) { + if (r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a) { + if (cancelled) { + queue.clear(); + return true; + } + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + return true; + } + } else { + Throwable e = error; + if (e != null) { + queue.clear(); + a.onError(e); + return true; + } else + if (empty) { + a.onComplete(); + return true; + } + } + } + return false; + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Nullable + @Override + public T poll() throws Exception { + return queue.poll(); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java new file mode 100755 index 0000000..f11a520 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java @@ -0,0 +1,278 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Action; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Handle backpressure with a bounded buffer and custom strategy. + * + * @param the input and output value type + */ +public final class FlowableOnBackpressureBufferStrategy extends AbstractFlowableWithUpstream { + + final long bufferSize; + + final Action onOverflow; + + final BackpressureOverflowStrategy strategy; + + public FlowableOnBackpressureBufferStrategy(Flowable source, + long bufferSize, Action onOverflow, BackpressureOverflowStrategy strategy) { + super(source); + this.bufferSize = bufferSize; + this.onOverflow = onOverflow; + this.strategy = strategy; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new OnBackpressureBufferStrategySubscriber(s, onOverflow, strategy, bufferSize)); + } + + static final class OnBackpressureBufferStrategySubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 3240706908776709697L; + + final Subscriber downstream; + + final Action onOverflow; + + final BackpressureOverflowStrategy strategy; + + final long bufferSize; + + final AtomicLong requested; + + final Deque deque; + + Subscription upstream; + + volatile boolean cancelled; + + volatile boolean done; + Throwable error; + + OnBackpressureBufferStrategySubscriber(Subscriber actual, Action onOverflow, + BackpressureOverflowStrategy strategy, long bufferSize) { + this.downstream = actual; + this.onOverflow = onOverflow; + this.strategy = strategy; + this.bufferSize = bufferSize; + this.requested = new AtomicLong(); + this.deque = new ArrayDeque(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean callOnOverflow = false; + boolean callError = false; + Deque dq = deque; + synchronized (dq) { + if (dq.size() == bufferSize) { + switch (strategy) { + case DROP_LATEST: + dq.pollLast(); + dq.offer(t); + callOnOverflow = true; + break; + case DROP_OLDEST: + dq.poll(); + dq.offer(t); + callOnOverflow = true; + break; + default: + // signal error + callError = true; + break; + } + } else { + dq.offer(t); + } + } + + if (callOnOverflow) { + if (onOverflow != null) { + try { + onOverflow.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + } + } + } else if (callError) { + upstream.cancel(); + onError(new MissingBackpressureException()); + } else { + drain(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + + if (getAndIncrement() == 0) { + clear(deque); + } + } + + void clear(Deque dq) { + synchronized (dq) { + dq.clear(); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Deque dq = deque; + Subscriber a = downstream; + for (;;) { + long r = requested.get(); + long e = 0L; + while (e != r) { + if (cancelled) { + clear(dq); + return; + } + + boolean d = done; + + T v; + + synchronized (dq) { + v = dq.poll(); + } + + boolean empty = v == null; + + if (d) { + Throwable ex = error; + if (ex != null) { + clear(dq); + a.onError(ex); + return; + } + if (empty) { + a.onComplete(); + return; + } + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e == r) { + if (cancelled) { + clear(dq); + return; + } + + boolean d = done; + + boolean empty; + + synchronized (dq) { + empty = dq.isEmpty(); + } + + if (d) { + Throwable ex = error; + if (ex != null) { + clear(dq); + a.onError(ex); + return; + } + if (empty) { + a.onComplete(); + return; + } + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureDrop.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureDrop.java new file mode 100755 index 0000000..df5f4a7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureDrop.java @@ -0,0 +1,128 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableOnBackpressureDrop extends AbstractFlowableWithUpstream implements Consumer { + + final Consumer onDrop; + + public FlowableOnBackpressureDrop(Flowable source) { + super(source); + this.onDrop = this; + } + + public FlowableOnBackpressureDrop(Flowable source, Consumer onDrop) { + super(source); + this.onDrop = onDrop; + } + + @Override + public void accept(T t) { + // deliberately ignoring + } + + @Override + protected void subscribeActual(Subscriber s) { + this.source.subscribe(new BackpressureDropSubscriber(s, onDrop)); + } + + static final class BackpressureDropSubscriber + extends AtomicLong implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -6246093802440953054L; + + final Subscriber downstream; + final Consumer onDrop; + + Subscription upstream; + + boolean done; + + BackpressureDropSubscriber(Subscriber actual, Consumer onDrop) { + this.downstream = actual; + this.onDrop = onDrop; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long r = get(); + if (r != 0L) { + downstream.onNext(t); + BackpressureHelper.produced(this, 1); + } else { + try { + onDrop.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + onError(e); + } + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(this, n); + } + } + + @Override + public void cancel() { + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java new file mode 100755 index 0000000..556e54b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java @@ -0,0 +1,104 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableOnBackpressureError extends AbstractFlowableWithUpstream { + + public FlowableOnBackpressureError(Flowable source) { + super(source); + } + + @Override + protected void subscribeActual(Subscriber s) { + this.source.subscribe(new BackpressureErrorSubscriber(s)); + } + + static final class BackpressureErrorSubscriber + extends AtomicLong implements FlowableSubscriber, Subscription { + private static final long serialVersionUID = -3176480756392482682L; + + final Subscriber downstream; + Subscription upstream; + boolean done; + + BackpressureErrorSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long r = get(); + if (r != 0L) { + downstream.onNext(t); + BackpressureHelper.produced(this, 1); + } else { + upstream.cancel(); + onError(new MissingBackpressureException("could not emit value due to lack of requests")); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(this, n); + } + } + + @Override + public void cancel() { + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatest.java new file mode 100755 index 0000000..f981b73 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureLatest.java @@ -0,0 +1,171 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +public final class FlowableOnBackpressureLatest extends AbstractFlowableWithUpstream { + + public FlowableOnBackpressureLatest(Flowable source) { + super(source); + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new BackpressureLatestSubscriber(s)); + } + + static final class BackpressureLatestSubscriber extends AtomicInteger implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 163080509307634843L; + + final Subscriber downstream; + + Subscription upstream; + + volatile boolean done; + Throwable error; + + volatile boolean cancelled; + + final AtomicLong requested = new AtomicLong(); + + final AtomicReference current = new AtomicReference(); + + BackpressureLatestSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + current.lazySet(t); + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + upstream.cancel(); + + if (getAndIncrement() == 0) { + current.lazySet(null); + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + final Subscriber a = downstream; + int missed = 1; + final AtomicLong r = requested; + final AtomicReference q = current; + + for (;;) { + long e = 0L; + + while (e != r.get()) { + boolean d = done; + T v = q.getAndSet(null); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e == r.get() && checkTerminated(done, q.get() == null, a, q)) { + return; + } + + if (e != 0L) { + BackpressureHelper.produced(r, e); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, AtomicReference q) { + if (cancelled) { + q.lazySet(null); + return true; + } + + if (d) { + Throwable e = error; + if (e != null) { + q.lazySet(null); + a.onError(e); + return true; + } else + if (empty) { + a.onComplete(); + return true; + } + } + + return false; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java new file mode 100755 index 0000000..7110d08 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorNext.java @@ -0,0 +1,128 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionArbiter; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableOnErrorNext extends AbstractFlowableWithUpstream { + final Function> nextSupplier; + final boolean allowFatal; + + public FlowableOnErrorNext(Flowable source, + Function> nextSupplier, boolean allowFatal) { + super(source); + this.nextSupplier = nextSupplier; + this.allowFatal = allowFatal; + } + + @Override + protected void subscribeActual(Subscriber s) { + OnErrorNextSubscriber parent = new OnErrorNextSubscriber(s, nextSupplier, allowFatal); + s.onSubscribe(parent); + source.subscribe(parent); + } + + static final class OnErrorNextSubscriber + extends SubscriptionArbiter + implements FlowableSubscriber { + private static final long serialVersionUID = 4063763155303814625L; + + final Subscriber downstream; + + final Function> nextSupplier; + + final boolean allowFatal; + + boolean once; + + boolean done; + + long produced; + + OnErrorNextSubscriber(Subscriber actual, Function> nextSupplier, boolean allowFatal) { + super(false); + this.downstream = actual; + this.nextSupplier = nextSupplier; + this.allowFatal = allowFatal; + } + + @Override + public void onSubscribe(Subscription s) { + setSubscription(s); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (!once) { + produced++; + } + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (once) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + downstream.onError(t); + return; + } + once = true; + + if (allowFatal && !(t instanceof Exception)) { + downstream.onError(t); + return; + } + + Publisher p; + + try { + p = ObjectHelper.requireNonNull(nextSupplier.apply(t), "The nextSupplier returned a null Publisher"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + return; + } + + long mainProduced = produced; + if (mainProduced != 0L) { + produced(mainProduced); + } + + p.subscribe(this); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + once = true; + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturn.java new file mode 100755 index 0000000..baf47d6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnErrorReturn.java @@ -0,0 +1,71 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscribers.SinglePostCompleteSubscriber; + +public final class FlowableOnErrorReturn extends AbstractFlowableWithUpstream { + final Function valueSupplier; + public FlowableOnErrorReturn(Flowable source, Function valueSupplier) { + super(source); + this.valueSupplier = valueSupplier; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new OnErrorReturnSubscriber(s, valueSupplier)); + } + + static final class OnErrorReturnSubscriber + extends SinglePostCompleteSubscriber { + + private static final long serialVersionUID = -3740826063558713822L; + final Function valueSupplier; + + OnErrorReturnSubscriber(Subscriber actual, Function valueSupplier) { + super(actual); + this.valueSupplier = valueSupplier; + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + T v; + try { + v = ObjectHelper.requireNonNull(valueSupplier.apply(t), "The valueSupplier returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(new CompositeException(t, ex)); + return; + } + complete(v); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java new file mode 100755 index 0000000..b325d2b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java @@ -0,0 +1,745 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * A connectable observable which shares an underlying source and dispatches source values to subscribers in a backpressure-aware + * manner. + * @param the value type + */ +public final class FlowablePublish extends ConnectableFlowable +implements HasUpstreamPublisher, FlowablePublishClassic { + /** + * Indicates this child has been cancelled: the state is swapped in atomically and + * will prevent the dispatch() to emit (too many) values to a terminated child subscriber. + */ + static final long CANCELLED = Long.MIN_VALUE; + + /** The source observable. */ + final Flowable source; + /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ + final AtomicReference> current; + + /** The size of the prefetch buffer. */ + final int bufferSize; + + final Publisher onSubscribe; + + /** + * Creates a OperatorPublish instance to publish values of the given source observable. + * @param the source value type + * @param source the source observable + * @param bufferSize the size of the prefetch buffer + * @return the connectable observable + */ + public static ConnectableFlowable create(Flowable source, final int bufferSize) { + // the current connection to source needs to be shared between the operator and its onSubscribe call + final AtomicReference> curr = new AtomicReference>(); + Publisher onSubscribe = new FlowablePublisher(curr, bufferSize); + return RxJavaPlugins.onAssembly(new FlowablePublish(onSubscribe, source, curr, bufferSize)); + } + + private FlowablePublish(Publisher onSubscribe, Flowable source, + final AtomicReference> current, int bufferSize) { + this.onSubscribe = onSubscribe; + this.source = source; + this.current = current; + this.bufferSize = bufferSize; + } + + @Override + public Publisher source() { + return source; + } + + /** + * The internal buffer size of this FloawblePublish operator. + * @return The internal buffer size of this FloawblePublish operator. + */ + @Override + public int publishBufferSize() { + return bufferSize; + } + + @Override + public Publisher publishSource() { + return source; + } + + @Override + protected void subscribeActual(Subscriber s) { + onSubscribe.subscribe(s); + } + + @Override + public void connect(Consumer connection) { + boolean doConnect; + PublishSubscriber ps; + // we loop because concurrent connect/disconnect and termination may change the state + for (;;) { + // retrieve the current subscriber-to-source instance + ps = current.get(); + // if there is none yet or the current has been disposed + if (ps == null || ps.isDisposed()) { + // create a new subscriber-to-source + PublishSubscriber u = new PublishSubscriber(current, bufferSize); + // try setting it as the current subscriber-to-source + if (!current.compareAndSet(ps, u)) { + // did not work, perhaps a new subscriber arrived + // and created a new subscriber-to-source as well, retry + continue; + } + ps = u; + } + // if connect() was called concurrently, only one of them should actually + // connect to the source + doConnect = !ps.shouldConnect.get() && ps.shouldConnect.compareAndSet(false, true); + break; // NOPMD + } + /* + * Notify the callback that we have a (new) connection which it can dispose + * but since ps is unique to a connection, multiple calls to connect() will return the + * same Subscription and even if there was a connect-disconnect-connect pair, the older + * references won't disconnect the newer connection. + * Synchronous source consumers have the opportunity to disconnect via dispose on the + * Disposable as subscribe() may never return on its own. + * + * Note however, that asynchronously disconnecting a running source might leave + * child subscribers without any terminal event; PublishProcessor does not have this + * issue because the cancellation was always triggered by the child subscribers + * themselves. + */ + try { + connection.accept(ps); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + if (doConnect) { + source.subscribe(ps); + } + } + + @SuppressWarnings("rawtypes") + static final class PublishSubscriber + extends AtomicInteger + implements FlowableSubscriber, Disposable { + private static final long serialVersionUID = -202316842419149694L; + + /** Indicates an empty array of inner subscribers. */ + static final InnerSubscriber[] EMPTY = new InnerSubscriber[0]; + /** Indicates a terminated PublishSubscriber. */ + static final InnerSubscriber[] TERMINATED = new InnerSubscriber[0]; + + /** Holds onto the current connected PublishSubscriber. */ + final AtomicReference> current; + /** The prefetch buffer size. */ + final int bufferSize; + + /** Tracks the subscribed InnerSubscribers. */ + final AtomicReference[]> subscribers; + /** + * Atomically changed from false to true by connect to make sure the + * connection is only performed by one thread. + */ + final AtomicBoolean shouldConnect; + + final AtomicReference upstream = new AtomicReference(); + + /** Contains either an onComplete or an onError token from upstream. */ + volatile Object terminalEvent; + + int sourceMode; + + /** Holds notifications from upstream. */ + volatile SimpleQueue queue; + + @SuppressWarnings("unchecked") + PublishSubscriber(AtomicReference> current, int bufferSize) { + this.subscribers = new AtomicReference[]>(EMPTY); + this.current = current; + this.shouldConnect = new AtomicBoolean(); + this.bufferSize = bufferSize; + } + + @Override + public void dispose() { + if (subscribers.get() != TERMINATED) { + @SuppressWarnings("unchecked") + InnerSubscriber[] ps = subscribers.getAndSet(TERMINATED); + if (ps != TERMINATED) { + current.compareAndSet(PublishSubscriber.this, null); + SubscriptionHelper.cancel(upstream); + } + } + } + + @Override + public boolean isDisposed() { + return subscribers.get() == TERMINATED; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = qs; + terminalEvent = NotificationLite.complete(); + dispatch(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = qs; + s.request(bufferSize); + return; + } + } + + queue = new SpscArrayQueue(bufferSize); + + s.request(bufferSize); + } + } + + @Override + public void onNext(T t) { + // we expect upstream to honor backpressure requests + if (sourceMode == QueueSubscription.NONE && !queue.offer(t)) { + onError(new MissingBackpressureException("Prefetch queue is full?!")); + return; + } + // since many things can happen concurrently, we have a common dispatch + // loop to act on the current state serially + dispatch(); + } + + @Override + public void onError(Throwable e) { + // The observer front is accessed serially as required by spec so + // no need to CAS in the terminal value + if (terminalEvent == null) { + terminalEvent = NotificationLite.error(e); + // since many things can happen concurrently, we have a common dispatch + // loop to act on the current state serially + dispatch(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + // The observer front is accessed serially as required by spec so + // no need to CAS in the terminal value + if (terminalEvent == null) { + terminalEvent = NotificationLite.complete(); + // since many things can happen concurrently, we have a common dispatch loop + // to act on the current state serially + dispatch(); + } + } + + /** + * Atomically try adding a new InnerSubscriber to this Subscriber or return false if this + * Subscriber was terminated. + * @param producer the producer to add + * @return true if succeeded, false otherwise + */ + boolean add(InnerSubscriber producer) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // get the current producer array + InnerSubscriber[] c = subscribers.get(); + // if this subscriber-to-source reached a terminal state by receiving + // an onError or onComplete, just refuse to add the new producer + if (c == TERMINATED) { + return false; + } + // we perform a copy-on-write logic + int len = c.length; + @SuppressWarnings("unchecked") + InnerSubscriber[] u = new InnerSubscriber[len + 1]; + System.arraycopy(c, 0, u, 0, len); + u[len] = producer; + // try setting the subscribers array + if (subscribers.compareAndSet(c, u)) { + return true; + } + // if failed, some other operation succeeded (another add, remove or termination) + // so retry + } + } + + /** + * Atomically removes the given InnerSubscriber from the subscribers array. + * @param producer the producer to remove + */ + @SuppressWarnings("unchecked") + void remove(InnerSubscriber producer) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // let's read the current subscribers array + InnerSubscriber[] c = subscribers.get(); + int len = c.length; + // if it is either empty or terminated, there is nothing to remove so we quit + if (len == 0) { + break; + } + // let's find the supplied producer in the array + // although this is O(n), we don't expect too many child subscribers in general + int j = -1; + for (int i = 0; i < len; i++) { + if (c[i].equals(producer)) { + j = i; + break; + } + } + // we didn't find it so just quit + if (j < 0) { + return; + } + // we do copy-on-write logic here + InnerSubscriber[] u; + // we don't create a new empty array if producer was the single inhabitant + // but rather reuse an empty array + if (len == 1) { + u = EMPTY; + } else { + // otherwise, create a new array one less in size + u = new InnerSubscriber[len - 1]; + // copy elements being before the given producer + System.arraycopy(c, 0, u, 0, j); + // copy elements being after the given producer + System.arraycopy(c, j + 1, u, j, len - j - 1); + } + // try setting this new array as + if (subscribers.compareAndSet(c, u)) { + break; + } + // if we failed, it means something else happened + // (a concurrent add/remove or termination), we need to retry + } + } + + /** + * Perform termination actions in case the source has terminated in some way and + * the queue has also become empty. + * @param term the terminal event (a NotificationLite.error or completed) + * @param empty set to true if the queue is empty + * @return true if there is indeed a terminal condition + */ + @SuppressWarnings("unchecked") + boolean checkTerminated(Object term, boolean empty) { + // first of all, check if there is actually a terminal event + if (term != null) { + // is it a completion event (impl. note, this is much cheaper than checking for isError) + if (NotificationLite.isComplete(term)) { + // but we also need to have an empty queue + if (empty) { + // this will prevent OnSubscribe spinning on a terminated but + // not yet cancelled PublishSubscriber + current.compareAndSet(this, null); + /* + * This will swap in a terminated array so add() in OnSubscribe will reject + * child subscribers to associate themselves with a terminated and thus + * never again emitting chain. + * + * Since we atomically change the contents of 'subscribers' only one + * operation wins at a time. If an add() wins before this getAndSet, + * its value will be part of the returned array by getAndSet and thus + * will receive the terminal notification. Otherwise, if getAndSet wins, + * add() will refuse to add the child producer and will trigger the + * creation of subscriber-to-source. + */ + for (InnerSubscriber ip : subscribers.getAndSet(TERMINATED)) { + ip.child.onComplete(); + } + // indicate we reached the terminal state + return true; + } + } else { + Throwable t = NotificationLite.getError(term); + // this will prevent OnSubscribe spinning on a terminated + // but not yet cancelled PublishSubscriber + current.compareAndSet(this, null); + // this will swap in a terminated array so add() in OnSubscribe will reject + // child subscribers to associate themselves with a terminated and thus + // never again emitting chain + InnerSubscriber[] a = subscribers.getAndSet(TERMINATED); + if (a.length != 0) { + for (InnerSubscriber ip : a) { + ip.child.onError(t); + } + } else { + RxJavaPlugins.onError(t); + } + // indicate we reached the terminal state + return true; + } + } + // there is still work to be done + return false; + } + + /** + * The common serialization point of events arriving from upstream and child subscribers + * requesting more. + */ + void dispatch() { + // standard construct of queue-drain + // if there is an emission going on, indicate that more work needs to be done + // the exact nature of this work needs to be determined from other data structures + if (getAndIncrement() != 0) { + return; + } + int missed = 1; + + // saving a local copy because this will be accessed after every item + // delivered to detect changes in the subscribers due to an onNext + // and thus not dropping items + AtomicReference[]> subscribers = this.subscribers; + + // We take a snapshot of the current child subscribers. + // Concurrent subscribers may miss this iteration, but it is to be expected + InnerSubscriber[] ps = subscribers.get(); + + outer: + for (;;) { + /* + * We need to read terminalEvent before checking the queue for emptiness because + * all enqueue happens before setting the terminal event. + * If it were the other way around, when the emission is paused between + * checking isEmpty and checking terminalEvent, some other thread might + * have produced elements and set the terminalEvent and we'd quit emitting + * prematurely. + */ + Object term = terminalEvent; + /* + * See if the queue is empty; since we need this information multiple + * times later on, we read it one. + * Although the queue can become non-empty in the mean time, we will + * detect it through the missing flag and will do another iteration. + */ + SimpleQueue q = queue; + + boolean empty = q == null || q.isEmpty(); + // if the queue is empty and the terminal event was received, quit + // and don't bother restoring emitting to false: no further activity is + // possible at this point + if (checkTerminated(term, empty)) { + return; + } + + // We have elements queued. Note that due to the serialization nature of dispatch() + // this loop is the only one which can turn a non-empty queue into an empty one + // and as such, no need to ask the queue itself again for that. + if (!empty) { + + int len = ps.length; + // Let's assume everyone requested the maximum value. + long maxRequested = Long.MAX_VALUE; + // count how many have triggered cancellation + int cancelled = 0; + + // Now find the minimum amount each child-subscriber requested + // since we can only emit that much to all of them without violating + // backpressure constraints + for (InnerSubscriber ip : ps) { + long r = ip.get(); + // if there is one child subscriber that hasn't requested yet + // we can't emit anything to anyone + if (r != CANCELLED) { + maxRequested = Math.min(maxRequested, r - ip.emitted); + } else { + cancelled++; + } + } + + // it may happen everyone has cancelled between here and subscribers.get() + // or we have no subscribers at all to begin with + if (len == cancelled) { + term = terminalEvent; + // so let's consume a value from the queue + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().cancel(); + term = NotificationLite.error(ex); + terminalEvent = term; + v = null; + } + // or terminate if there was a terminal event and the queue is empty + if (checkTerminated(term, v == null)) { + return; + } + // otherwise, just ask for a new value + if (sourceMode != QueueSubscription.SYNC) { + upstream.get().request(1); + } + // and retry emitting to potential new child subscribers + continue; + } + // if we get here, it means there are non-cancelled child subscribers + // and we count the number of emitted values because the queue + // may contain less than requested + int d = 0; + while (d < maxRequested) { + term = terminalEvent; + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().cancel(); + term = NotificationLite.error(ex); + terminalEvent = term; + v = null; + } + + empty = v == null; + // let's check if there is a terminal event and the queue became empty just now + if (checkTerminated(term, empty)) { + return; + } + // the queue is empty but we aren't terminated yet, finish this emission loop + if (empty) { + break; + } + // we need to unwrap potential nulls + T value = NotificationLite.getValue(v); + + boolean subscribersChanged = false; + + // let's emit this value to all child subscribers + for (InnerSubscriber ip : ps) { + // if ip.get() is negative, the child has either cancelled in the + // meantime or hasn't requested anything yet + // this eager behavior will skip cancelled children in case + // multiple values are available in the queue + long ipr = ip.get(); + if (ipr != CANCELLED) { + if (ipr != Long.MAX_VALUE) { + // indicate this child has received 1 element + ip.emitted++; + } + ip.child.onNext(value); + } else { + subscribersChanged = true; + } + } + // indicate we emitted one element + d++; + + // see if the array of subscribers changed as a consequence + // of emission or concurrent activity + InnerSubscriber[] freshArray = subscribers.get(); + if (subscribersChanged || freshArray != ps) { + ps = freshArray; + + // if we did emit at least one element, request more to replenish the queue + if (d != 0) { + if (sourceMode != QueueSubscription.SYNC) { + upstream.get().request(d); + } + } + + continue outer; + } + } + + // if we did emit at least one element, request more to replenish the queue + if (d != 0) { + if (sourceMode != QueueSubscription.SYNC) { + upstream.get().request(d); + } + } + // if we have requests but not an empty queue after emission + // let's try again to see if more requests/child subscribers are + // ready to receive more + if (maxRequested != 0L && !empty) { + continue; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + + // get a fresh copy of the current subscribers + ps = subscribers.get(); + } + } + } + /** + * A Subscription that manages the request and cancellation state of a + * child subscriber in thread-safe manner. + * @param the value type + */ + static final class InnerSubscriber extends AtomicLong implements Subscription { + + private static final long serialVersionUID = -4453897557930727610L; + /** The actual child subscriber. */ + final Subscriber child; + /** + * The parent subscriber-to-source used to allow removing the child in case of + * child cancellation. + */ + volatile PublishSubscriber parent; + + /** Track the number of emitted items (avoids decrementing the request counter). */ + long emitted; + + InnerSubscriber(Subscriber child) { + this.child = child; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.addCancel(this, n); + PublishSubscriber p = parent; + if (p != null) { + p.dispatch(); + } + } + } + + @Override + public void cancel() { + long r = get(); + // let's see if we are cancelled + if (r != CANCELLED) { + // if not, swap in the terminal state, this is idempotent + // because other methods using CAS won't overwrite this value, + // concurrent calls to cancel will atomically swap in the same + // terminal value + r = getAndSet(CANCELLED); + // and only one of them will see a non-terminated value before the swap + if (r != CANCELLED) { + PublishSubscriber p = parent; + if (p != null) { + // remove this from the parent + p.remove(this); + // After removal, we might have unblocked the other child subscribers: + // let's assume this child had 0 requested before the cancellation while + // the others had non-zero. By removing this 'blocking' child, the others + // are now free to receive events + p.dispatch(); + } + } + } + } + } + + static final class FlowablePublisher implements Publisher { + private final AtomicReference> curr; + private final int bufferSize; + + FlowablePublisher(AtomicReference> curr, int bufferSize) { + this.curr = curr; + this.bufferSize = bufferSize; + } + + @Override + public void subscribe(Subscriber child) { + // create the backpressure-managing producer for this child + InnerSubscriber inner = new InnerSubscriber(child); + child.onSubscribe(inner); + // concurrent connection/disconnection may change the state, + // we loop to be atomic while the child subscribes + for (;;) { + // get the current subscriber-to-source + PublishSubscriber r = curr.get(); + // if there isn't one or it is cancelled/disposed + if (r == null || r.isDisposed()) { + // create a new subscriber to source + PublishSubscriber u = new PublishSubscriber(curr, bufferSize); + // let's try setting it as the current subscriber-to-source + if (!curr.compareAndSet(r, u)) { + // didn't work, maybe someone else did it or the current subscriber + // to source has just finished + continue; + } + // we won, let's use it going onwards + r = u; + } + + /* + * Try adding it to the current subscriber-to-source, add is atomic in respect + * to other adds and the termination of the subscriber-to-source. + */ + if (r.add(inner)) { + if (inner.get() == CANCELLED) { + r.remove(inner); + } else { + inner.parent = r; + } + r.dispatch(); + break; // NOPMD + } + /* + * The current PublishSubscriber has been terminated, try with a newer one. + */ + /* + * Note: although technically correct, concurrent disconnects can cause + * unexpected behavior such as child subscribers never receiving anything + * (unless connected again). An alternative approach, similar to + * PublishProcessor would be to immediately terminate such child + * subscribers as well: + * + * Object term = r.terminalEvent; + * if (r.nl.isCompleted(term)) { + * child.onComplete(); + * } else { + * child.onError(r.nl.getError(term)); + * } + * return; + * + * The original concurrent behavior was non-deterministic in this regard as well. + * Allowing this behavior, however, may introduce another unexpected behavior: + * after disconnecting a previous connection, one might not be able to prepare + * a new connection right after a previous termination by subscribing new child + * subscribers asynchronously before a connect call. + */ + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishAlt.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishAlt.java new file mode 100755 index 0000000..932e0bf --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishAlt.java @@ -0,0 +1,484 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.ResettableConnectable; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Shares a single underlying connection to the upstream Publisher + * and multicasts events to all subscribed subscribers until the upstream + * completes or the connection is disposed. + *

+ * The difference to FlowablePublish is that when the upstream terminates, + * late subscriberss will receive that terminal event until the connection is + * disposed and the ConnectableFlowable is reset to its fresh state. + * + * @param the element type + * @since 2.2.10 + */ +public final class FlowablePublishAlt extends ConnectableFlowable +implements HasUpstreamPublisher, ResettableConnectable { + + final Publisher source; + + final int bufferSize; + + final AtomicReference> current; + + public FlowablePublishAlt(Publisher source, int bufferSize) { + this.source = source; + this.bufferSize = bufferSize; + this.current = new AtomicReference>(); + } + + @Override + public Publisher source() { + return source; + } + + /** + * The internal buffer size of this FloawblePublishAlt operator. + * @return The internal buffer size of this FloawblePublishAlt operator. + */ + public int publishBufferSize() { + return bufferSize; + } + + @Override + public void connect(Consumer connection) { + PublishConnection conn; + boolean doConnect = false; + + for (;;) { + conn = current.get(); + + if (conn == null || conn.isDisposed()) { + PublishConnection fresh = new PublishConnection(current, bufferSize); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + + doConnect = !conn.connect.get() && conn.connect.compareAndSet(false, true); + break; + } + + try { + connection.accept(conn); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + + if (doConnect) { + source.subscribe(conn); + } + } + + @Override + protected void subscribeActual(Subscriber s) { + PublishConnection conn; + + for (;;) { + conn = current.get(); + + // don't create a fresh connection if the current is disposed + if (conn == null) { + PublishConnection fresh = new PublishConnection(current, bufferSize); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + + break; + } + + InnerSubscription inner = new InnerSubscription(s, conn); + s.onSubscribe(inner); + + if (conn.add(inner)) { + if (inner.isCancelled()) { + conn.remove(inner); + } else { + conn.drain(); + } + return; + } + + Throwable ex = conn.error; + if (ex != null) { + s.onError(ex); + } else { + s.onComplete(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void resetIf(Disposable connection) { + current.compareAndSet((PublishConnection)connection, null); + } + + static final class PublishConnection + extends AtomicInteger + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = -1672047311619175801L; + + final AtomicReference> current; + + final AtomicReference upstream; + + final AtomicBoolean connect; + + final AtomicReference[]> subscribers; + + final int bufferSize; + + volatile SimpleQueue queue; + + int sourceMode; + + volatile boolean done; + Throwable error; + + int consumed; + + @SuppressWarnings("rawtypes") + static final InnerSubscription[] EMPTY = new InnerSubscription[0]; + @SuppressWarnings("rawtypes") + static final InnerSubscription[] TERMINATED = new InnerSubscription[0]; + + @SuppressWarnings("unchecked") + PublishConnection(AtomicReference> current, int bufferSize) { + this.current = current; + this.upstream = new AtomicReference(); + this.connect = new AtomicBoolean(); + this.bufferSize = bufferSize; + this.subscribers = new AtomicReference[]>(EMPTY); + } + + @SuppressWarnings("unchecked") + @Override + public void dispose() { + subscribers.getAndSet(TERMINATED); + current.compareAndSet(this, null); + SubscriptionHelper.cancel(upstream); + } + + @Override + public boolean isDisposed() { + return subscribers.get() == TERMINATED; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = qs; + done = true; + drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = qs; + s.request(bufferSize); + return; + } + } + + queue = new SpscArrayQueue(bufferSize); + + s.request(bufferSize); + } + } + + @Override + public void onNext(T t) { + // we expect upstream to honor backpressure requests + if (sourceMode == QueueSubscription.NONE && !queue.offer(t)) { + onError(new MissingBackpressureException("Prefetch queue is full?!")); + return; + } + // since many things can happen concurrently, we have a common dispatch + // loop to act on the current state serially + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + } else { + error = t; + done = true; + drain(); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + SimpleQueue queue = this.queue; + int consumed = this.consumed; + int limit = this.bufferSize - (this.bufferSize >> 2); + boolean async = this.sourceMode != QueueSubscription.SYNC; + + outer: + for (;;) { + if (queue != null) { + long minDemand = Long.MAX_VALUE; + boolean hasDemand = false; + + InnerSubscription[] innerSubscriptions = subscribers.get(); + + for (InnerSubscription inner : innerSubscriptions) { + long request = inner.get(); + if (request != Long.MIN_VALUE) { + hasDemand = true; + minDemand = Math.min(request - inner.emitted, minDemand); + } + } + + if (!hasDemand) { + minDemand = 0L; + } + + while (minDemand != 0L) { + boolean d = done; + T v; + + try { + v = queue.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().cancel(); + queue.clear(); + done = true; + signalError(ex); + return; + } + + boolean empty = v == null; + + if (checkTerminated(d, empty)) { + return; + } + + if (empty) { + break; + } + + for (InnerSubscription inner : innerSubscriptions) { + if (!inner.isCancelled()) { + inner.downstream.onNext(v); + inner.emitted++; + } + } + + if (async && ++consumed == limit) { + consumed = 0; + upstream.get().request(limit); + } + minDemand--; + + if (innerSubscriptions != subscribers.get()) { + continue outer; + } + } + + if (checkTerminated(done, queue.isEmpty())) { + return; + } + } + + this.consumed = consumed; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + if (queue == null) { + queue = this.queue; + } + } + } + + @SuppressWarnings("unchecked") + boolean checkTerminated(boolean isDone, boolean isEmpty) { + if (isDone && isEmpty) { + Throwable ex = error; + + if (ex != null) { + signalError(ex); + } else { + for (InnerSubscription inner : subscribers.getAndSet(TERMINATED)) { + if (!inner.isCancelled()) { + inner.downstream.onComplete(); + } + } + } + return true; + } + return false; + } + + @SuppressWarnings("unchecked") + void signalError(Throwable ex) { + for (InnerSubscription inner : subscribers.getAndSet(TERMINATED)) { + if (!inner.isCancelled()) { + inner.downstream.onError(ex); + } + } + } + + boolean add(InnerSubscription inner) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // get the current producer array + InnerSubscription[] c = subscribers.get(); + // if this subscriber-to-source reached a terminal state by receiving + // an onError or onComplete, just refuse to add the new producer + if (c == TERMINATED) { + return false; + } + // we perform a copy-on-write logic + int len = c.length; + @SuppressWarnings("unchecked") + InnerSubscription[] u = new InnerSubscription[len + 1]; + System.arraycopy(c, 0, u, 0, len); + u[len] = inner; + // try setting the subscribers array + if (subscribers.compareAndSet(c, u)) { + return true; + } + // if failed, some other operation succeeded (another add, remove or termination) + // so retry + } + } + + @SuppressWarnings("unchecked") + void remove(InnerSubscription inner) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // let's read the current subscribers array + InnerSubscription[] c = subscribers.get(); + int len = c.length; + // if it is either empty or terminated, there is nothing to remove so we quit + if (len == 0) { + break; + } + // let's find the supplied producer in the array + // although this is O(n), we don't expect too many child subscribers in general + int j = -1; + for (int i = 0; i < len; i++) { + if (c[i] == inner) { + j = i; + break; + } + } + // we didn't find it so just quit + if (j < 0) { + return; + } + // we do copy-on-write logic here + InnerSubscription[] u; + // we don't create a new empty array if producer was the single inhabitant + // but rather reuse an empty array + if (len == 1) { + u = EMPTY; + } else { + // otherwise, create a new array one less in size + u = new InnerSubscription[len - 1]; + // copy elements being before the given producer + System.arraycopy(c, 0, u, 0, j); + // copy elements being after the given producer + System.arraycopy(c, j + 1, u, j, len - j - 1); + } + // try setting this new array as + if (subscribers.compareAndSet(c, u)) { + break; + } + // if we failed, it means something else happened + // (a concurrent add/remove or termination), we need to retry + } + } + } + + static final class InnerSubscription extends AtomicLong + implements Subscription { + + private static final long serialVersionUID = 2845000326761540265L; + + final Subscriber downstream; + + final PublishConnection parent; + + long emitted; + + InnerSubscription(Subscriber downstream, PublishConnection parent) { + this.downstream = downstream; + this.parent = parent; + } + + @Override + public void request(long n) { + BackpressureHelper.addCancel(this, n); + parent.drain(); + } + + @Override + public void cancel() { + if (getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + parent.drain(); + } + } + + public boolean isCancelled() { + return get() == Long.MIN_VALUE; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishClassic.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishClassic.java new file mode 100755 index 0000000..27ded26 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishClassic.java @@ -0,0 +1,43 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Publisher; + +/** + * Interface to mark classic publish() operators to + * indicate refCount() should replace them with the Alt + * implementation. + *

+ * Without this, hooking the connectables with an intercept + * implementation would result in the unintended lack + * or presense of the replacement by refCount(). + * + * @param the element type of the sequence + * @since 2.2.10 + */ +public interface FlowablePublishClassic { + + /** + * The upstream source of this publish operator. + * @return the upstream source of this publish operator + */ + Publisher publishSource(); + + /** + * The internal buffer size of this publish operator. + * @return the internal buffer size of this publish operator + */ + int publishBufferSize(); +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java new file mode 100755 index 0000000..46a2dbd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishMulticast.java @@ -0,0 +1,520 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Multicasts a Flowable over a selector function. + * + * @param the input value type + * @param the output value type + */ +public final class FlowablePublishMulticast extends AbstractFlowableWithUpstream { + + final Function, ? extends Publisher> selector; + + final int prefetch; + + final boolean delayError; + + public FlowablePublishMulticast(Flowable source, + Function, ? extends Publisher> selector, int prefetch, + boolean delayError) { + super(source); + this.selector = selector; + this.prefetch = prefetch; + this.delayError = delayError; + } + + @Override + protected void subscribeActual(Subscriber s) { + MulticastProcessor mp = new MulticastProcessor(prefetch, delayError); + + Publisher other; + + try { + other = ObjectHelper.requireNonNull(selector.apply(mp), "selector returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, s); + return; + } + + OutputCanceller out = new OutputCanceller(s, mp); + + other.subscribe(out); + + source.subscribe(mp); + } + + static final class OutputCanceller implements FlowableSubscriber, Subscription { + final Subscriber downstream; + + final MulticastProcessor processor; + + Subscription upstream; + + OutputCanceller(Subscriber actual, MulticastProcessor processor) { + this.downstream = actual; + this.processor = processor; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + processor.dispose(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + processor.dispose(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + processor.dispose(); + } + } + + static final class MulticastProcessor extends Flowable implements FlowableSubscriber, Disposable { + + @SuppressWarnings("rawtypes") + static final MulticastSubscription[] EMPTY = new MulticastSubscription[0]; + + @SuppressWarnings("rawtypes") + static final MulticastSubscription[] TERMINATED = new MulticastSubscription[0]; + + final AtomicInteger wip; + + final AtomicReference[]> subscribers; + + final int prefetch; + + final int limit; + + final boolean delayError; + + final AtomicReference upstream; + + volatile SimpleQueue queue; + + int sourceMode; + + volatile boolean done; + Throwable error; + + int consumed; + + @SuppressWarnings("unchecked") + MulticastProcessor(int prefetch, boolean delayError) { + this.prefetch = prefetch; + this.limit = prefetch - (prefetch >> 2); // request after 75% consumption + this.delayError = delayError; + this.wip = new AtomicInteger(); + this.upstream = new AtomicReference(); + this.subscribers = new AtomicReference[]>(EMPTY); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY); + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = qs; + done = true; + drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = qs; + QueueDrainHelper.request(s, prefetch); + return; + } + } + + queue = QueueDrainHelper.createQueue(prefetch); + + QueueDrainHelper.request(s, prefetch); + } + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(upstream); + if (wip.getAndIncrement() == 0) { + SimpleQueue q = queue; + if (q != null) { + q.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return upstream.get() == SubscriptionHelper.CANCELLED; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (sourceMode == QueueSubscription.NONE && !queue.offer(t)) { + upstream.get().cancel(); + onError(new MissingBackpressureException()); + return; + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + drain(); + } + } + + boolean add(MulticastSubscription s) { + for (;;) { + MulticastSubscription[] current = subscribers.get(); + if (current == TERMINATED) { + return false; + } + int n = current.length; + @SuppressWarnings("unchecked") + MulticastSubscription[] next = new MulticastSubscription[n + 1]; + System.arraycopy(current, 0, next, 0, n); + next[n] = s; + if (subscribers.compareAndSet(current, next)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(MulticastSubscription s) { + for (;;) { + MulticastSubscription[] current = subscribers.get(); + int n = current.length; + if (n == 0) { + return; + } + int j = -1; + + for (int i = 0; i < n; i++) { + if (current[i] == s) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + MulticastSubscription[] next; + if (n == 1) { + next = EMPTY; + } else { + next = new MulticastSubscription[n - 1]; + System.arraycopy(current, 0, next, 0, j); + System.arraycopy(current, j + 1, next, j, n - j - 1); + } + if (subscribers.compareAndSet(current, next)) { + return; + } + } + } + + @Override + protected void subscribeActual(Subscriber s) { + MulticastSubscription ms = new MulticastSubscription(s, this); + s.onSubscribe(ms); + if (add(ms)) { + if (ms.isCancelled()) { + remove(ms); + return; + } + drain(); + } else { + Throwable ex = error; + if (ex != null) { + s.onError(ex); + } else { + s.onComplete(); + } + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + + SimpleQueue q = queue; + + int upstreamConsumed = consumed; + int localLimit = limit; + boolean canRequest = sourceMode != QueueSubscription.SYNC; + AtomicReference[]> subs = subscribers; + + MulticastSubscription[] array = subs.get(); + + outer: + for (;;) { + + int n = array.length; + + if (q != null && n != 0) { + long r = Long.MAX_VALUE; + + for (MulticastSubscription ms : array) { + long u = ms.get() - ms.emitted; + if (u != Long.MIN_VALUE) { + if (r > u) { + r = u; + } + } else { + n--; + } + } + + if (n == 0) { + r = 0; + } + + while (r != 0) { + if (isDisposed()) { + q.clear(); + return; + } + + boolean d = done; + + if (d && !delayError) { + Throwable ex = error; + if (ex != null) { + errorAll(ex); + return; + } + } + + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + SubscriptionHelper.cancel(upstream); + errorAll(ex); + return; + } + + boolean empty = v == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + errorAll(ex); + } else { + completeAll(); + } + return; + } + + if (empty) { + break; + } + + boolean subscribersChange = false; + + for (MulticastSubscription ms : array) { + long msr = ms.get(); + if (msr != Long.MIN_VALUE) { + if (msr != Long.MAX_VALUE) { + ms.emitted++; + } + ms.downstream.onNext(v); + } else { + subscribersChange = true; + } + } + + r--; + + if (canRequest && ++upstreamConsumed == localLimit) { + upstreamConsumed = 0; + upstream.get().request(localLimit); + } + + MulticastSubscription[] freshArray = subs.get(); + if (subscribersChange || freshArray != array) { + array = freshArray; + continue outer; + } + } + + if (r == 0) { + if (isDisposed()) { + q.clear(); + return; + } + + boolean d = done; + + if (d && !delayError) { + Throwable ex = error; + if (ex != null) { + errorAll(ex); + return; + } + } + + if (d && q.isEmpty()) { + Throwable ex = error; + if (ex != null) { + errorAll(ex); + } else { + completeAll(); + } + return; + } + } + } + + consumed = upstreamConsumed; + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + if (q == null) { + q = queue; + } + array = subs.get(); + } + } + + @SuppressWarnings("unchecked") + void errorAll(Throwable ex) { + for (MulticastSubscription ms : subscribers.getAndSet(TERMINATED)) { + if (ms.get() != Long.MIN_VALUE) { + ms.downstream.onError(ex); + } + } + } + + @SuppressWarnings("unchecked") + void completeAll() { + for (MulticastSubscription ms : subscribers.getAndSet(TERMINATED)) { + if (ms.get() != Long.MIN_VALUE) { + ms.downstream.onComplete(); + } + } + } + } + + static final class MulticastSubscription + extends AtomicLong + implements Subscription { + + private static final long serialVersionUID = 8664815189257569791L; + + final Subscriber downstream; + + final MulticastProcessor parent; + + long emitted; + + MulticastSubscription(Subscriber actual, MulticastProcessor parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.addCancel(this, n); + parent.drain(); + } + } + + @Override + public void cancel() { + if (getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + parent.drain(); // unblock the others + } + } + + public boolean isCancelled() { + return get() == Long.MIN_VALUE; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java new file mode 100755 index 0000000..d4b6b50 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRange.java @@ -0,0 +1,244 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; + +/** + * Emits a range of integer values. + */ +public final class FlowableRange extends Flowable { + final int start; + final int end; + public FlowableRange(int start, int count) { + this.start = start; + this.end = start + count; + } + + @Override + public void subscribeActual(Subscriber s) { + if (s instanceof ConditionalSubscriber) { + s.onSubscribe(new RangeConditionalSubscription( + (ConditionalSubscriber)s, start, end)); + } else { + s.onSubscribe(new RangeSubscription(s, start, end)); + } + } + + abstract static class BaseRangeSubscription extends BasicQueueSubscription { + private static final long serialVersionUID = -2252972430506210021L; + + final int end; + + int index; + + volatile boolean cancelled; + + BaseRangeSubscription(int index, int end) { + this.index = index; + this.end = end; + } + + @Override + public final int requestFusion(int mode) { + return mode & SYNC; + } + + @Nullable + @Override + public final Integer poll() { + int i = index; + if (i == end) { + return null; + } + index = i + 1; + return i; + } + + @Override + public final boolean isEmpty() { + return index == end; + } + + @Override + public final void clear() { + index = end; + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + if (BackpressureHelper.add(this, n) == 0L) { + if (n == Long.MAX_VALUE) { + fastPath(); + } else { + slowPath(n); + } + } + } + } + + @Override + public final void cancel() { + cancelled = true; + } + + abstract void fastPath(); + + abstract void slowPath(long r); + } + + static final class RangeSubscription extends BaseRangeSubscription { + + private static final long serialVersionUID = 2587302975077663557L; + + final Subscriber downstream; + + RangeSubscription(Subscriber actual, int index, int end) { + super(index, end); + this.downstream = actual; + } + + @Override + void fastPath() { + int f = end; + Subscriber a = downstream; + + for (int i = index; i != f; i++) { + if (cancelled) { + return; + } + a.onNext(i); + } + if (cancelled) { + return; + } + a.onComplete(); + } + + @Override + void slowPath(long r) { + long e = 0; + int f = end; + int i = index; + Subscriber a = downstream; + + for (;;) { + + while (e != r && i != f) { + if (cancelled) { + return; + } + + a.onNext(i); + + e++; + i++; + } + + if (i == f) { + if (!cancelled) { + a.onComplete(); + } + return; + } + + r = get(); + if (e == r) { + index = i; + r = addAndGet(-e); + if (r == 0L) { + return; + } + e = 0L; + } + } + } + } + + static final class RangeConditionalSubscription extends BaseRangeSubscription { + + private static final long serialVersionUID = 2587302975077663557L; + + final ConditionalSubscriber downstream; + + RangeConditionalSubscription(ConditionalSubscriber actual, int index, int end) { + super(index, end); + this.downstream = actual; + } + + @Override + void fastPath() { + int f = end; + ConditionalSubscriber a = downstream; + + for (int i = index; i != f; i++) { + if (cancelled) { + return; + } + a.tryOnNext(i); + } + if (cancelled) { + return; + } + a.onComplete(); + } + + @Override + void slowPath(long r) { + long e = 0; + int f = end; + int i = index; + ConditionalSubscriber a = downstream; + + for (;;) { + + while (e != r && i != f) { + if (cancelled) { + return; + } + + if (a.tryOnNext(i)) { + e++; + } + + i++; + } + + if (i == f) { + if (!cancelled) { + a.onComplete(); + } + return; + } + + r = get(); + if (e == r) { + index = i; + r = addAndGet(-e); + if (r == 0) { + return; + } + e = 0; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java new file mode 100755 index 0000000..d641e6a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRangeLong.java @@ -0,0 +1,246 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; + +/** + * Emits a range of long values. + */ +public final class FlowableRangeLong extends Flowable { + final long start; + final long end; + + public FlowableRangeLong(long start, long count) { + this.start = start; + this.end = start + count; + } + + @Override + public void subscribeActual(Subscriber s) { + if (s instanceof ConditionalSubscriber) { + s.onSubscribe(new RangeConditionalSubscription( + (ConditionalSubscriber)s, start, end)); + } else { + s.onSubscribe(new RangeSubscription(s, start, end)); + } + } + + abstract static class BaseRangeSubscription extends BasicQueueSubscription { + + private static final long serialVersionUID = -2252972430506210021L; + + final long end; + + long index; + + volatile boolean cancelled; + + BaseRangeSubscription(long index, long end) { + this.index = index; + this.end = end; + } + + @Override + public final int requestFusion(int mode) { + return mode & SYNC; + } + + @Nullable + @Override + public final Long poll() { + long i = index; + if (i == end) { + return null; + } + index = i + 1; + return i; + } + + @Override + public final boolean isEmpty() { + return index == end; + } + + @Override + public final void clear() { + index = end; + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + if (BackpressureHelper.add(this, n) == 0L) { + if (n == Long.MAX_VALUE) { + fastPath(); + } else { + slowPath(n); + } + } + } + } + + @Override + public final void cancel() { + cancelled = true; + } + + abstract void fastPath(); + + abstract void slowPath(long r); + } + + static final class RangeSubscription extends BaseRangeSubscription { + + private static final long serialVersionUID = 2587302975077663557L; + + final Subscriber downstream; + + RangeSubscription(Subscriber actual, long index, long end) { + super(index, end); + this.downstream = actual; + } + + @Override + void fastPath() { + long f = end; + Subscriber a = downstream; + + for (long i = index; i != f; i++) { + if (cancelled) { + return; + } + a.onNext(i); + } + if (cancelled) { + return; + } + a.onComplete(); + } + + @Override + void slowPath(long r) { + long e = 0; + long f = end; + long i = index; + Subscriber a = downstream; + + for (;;) { + + while (e != r && i != f) { + if (cancelled) { + return; + } + + a.onNext(i); + + e++; + i++; + } + + if (i == f) { + if (!cancelled) { + a.onComplete(); + } + return; + } + + r = get(); + if (e == r) { + index = i; + r = addAndGet(-e); + if (r == 0L) { + return; + } + e = 0L; + } + } + } + } + + static final class RangeConditionalSubscription extends BaseRangeSubscription { + + private static final long serialVersionUID = 2587302975077663557L; + + final ConditionalSubscriber downstream; + + RangeConditionalSubscription(ConditionalSubscriber actual, long index, long end) { + super(index, end); + this.downstream = actual; + } + + @Override + void fastPath() { + long f = end; + ConditionalSubscriber a = downstream; + + for (long i = index; i != f; i++) { + if (cancelled) { + return; + } + a.tryOnNext(i); + } + if (cancelled) { + return; + } + a.onComplete(); + } + + @Override + void slowPath(long r) { + long e = 0; + long f = end; + long i = index; + ConditionalSubscriber a = downstream; + + for (;;) { + + while (e != r && i != f) { + if (cancelled) { + return; + } + + if (a.tryOnNext(i)) { + e++; + } + + i++; + } + + if (i == f) { + if (!cancelled) { + a.onComplete(); + } + return; + } + + r = get(); + if (e == r) { + index = i; + r = addAndGet(-e); + if (r == 0) { + return; + } + e = 0; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduce.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduce.java new file mode 100755 index 0000000..67be1d1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduce.java @@ -0,0 +1,122 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Reduces a sequence via a function into a single value or signals NoSuchElementException for + * an empty source. + * + * @param the value type + */ +public final class FlowableReduce extends AbstractFlowableWithUpstream { + + final BiFunction reducer; + + public FlowableReduce(Flowable source, BiFunction reducer) { + super(source); + this.reducer = reducer; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ReduceSubscriber(s, reducer)); + } + + static final class ReduceSubscriber extends DeferredScalarSubscription implements FlowableSubscriber { + + private static final long serialVersionUID = -4663883003264602070L; + + final BiFunction reducer; + + Subscription upstream; + + ReduceSubscriber(Subscriber actual, BiFunction reducer) { + super(actual); + this.reducer = reducer; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (upstream == SubscriptionHelper.CANCELLED) { + return; + } + + T v = value; + if (v == null) { + value = t; + } else { + try { + value = ObjectHelper.requireNonNull(reducer.apply(v, t), "The reducer returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + } + } + } + + @Override + public void onError(Throwable t) { + if (upstream == SubscriptionHelper.CANCELLED) { + RxJavaPlugins.onError(t); + return; + } + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (upstream == SubscriptionHelper.CANCELLED) { + return; + } + upstream = SubscriptionHelper.CANCELLED; + + T v = value; + if (v != null) { + complete(v); + } else { + downstream.onComplete(); + } + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java new file mode 100755 index 0000000..569dcdc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceMaybe.java @@ -0,0 +1,142 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Reduce a Flowable into a single value exposed as Single or signal NoSuchElementException. + * + * @param the value type + */ +public final class FlowableReduceMaybe +extends Maybe +implements HasUpstreamPublisher, FuseToFlowable { + + final Flowable source; + + final BiFunction reducer; + + public FlowableReduceMaybe(Flowable source, BiFunction reducer) { + this.source = source; + this.reducer = reducer; + } + + @Override + public Publisher source() { + return source; + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableReduce(source, reducer)); + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new ReduceSubscriber(observer, reducer)); + } + + static final class ReduceSubscriber implements FlowableSubscriber, Disposable { + final MaybeObserver downstream; + + final BiFunction reducer; + + T value; + + Subscription upstream; + + boolean done; + + ReduceSubscriber(MaybeObserver actual, BiFunction reducer) { + this.downstream = actual; + this.reducer = reducer; + } + + @Override + public void dispose() { + upstream.cancel(); + done = true; + } + + @Override + public boolean isDisposed() { + return done; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + T v = value; + if (v == null) { + value = t; + } else { + try { + value = ObjectHelper.requireNonNull(reducer.apply(v, t), "The reducer returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + } + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + T v = value; + if (v != null) { +// value = null; + downstream.onSuccess(v); + } else { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceSeedSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceSeedSingle.java new file mode 100755 index 0000000..bcd1042 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceSeedSingle.java @@ -0,0 +1,125 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Reduce a sequence of values, starting from a seed value and by using + * an accumulator function and return the last accumulated value. + * + * @param the source value type + * @param the accumulated result type + */ +public final class FlowableReduceSeedSingle extends Single { + + final Publisher source; + + final R seed; + + final BiFunction reducer; + + public FlowableReduceSeedSingle(Publisher source, R seed, BiFunction reducer) { + this.source = source; + this.seed = seed; + this.reducer = reducer; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new ReduceSeedObserver(observer, reducer, seed)); + } + + static final class ReduceSeedObserver implements FlowableSubscriber, Disposable { + + final SingleObserver downstream; + + final BiFunction reducer; + + R value; + + Subscription upstream; + + ReduceSeedObserver(SingleObserver actual, BiFunction reducer, R value) { + this.downstream = actual; + this.value = value; + this.reducer = reducer; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T value) { + R v = this.value; + if (v != null) { + try { + this.value = ObjectHelper.requireNonNull(reducer.apply(v, value), "The reducer returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + } + } + } + + @Override + public void onError(Throwable e) { + if (value != null) { + value = null; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + R v = value; + if (v != null) { + value = null; + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(v); + } + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceWithSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceWithSingle.java new file mode 100755 index 0000000..321f274 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReduceWithSingle.java @@ -0,0 +1,61 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; + +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.flowable.FlowableReduceSeedSingle.ReduceSeedObserver; + +/** + * Reduce a sequence of values, starting from a generated seed value and by using + * an accumulator function and return the last accumulated value. + * + * @param the source value type + * @param the accumulated result type + */ +public final class FlowableReduceWithSingle extends Single { + + final Publisher source; + + final Callable seedSupplier; + + final BiFunction reducer; + + public FlowableReduceWithSingle(Publisher source, Callable seedSupplier, BiFunction reducer) { + this.source = source; + this.seedSupplier = seedSupplier; + this.reducer = reducer; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + R seed; + + try { + seed = ObjectHelper.requireNonNull(seedSupplier.call(), "The seedSupplier returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + source.subscribe(new ReduceSeedObserver(observer, reducer, seed)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java new file mode 100755 index 0000000..2da1306 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java @@ -0,0 +1,272 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Returns an observable sequence that stays connected to the source as long as + * there is at least one subscription to the observable sequence. + * + * @param + * the value type + */ +public final class FlowableRefCount extends Flowable { + + final ConnectableFlowable source; + + final int n; + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + RefConnection connection; + + public FlowableRefCount(ConnectableFlowable source) { + this(source, 1, 0L, TimeUnit.NANOSECONDS, null); + } + + public FlowableRefCount(ConnectableFlowable source, int n, long timeout, TimeUnit unit, + Scheduler scheduler) { + this.source = source; + this.n = n; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(Subscriber s) { + + RefConnection conn; + + boolean connect = false; + synchronized (this) { + conn = connection; + if (conn == null) { + conn = new RefConnection(this); + connection = conn; + } + + long c = conn.subscriberCount; + if (c == 0L && conn.timer != null) { + conn.timer.dispose(); + } + conn.subscriberCount = c + 1; + if (!conn.connected && c + 1 == n) { + connect = true; + conn.connected = true; + } + } + + source.subscribe(new RefCountSubscriber(s, this, conn)); + + if (connect) { + source.connect(conn); + } + } + + void cancel(RefConnection rc) { + SequentialDisposable sd; + synchronized (this) { + if (connection == null || connection != rc) { + return; + } + long c = rc.subscriberCount - 1; + rc.subscriberCount = c; + if (c != 0L || !rc.connected) { + return; + } + if (timeout == 0L) { + timeout(rc); + return; + } + sd = new SequentialDisposable(); + rc.timer = sd; + } + + sd.replace(scheduler.scheduleDirect(rc, timeout, unit)); + } + + void terminated(RefConnection rc) { + synchronized (this) { + if (source instanceof FlowablePublishClassic) { + if (connection != null && connection == rc) { + connection = null; + clearTimer(rc); + } + + if (--rc.subscriberCount == 0) { + reset(rc); + } + } else { + if (connection != null && connection == rc) { + clearTimer(rc); + if (--rc.subscriberCount == 0) { + connection = null; + reset(rc); + } + } + } + } + } + + void clearTimer(RefConnection rc) { + if (rc.timer != null) { + rc.timer.dispose(); + rc.timer = null; + } + } + + void reset(RefConnection rc) { + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + ((ResettableConnectable)source).resetIf(rc.get()); + } + } + + void timeout(RefConnection rc) { + synchronized (this) { + if (rc.subscriberCount == 0 && rc == connection) { + connection = null; + Disposable connectionObject = rc.get(); + DisposableHelper.dispose(rc); + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + if (connectionObject == null) { + rc.disconnectedEarly = true; + } else { + ((ResettableConnectable)source).resetIf(connectionObject); + } + } + } + } + } + + static final class RefConnection extends AtomicReference + implements Runnable, Consumer { + + private static final long serialVersionUID = -4552101107598366241L; + + final FlowableRefCount parent; + + Disposable timer; + + long subscriberCount; + + boolean connected; + + boolean disconnectedEarly; + + RefConnection(FlowableRefCount parent) { + this.parent = parent; + } + + @Override + public void run() { + parent.timeout(this); + } + + @Override + public void accept(Disposable t) throws Exception { + DisposableHelper.replace(this, t); + synchronized (parent) { + if (disconnectedEarly) { + ((ResettableConnectable)parent.source).resetIf(t); + } + } + } + } + + static final class RefCountSubscriber + extends AtomicBoolean implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -7419642935409022375L; + + final Subscriber downstream; + + final FlowableRefCount parent; + + final RefConnection connection; + + Subscription upstream; + + RefCountSubscriber(Subscriber actual, FlowableRefCount parent, RefConnection connection) { + this.downstream = actual; + this.parent = parent; + this.connection = connection; + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (compareAndSet(false, true)) { + parent.terminated(connection); + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (compareAndSet(false, true)) { + parent.terminated(connection); + downstream.onComplete(); + } + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + if (compareAndSet(false, true)) { + parent.cancel(connection); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java new file mode 100755 index 0000000..1bbdd7c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeat.java @@ -0,0 +1,111 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionArbiter; + +public final class FlowableRepeat extends AbstractFlowableWithUpstream { + final long count; + public FlowableRepeat(Flowable source, long count) { + super(source); + this.count = count; + } + + @Override + public void subscribeActual(Subscriber s) { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + s.onSubscribe(sa); + + RepeatSubscriber rs = new RepeatSubscriber(s, count != Long.MAX_VALUE ? count - 1 : Long.MAX_VALUE, sa, source); + rs.subscribeNext(); + } + + static final class RepeatSubscriber extends AtomicInteger implements FlowableSubscriber { + + private static final long serialVersionUID = -7098360935104053232L; + + final Subscriber downstream; + final SubscriptionArbiter sa; + final Publisher source; + long remaining; + + long produced; + + RepeatSubscriber(Subscriber actual, long count, SubscriptionArbiter sa, Publisher source) { + this.downstream = actual; + this.sa = sa; + this.source = source; + this.remaining = count; + } + + @Override + public void onSubscribe(Subscription s) { + sa.setSubscription(s); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + long r = remaining; + if (r != Long.MAX_VALUE) { + remaining = r - 1; + } + if (r != 0L) { + subscribeNext(); + } else { + downstream.onComplete(); + } + } + + /** + * Subscribes to the source again via trampolining. + */ + void subscribeNext() { + if (getAndIncrement() == 0) { + int missed = 1; + for (;;) { + if (sa.isCancelled()) { + return; + } + long p = produced; + if (p != 0L) { + produced = 0L; + sa.produced(p); + } + source.subscribe(this); + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java new file mode 100755 index 0000000..9c7057a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatUntil.java @@ -0,0 +1,119 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BooleanSupplier; +import io.reactivex.internal.subscriptions.SubscriptionArbiter; + +public final class FlowableRepeatUntil extends AbstractFlowableWithUpstream { + final BooleanSupplier until; + public FlowableRepeatUntil(Flowable source, BooleanSupplier until) { + super(source); + this.until = until; + } + + @Override + public void subscribeActual(Subscriber s) { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + s.onSubscribe(sa); + + RepeatSubscriber rs = new RepeatSubscriber(s, until, sa, source); + rs.subscribeNext(); + } + + static final class RepeatSubscriber extends AtomicInteger implements FlowableSubscriber { + + private static final long serialVersionUID = -7098360935104053232L; + + final Subscriber downstream; + final SubscriptionArbiter sa; + final Publisher source; + final BooleanSupplier stop; + + long produced; + + RepeatSubscriber(Subscriber actual, BooleanSupplier until, SubscriptionArbiter sa, Publisher source) { + this.downstream = actual; + this.sa = sa; + this.source = source; + this.stop = until; + } + + @Override + public void onSubscribe(Subscription s) { + sa.setSubscription(s); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + boolean b; + try { + b = stop.getAsBoolean(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + if (b) { + downstream.onComplete(); + } else { + subscribeNext(); + } + } + + /** + * Subscribes to the source again via trampolining. + */ + void subscribeNext() { + if (getAndIncrement() == 0) { + int missed = 1; + for (;;) { + if (sa.isCancelled()) { + return; + } + + long p = produced; + if (p != 0L) { + produced = 0L; + sa.produced(p); + } + + source.subscribe(this); + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java new file mode 100755 index 0000000..b622541 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRepeatWhen.java @@ -0,0 +1,201 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.processors.*; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableRepeatWhen extends AbstractFlowableWithUpstream { + final Function, ? extends Publisher> handler; + + public FlowableRepeatWhen(Flowable source, + Function, ? extends Publisher> handler) { + super(source); + this.handler = handler; + } + + @Override + public void subscribeActual(Subscriber s) { + + SerializedSubscriber z = new SerializedSubscriber(s); + + FlowableProcessor processor = UnicastProcessor.create(8).toSerialized(); + + Publisher when; + + try { + when = ObjectHelper.requireNonNull(handler.apply(processor), "handler returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, s); + return; + } + + WhenReceiver receiver = new WhenReceiver(source); + + RepeatWhenSubscriber subscriber = new RepeatWhenSubscriber(z, processor, receiver); + + receiver.subscriber = subscriber; + + s.onSubscribe(subscriber); + + when.subscribe(receiver); + + receiver.onNext(0); + } + + static final class WhenReceiver + extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 2827772011130406689L; + + final Publisher source; + + final AtomicReference upstream; + + final AtomicLong requested; + + WhenSourceSubscriber subscriber; + + WhenReceiver(Publisher source) { + this.source = source; + this.upstream = new AtomicReference(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(upstream, requested, s); + } + + @Override + public void onNext(Object t) { + if (getAndIncrement() == 0) { + for (;;) { + if (upstream.get() == SubscriptionHelper.CANCELLED) { + return; + } + + source.subscribe(subscriber); + + if (decrementAndGet() == 0) { + break; + } + } + } + } + + @Override + public void onError(Throwable t) { + subscriber.cancel(); + subscriber.downstream.onError(t); + } + + @Override + public void onComplete() { + subscriber.cancel(); + subscriber.downstream.onComplete(); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + } + } + + abstract static class WhenSourceSubscriber extends SubscriptionArbiter implements FlowableSubscriber { + + private static final long serialVersionUID = -5604623027276966720L; + + protected final Subscriber downstream; + + protected final FlowableProcessor processor; + + protected final Subscription receiver; + + private long produced; + + WhenSourceSubscriber(Subscriber actual, FlowableProcessor processor, + Subscription receiver) { + super(false); + this.downstream = actual; + this.processor = processor; + this.receiver = receiver; + } + + @Override + public final void onSubscribe(Subscription s) { + setSubscription(s); + } + + @Override + public final void onNext(T t) { + produced++; + downstream.onNext(t); + } + + protected final void again(U signal) { + setSubscription(EmptySubscription.INSTANCE); + long p = produced; + if (p != 0L) { + produced = 0L; + produced(p); + } + receiver.request(1); + processor.onNext(signal); + } + + @Override + public final void cancel() { + super.cancel(); + receiver.cancel(); + } + } + + static final class RepeatWhenSubscriber extends WhenSourceSubscriber { + + private static final long serialVersionUID = -2680129890138081029L; + + RepeatWhenSubscriber(Subscriber actual, FlowableProcessor processor, + Subscription receiver) { + super(actual, processor, receiver); + } + + @Override + public void onError(Throwable t) { + receiver.cancel(); + downstream.onError(t); + } + + @Override + public void onComplete() { + again(0); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java new file mode 100755 index 0000000..196596f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java @@ -0,0 +1,1274 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.ResettableConnectable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.HasUpstreamPublisher; +import io.reactivex.internal.subscribers.SubscriberResourceWrapper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Timed; + +public final class FlowableReplay extends ConnectableFlowable implements HasUpstreamPublisher, ResettableConnectable { + /** The source observable. */ + final Flowable source; + /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ + final AtomicReference> current; + /** A factory that creates the appropriate buffer for the ReplaySubscriber. */ + final Callable> bufferFactory; + + final Publisher onSubscribe; + + @SuppressWarnings("rawtypes") + static final Callable DEFAULT_UNBOUNDED_FACTORY = new DefaultUnboundedFactory(); + + /** + * Given a connectable observable factory, it multicasts over the generated + * ConnectableObservable via a selector function. + * @param the connectable observable type + * @param the result type + * @param connectableFactory the factory that returns a ConnectableFlowable for each individual subscriber + * @param selector the function that receives a Flowable and should return another Flowable that will be subscribed to + * @return the new Observable instance + */ + public static Flowable multicastSelector( + final Callable> connectableFactory, + final Function, ? extends Publisher> selector) { + return new MulticastFlowable(connectableFactory, selector); + } + + /** + * Child Subscribers will observe the events of the ConnectableObservable on the + * specified scheduler. + * @param the value type + * @param cf the ConnectableFlowable to wrap + * @param scheduler the target scheduler + * @return the new ConnectableObservable instance + */ + public static ConnectableFlowable observeOn(final ConnectableFlowable cf, final Scheduler scheduler) { + final Flowable flowable = cf.observeOn(scheduler); + return RxJavaPlugins.onAssembly(new ConnectableFlowableReplay(cf, flowable)); + } + + /** + * Creates a replaying ConnectableObservable with an unbounded buffer. + * @param the value type + * @param source the source Publisher to use + * @return the new ConnectableObservable instance + */ + @SuppressWarnings("unchecked") + public static ConnectableFlowable createFrom(Flowable source) { + return create(source, DEFAULT_UNBOUNDED_FACTORY); + } + + /** + * Creates a replaying ConnectableObservable with a size bound buffer. + * @param the value type + * @param source the source Flowable to use + * @param bufferSize the maximum number of elements to hold + * @return the new ConnectableObservable instance + */ + public static ConnectableFlowable create(Flowable source, + final int bufferSize) { + if (bufferSize == Integer.MAX_VALUE) { + return createFrom(source); + } + return create(source, new ReplayBufferTask(bufferSize)); + } + + /** + * Creates a replaying ConnectableObservable with a time bound buffer. + * @param the value type + * @param source the source Flowable to use + * @param maxAge the maximum age of entries + * @param unit the unit of measure of the age amount + * @param scheduler the target scheduler providing the current time + * @return the new ConnectableObservable instance + */ + public static ConnectableFlowable create(Flowable source, + long maxAge, TimeUnit unit, Scheduler scheduler) { + return create(source, maxAge, unit, scheduler, Integer.MAX_VALUE); + } + + /** + * Creates a replaying ConnectableObservable with a size and time bound buffer. + * @param the value type + * @param source the source Flowable to use + * @param maxAge the maximum age of entries + * @param unit the unit of measure of the age amount + * @param scheduler the target scheduler providing the current time + * @param bufferSize the maximum number of elements to hold + * @return the new ConnectableFlowable instance + */ + public static ConnectableFlowable create(Flowable source, + final long maxAge, final TimeUnit unit, final Scheduler scheduler, final int bufferSize) { + return create(source, new ScheduledReplayBufferTask(bufferSize, maxAge, unit, scheduler)); + } + + /** + * Creates a OperatorReplay instance to replay values of the given source observable. + * @param source the source observable + * @param bufferFactory the factory to instantiate the appropriate buffer when the observable becomes active + * @return the connectable observable + */ + static ConnectableFlowable create(Flowable source, + final Callable> bufferFactory) { + // the current connection to source needs to be shared between the operator and its onSubscribe call + final AtomicReference> curr = new AtomicReference>(); + Publisher onSubscribe = new ReplayPublisher(curr, bufferFactory); + return RxJavaPlugins.onAssembly(new FlowableReplay(onSubscribe, source, curr, bufferFactory)); + } + + private FlowableReplay(Publisher onSubscribe, Flowable source, + final AtomicReference> current, + final Callable> bufferFactory) { + this.onSubscribe = onSubscribe; + this.source = source; + this.current = current; + this.bufferFactory = bufferFactory; + } + + @Override + public Publisher source() { + return source; + } + + @Override + protected void subscribeActual(Subscriber s) { + onSubscribe.subscribe(s); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void resetIf(Disposable connectionObject) { + current.compareAndSet((ReplaySubscriber)connectionObject, null); + } + + @Override + public void connect(Consumer connection) { + boolean doConnect; + ReplaySubscriber ps; + // we loop because concurrent connect/disconnect and termination may change the state + for (;;) { + // retrieve the current subscriber-to-source instance + ps = current.get(); + // if there is none yet or the current was disposed + if (ps == null || ps.isDisposed()) { + + ReplayBuffer buf; + + try { + buf = bufferFactory.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + + // create a new subscriber-to-source + ReplaySubscriber u = new ReplaySubscriber(buf); + // try setting it as the current subscriber-to-source + if (!current.compareAndSet(ps, u)) { + // did not work, perhaps a new subscriber arrived + // and created a new subscriber-to-source as well, retry + continue; + } + ps = u; + } + // if connect() was called concurrently, only one of them should actually + // connect to the source + doConnect = !ps.shouldConnect.get() && ps.shouldConnect.compareAndSet(false, true); + break; // NOPMD + } + /* + * Notify the callback that we have a (new) connection which it can dispose + * but since ps is unique to a connection, multiple calls to connect() will return the + * same Subscription and even if there was a connect-disconnect-connect pair, the older + * references won't disconnect the newer connection. + * Synchronous source consumers have the opportunity to disconnect via dispose on the + * Disposable as unsafeSubscribe may never return in its own. + * + * Note however, that asynchronously disconnecting a running source might leave + * child-subscribers without any terminal event; ReplaySubject does not have this + * issue because the cancellation was always triggered by the child-subscribers + * themselves. + */ + try { + connection.accept(ps); + } catch (Throwable ex) { + if (doConnect) { + ps.shouldConnect.compareAndSet(true, false); + } + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + if (doConnect) { + source.subscribe(ps); + } + } + + @SuppressWarnings("rawtypes") + static final class ReplaySubscriber + extends AtomicReference + implements FlowableSubscriber, Disposable { + private static final long serialVersionUID = 7224554242710036740L; + /** Holds notifications from upstream. */ + final ReplayBuffer buffer; + /** Indicates this Subscriber received a terminal event. */ + boolean done; + + /** Indicates an empty array of inner subscriptions. */ + static final InnerSubscription[] EMPTY = new InnerSubscription[0]; + /** Indicates a terminated ReplaySubscriber. */ + static final InnerSubscription[] TERMINATED = new InnerSubscription[0]; + + /** Tracks the subscribed InnerSubscriptions. */ + final AtomicReference[]> subscribers; + /** + * Atomically changed from false to true by connect to make sure the + * connection is only performed by one thread. + */ + final AtomicBoolean shouldConnect; + + final AtomicInteger management; + + /** Contains the maximum element index the child Subscribers requested so far. Accessed while emitting is true. */ + long maxChildRequested; + /** Counts the outstanding upstream requests until the producer arrives. */ + long maxUpstreamRequested; + + @SuppressWarnings("unchecked") + ReplaySubscriber(ReplayBuffer buffer) { + this.buffer = buffer; + this.management = new AtomicInteger(); + this.subscribers = new AtomicReference[]>(EMPTY); + this.shouldConnect = new AtomicBoolean(); + } + + @Override + public boolean isDisposed() { + return subscribers.get() == TERMINATED; + } + + @SuppressWarnings("unchecked") + @Override + public void dispose() { + subscribers.set(TERMINATED); + // unlike OperatorPublish, we can't null out the terminated so + // late subscribers can still get replay + // current.compareAndSet(ReplaySubscriber.this, null); + // we don't care if it fails because it means the current has + // been replaced in the meantime + SubscriptionHelper.cancel(this); + } + + /** + * Atomically try adding a new InnerSubscription to this Subscriber or return false if this + * Subscriber was terminated. + * @param producer the producer to add + * @return true if succeeded, false otherwise + */ + @SuppressWarnings("unchecked") + boolean add(InnerSubscription producer) { + if (producer == null) { + throw new NullPointerException(); + } + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // get the current producer array + InnerSubscription[] c = subscribers.get(); + // if this subscriber-to-source reached a terminal state by receiving + // an onError or onComplete, just refuse to add the new producer + if (c == TERMINATED) { + return false; + } + // we perform a copy-on-write logic + int len = c.length; + InnerSubscription[] u = new InnerSubscription[len + 1]; + System.arraycopy(c, 0, u, 0, len); + u[len] = producer; + // try setting the subscribers array + if (subscribers.compareAndSet(c, u)) { + return true; + } + // if failed, some other operation succeeded (another add, remove or termination) + // so retry + } + } + + /** + * Atomically removes the given InnerSubscription from the subscribers array. + * @param p the InnerSubscription to remove + */ + @SuppressWarnings("unchecked") + void remove(InnerSubscription p) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // let's read the current subscribers array + InnerSubscription[] c = subscribers.get(); + int len = c.length; + // if it is either empty or terminated, there is nothing to remove so we quit + if (len == 0) { + return; + } + // let's find the supplied producer in the array + // although this is O(n), we don't expect too many child subscribers in general + int j = -1; + for (int i = 0; i < len; i++) { + if (c[i].equals(p)) { + j = i; + break; + } + } + // we didn't find it so just quit + if (j < 0) { + return; + } + // we do copy-on-write logic here + InnerSubscription[] u; + // we don't create a new empty array if producer was the single inhabitant + // but rather reuse an empty array + if (len == 1) { + u = EMPTY; + } else { + // otherwise, create a new array one less in size + u = new InnerSubscription[len - 1]; + // copy elements being before the given producer + System.arraycopy(c, 0, u, 0, j); + // copy elements being after the given producer + System.arraycopy(c, j + 1, u, j, len - j - 1); + } + // try setting this new array as + if (subscribers.compareAndSet(c, u)) { + return; + } + // if we failed, it means something else happened + // (a concurrent add/remove or termination), we need to retry + } + } + + @Override + public void onSubscribe(Subscription p) { + if (SubscriptionHelper.setOnce(this, p)) { + manageRequests(); + for (InnerSubscription rp : subscribers.get()) { + buffer.replay(rp); + } + } + } + + @Override + public void onNext(T t) { + if (!done) { + buffer.next(t); + for (InnerSubscription rp : subscribers.get()) { + buffer.replay(rp); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable e) { + // The observer front is accessed serially as required by spec so + // no need to CAS in the terminal value + if (!done) { + done = true; + buffer.error(e); + for (InnerSubscription rp : subscribers.getAndSet(TERMINATED)) { + buffer.replay(rp); + } + } else { + RxJavaPlugins.onError(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + // The observer front is accessed serially as required by spec so + // no need to CAS in the terminal value + if (!done) { + done = true; + buffer.complete(); + for (InnerSubscription rp : subscribers.getAndSet(TERMINATED)) { + buffer.replay(rp); + } + } + } + + /** + * Coordinates the request amounts of various child Subscribers. + */ + void manageRequests() { + if (management.getAndIncrement() != 0) { + return; + } + int missed = 1; + for (;;) { + // if the upstream has completed, no more requesting is possible + if (isDisposed()) { + return; + } + + InnerSubscription[] a = subscribers.get(); + + long ri = maxChildRequested; + long maxTotalRequests = ri; + + for (InnerSubscription rp : a) { + maxTotalRequests = Math.max(maxTotalRequests, rp.totalRequested.get()); + } + + long ur = maxUpstreamRequested; + Subscription p = get(); + + long diff = maxTotalRequests - ri; + if (diff != 0L) { + maxChildRequested = maxTotalRequests; + if (p != null) { + if (ur != 0L) { + maxUpstreamRequested = 0L; + p.request(ur + diff); + } else { + p.request(diff); + } + } else { + // collect upstream request amounts until there is a producer for them + long u = ur + diff; + if (u < 0) { + u = Long.MAX_VALUE; + } + maxUpstreamRequested = u; + } + } else + // if there were outstanding upstream requests and we have a producer + if (ur != 0L && p != null) { + maxUpstreamRequested = 0L; + // fire the accumulated requests + p.request(ur); + } + + missed = management.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + /** + * A Subscription that manages the request and cancellation state of a + * child subscriber in thread-safe manner. + * @param the value type + */ + static final class InnerSubscription extends AtomicLong implements Subscription, Disposable { + + private static final long serialVersionUID = -4453897557930727610L; + /** + * The parent subscriber-to-source used to allow removing the child in case of + * child cancellation. + */ + final ReplaySubscriber parent; + /** The actual child subscriber. */ + final Subscriber child; + /** + * Holds an object that represents the current location in the buffer. + * Guarded by the emitter loop. + */ + Object index; + /** + * Keeps the sum of all requested amounts. + */ + final AtomicLong totalRequested; + /** Indicates an emission state. Guarded by this. */ + boolean emitting; + /** Indicates a missed update. Guarded by this. */ + boolean missed; + /** + * Indicates this child has been cancelled: the state is swapped in atomically and + * will prevent the dispatch() to emit (too many) values to a terminated child subscriber. + */ + static final long CANCELLED = Long.MIN_VALUE; + + InnerSubscription(ReplaySubscriber parent, Subscriber child) { + this.parent = parent; + this.child = child; + this.totalRequested = new AtomicLong(); + } + + @Override + public void request(long n) { + // ignore negative requests + if (SubscriptionHelper.validate(n)) { + // add to the current requested and cap it at MAX_VALUE + // except when there was a concurrent cancellation + if (BackpressureHelper.addCancel(this, n) != CANCELLED) { + // increment the total request counter + BackpressureHelper.add(totalRequested, n); + // if successful, notify the parent dispatcher this child can receive more + // elements + parent.manageRequests(); + // try replaying any cached content + parent.buffer.replay(this); + } + } + } + + /** + * Indicate that values have been emitted to this child subscriber by the dispatch() method. + * @param n the number of items emitted + * @return the updated request value (may indicate how much can be produced or a terminal state) + */ + public long produced(long n) { + return BackpressureHelper.producedCancel(this, n); + } + + @Override + public boolean isDisposed() { + return get() == CANCELLED; + } + + @Override + public void cancel() { + dispose(); + } + + @Override + public void dispose() { + if (getAndSet(CANCELLED) != CANCELLED) { + // remove this from the parent + parent.remove(this); + // After removal, we might have unblocked the other child subscribers: + // let's assume this child had 0 requested before the cancellation while + // the others had non-zero. By removing this 'blocking' child, the others + // are now free to receive events + parent.manageRequests(); + // make sure the last known node is not retained + index = null; + } + } + /** + * Convenience method to auto-cast the index object. + * @return the current index object + */ + @SuppressWarnings("unchecked") + U index() { + return (U)index; + } + } + /** + * The interface for interacting with various buffering logic. + * + * @param the value type + */ + interface ReplayBuffer { + /** + * Adds a regular value to the buffer. + * @param value the next value to store + */ + void next(T value); + /** + * Adds a terminal exception to the buffer. + * @param e the Throwable instance + */ + void error(Throwable e); + /** + * Adds a completion event to the buffer. + */ + void complete(); + /** + * Tries to replay the buffered values to the + * subscriber inside the output if there + * is new value and requests available at the + * same time. + * @param output the receiver of the events + */ + void replay(InnerSubscription output); + } + + /** + * Holds an unbounded list of events. + * + * @param the value type + */ + static final class UnboundedReplayBuffer extends ArrayList implements ReplayBuffer { + + private static final long serialVersionUID = 7063189396499112664L; + /** The total number of events in the buffer. */ + volatile int size; + + UnboundedReplayBuffer(int capacityHint) { + super(capacityHint); + } + + @Override + public void next(T value) { + add(NotificationLite.next(value)); + size++; + } + + @Override + public void error(Throwable e) { + add(NotificationLite.error(e)); + size++; + } + + @Override + public void complete() { + add(NotificationLite.complete()); + size++; + } + + @Override + public void replay(InnerSubscription output) { + synchronized (output) { + if (output.emitting) { + output.missed = true; + return; + } + output.emitting = true; + } + final Subscriber child = output.child; + + for (;;) { + if (output.isDisposed()) { + return; + } + int sourceIndex = size; + + Integer destinationIndexObject = output.index(); + int destinationIndex = destinationIndexObject != null ? destinationIndexObject : 0; + + long r = output.get(); + long r0 = r; // NOPMD + long e = 0L; + + while (r != 0L && destinationIndex < sourceIndex) { + Object o = get(destinationIndex); + try { + if (NotificationLite.accept(o, child)) { + return; + } + } catch (Throwable err) { + Exceptions.throwIfFatal(err); + output.dispose(); + if (!NotificationLite.isError(o) && !NotificationLite.isComplete(o)) { + child.onError(err); + } + return; + } + if (output.isDisposed()) { + return; + } + destinationIndex++; + r--; + e++; + } + if (e != 0L) { + output.index = destinationIndex; + if (r0 != Long.MAX_VALUE) { + output.produced(e); + } + } + + synchronized (output) { + if (!output.missed) { + output.emitting = false; + return; + } + output.missed = false; + } + } + } + } + + /** + * Represents a node in a bounded replay buffer's linked list. + */ + static final class Node extends AtomicReference { + + private static final long serialVersionUID = 245354315435971818L; + final Object value; + final long index; + + Node(Object value, long index) { + this.value = value; + this.index = index; + } + } + + /** + * Base class for bounded buffering with options to specify an + * enter and leave transforms and custom truncation behavior. + * + * @param the value type + */ + static class BoundedReplayBuffer extends AtomicReference implements ReplayBuffer { + + private static final long serialVersionUID = 2346567790059478686L; + + Node tail; + int size; + + long index; + + BoundedReplayBuffer() { + Node n = new Node(null, 0); + tail = n; + set(n); + } + + /** + * Add a new node to the linked list. + * @param n the Node instance to add + */ + final void addLast(Node n) { + tail.set(n); + tail = n; + size++; + } + /** + * Remove the first node from the linked list. + */ + final void removeFirst() { + Node head = get(); + Node next = head.get(); + if (next == null) { + throw new IllegalStateException("Empty list!"); + } + size--; + // can't just move the head because it would retain the very first value + // can't null out the head's value because of late replayers would see null + setFirst(next); + } + /* test */ final void removeSome(int n) { + Node head = get(); + while (n > 0) { + head = head.get(); + n--; + size--; + } + + setFirst(head); + // correct the tail if all items have been removed + head = get(); + if (head.get() == null) { + tail = head; + } + } + /** + * Arranges the given node is the new head from now on. + * @param n the Node instance to set as first + */ + final void setFirst(Node n) { + set(n); + } + + @Override + public final void next(T value) { + Object o = enterTransform(NotificationLite.next(value)); + Node n = new Node(o, ++index); + addLast(n); + truncate(); + } + + @Override + public final void error(Throwable e) { + Object o = enterTransform(NotificationLite.error(e)); + Node n = new Node(o, ++index); + addLast(n); + truncateFinal(); + } + + @Override + public final void complete() { + Object o = enterTransform(NotificationLite.complete()); + Node n = new Node(o, ++index); + addLast(n); + truncateFinal(); + } + + final void trimHead() { + Node head = get(); + if (head.value != null) { + Node n = new Node(null, 0L); + n.lazySet(head.get()); + set(n); + } + } + + @Override + public final void replay(InnerSubscription output) { + synchronized (output) { + if (output.emitting) { + output.missed = true; + return; + } + output.emitting = true; + } + for (;;) { + if (output.isDisposed()) { + output.index = null; + return; + } + + long r = output.get(); + boolean unbounded = r == Long.MAX_VALUE; // NOPMD + long e = 0L; + + Node node = output.index(); + if (node == null) { + node = getHead(); + output.index = node; + + BackpressureHelper.add(output.totalRequested, node.index); + } + + while (r != 0) { + Node v = node.get(); + if (v != null) { + Object o = leaveTransform(v.value); + try { + if (NotificationLite.accept(o, output.child)) { + output.index = null; + return; + } + } catch (Throwable err) { + Exceptions.throwIfFatal(err); + output.index = null; + output.dispose(); + if (!NotificationLite.isError(o) && !NotificationLite.isComplete(o)) { + output.child.onError(err); + } + return; + } + e++; + r--; + node = v; + } else { + break; + } + if (output.isDisposed()) { + output.index = null; + return; + } + } + + if (e != 0L) { + output.index = node; + if (!unbounded) { + output.produced(e); + } + } + + synchronized (output) { + if (!output.missed) { + output.emitting = false; + return; + } + output.missed = false; + } + } + + } + + /** + * Override this to wrap the NotificationLite object into a + * container to be used later by truncate. + * @param value the value to transform into the internal representation + * @return the transformed value + */ + Object enterTransform(Object value) { + return value; + } + /** + * Override this to unwrap the transformed value into a + * NotificationLite object. + * @param value the input value to transform to the external representation + * @return the transformed value + */ + Object leaveTransform(Object value) { + return value; + } + /** + * Override this method to truncate a non-terminated buffer + * based on its current properties. + */ + void truncate() { + + } + /** + * Override this method to truncate a terminated buffer + * based on its properties (i.e., truncate but the very last node). + */ + void truncateFinal() { + trimHead(); + } + /* test */ final void collect(Collection output) { + Node n = getHead(); + for (;;) { + Node next = n.get(); + if (next != null) { + Object o = next.value; + Object v = leaveTransform(o); + if (NotificationLite.isComplete(v) || NotificationLite.isError(v)) { + break; + } + output.add(NotificationLite.getValue(v)); + n = next; + } else { + break; + } + } + } + /* test */ boolean hasError() { + return tail.value != null && NotificationLite.isError(leaveTransform(tail.value)); + } + /* test */ boolean hasCompleted() { + return tail.value != null && NotificationLite.isComplete(leaveTransform(tail.value)); + } + + Node getHead() { + return get(); + } + } + + /** + * A bounded replay buffer implementation with size limit only. + * + * @param the value type + */ + static final class SizeBoundReplayBuffer extends BoundedReplayBuffer { + + private static final long serialVersionUID = -5898283885385201806L; + + final int limit; + SizeBoundReplayBuffer(int limit) { + this.limit = limit; + } + + @Override + void truncate() { + // overflow can be at most one element + if (size > limit) { + removeFirst(); + } + } + + // no need for final truncation because values are truncated one by one + } + + /** + * Size and time bound replay buffer. + * + * @param the buffered value type + */ + static final class SizeAndTimeBoundReplayBuffer extends BoundedReplayBuffer { + + private static final long serialVersionUID = 3457957419649567404L; + final Scheduler scheduler; + final long maxAge; + final TimeUnit unit; + final int limit; + SizeAndTimeBoundReplayBuffer(int limit, long maxAge, TimeUnit unit, Scheduler scheduler) { + this.scheduler = scheduler; + this.limit = limit; + this.maxAge = maxAge; + this.unit = unit; + } + + @Override + Object enterTransform(Object value) { + return new Timed(value, scheduler.now(unit), unit); + } + + @Override + Object leaveTransform(Object value) { + return ((Timed)value).value(); + } + + @Override + void truncate() { + long timeLimit = scheduler.now(unit) - maxAge; + + Node prev = get(); + Node next = prev.get(); + + int e = 0; + for (;;) { + if (next != null) { + if (size > limit && size > 1) { // never truncate the very last item just added + e++; + size--; + prev = next; + next = next.get(); + } else { + Timed v = (Timed)next.value; + if (v.time() <= timeLimit) { + e++; + size--; + prev = next; + next = next.get(); + } else { + break; + } + } + } else { + break; + } + } + if (e != 0) { + setFirst(prev); + } + } + + @Override + void truncateFinal() { + long timeLimit = scheduler.now(unit) - maxAge; + + Node prev = get(); + Node next = prev.get(); + + int e = 0; + for (;;) { + if (next != null && size > 1) { + Timed v = (Timed)next.value; + if (v.time() <= timeLimit) { + e++; + size--; + prev = next; + next = next.get(); + } else { + break; + } + } else { + break; + } + } + if (e != 0) { + setFirst(prev); + } + } + + @Override + Node getHead() { + long timeLimit = scheduler.now(unit) - maxAge; + Node prev = get(); + Node next = prev.get(); + for (;;) { + if (next == null) { + break; + } + Timed v = (Timed)next.value; + if (NotificationLite.isComplete(v.value()) || NotificationLite.isError(v.value())) { + break; + } + if (v.time() <= timeLimit) { + prev = next; + next = next.get(); + } else { + break; + } + } + return prev; + } + } + + static final class MulticastFlowable extends Flowable { + private final Callable> connectableFactory; + private final Function, ? extends Publisher> selector; + + MulticastFlowable(Callable> connectableFactory, Function, ? extends Publisher> selector) { + this.connectableFactory = connectableFactory; + this.selector = selector; + } + + @Override + protected void subscribeActual(Subscriber child) { + ConnectableFlowable cf; + try { + cf = ObjectHelper.requireNonNull(connectableFactory.call(), "The connectableFactory returned null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, child); + return; + } + + Publisher observable; + try { + observable = ObjectHelper.requireNonNull(selector.apply(cf), "The selector returned a null Publisher"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, child); + return; + } + + final SubscriberResourceWrapper srw = new SubscriberResourceWrapper(child); + + observable.subscribe(srw); + + cf.connect(new DisposableConsumer(srw)); + } + + final class DisposableConsumer implements Consumer { + private final SubscriberResourceWrapper srw; + + DisposableConsumer(SubscriberResourceWrapper srw) { + this.srw = srw; + } + + @Override + public void accept(Disposable r) { + srw.setResource(r); + } + } + } + + static final class ConnectableFlowableReplay extends ConnectableFlowable { + private final ConnectableFlowable cf; + private final Flowable flowable; + + ConnectableFlowableReplay(ConnectableFlowable cf, Flowable flowable) { + this.cf = cf; + this.flowable = flowable; + } + + @Override + public void connect(Consumer connection) { + cf.connect(connection); + } + + @Override + protected void subscribeActual(Subscriber s) { + flowable.subscribe(s); + } + } + + static final class ReplayBufferTask implements Callable> { + private final int bufferSize; + + ReplayBufferTask(int bufferSize) { + this.bufferSize = bufferSize; + } + + @Override + public ReplayBuffer call() { + return new SizeBoundReplayBuffer(bufferSize); + } + } + + static final class ScheduledReplayBufferTask implements Callable> { + private final int bufferSize; + private final long maxAge; + private final TimeUnit unit; + private final Scheduler scheduler; + + ScheduledReplayBufferTask(int bufferSize, long maxAge, TimeUnit unit, Scheduler scheduler) { + this.bufferSize = bufferSize; + this.maxAge = maxAge; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public ReplayBuffer call() { + return new SizeAndTimeBoundReplayBuffer(bufferSize, maxAge, unit, scheduler); + } + } + + static final class ReplayPublisher implements Publisher { + private final AtomicReference> curr; + private final Callable> bufferFactory; + + ReplayPublisher(AtomicReference> curr, Callable> bufferFactory) { + this.curr = curr; + this.bufferFactory = bufferFactory; + } + + @Override + public void subscribe(Subscriber child) { + // concurrent connection/disconnection may change the state, + // we loop to be atomic while the child subscribes + for (;;) { + // get the current subscriber-to-source + ReplaySubscriber r = curr.get(); + // if there isn't one + if (r == null) { + ReplayBuffer buf; + + try { + buf = bufferFactory.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, child); + return; + } + // create a new subscriber to source + ReplaySubscriber u = new ReplaySubscriber(buf); + // let's try setting it as the current subscriber-to-source + if (!curr.compareAndSet(null, u)) { + // didn't work, maybe someone else did it or the current subscriber + // to source has just finished + continue; + } + // we won, let's use it going onwards + r = u; + } + + // create the backpressure-managing producer for this child + InnerSubscription inner = new InnerSubscription(r, child); + // the producer has been registered with the current subscriber-to-source so + // at least it will receive the next terminal event + // setting the producer will trigger the first request to be considered by + // the subscriber-to-source. + child.onSubscribe(inner); + // we try to add it to the array of subscribers + // if it fails, no worries because we will still have its buffer + // so it is going to replay it for us + r.add(inner); + + if (inner.isDisposed()) { + r.remove(inner); + return; + } + + r.manageRequests(); + + // trigger the capturing of the current node and total requested + r.buffer.replay(inner); + + break; // NOPMD + } + } + } + + static final class DefaultUnboundedFactory implements Callable { + @Override + public Object call() { + return new UnboundedReplayBuffer(16); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java new file mode 100755 index 0000000..8bc9ba2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryBiPredicate.java @@ -0,0 +1,123 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.BiPredicate; +import io.reactivex.internal.subscriptions.SubscriptionArbiter; + +public final class FlowableRetryBiPredicate extends AbstractFlowableWithUpstream { + final BiPredicate predicate; + public FlowableRetryBiPredicate( + Flowable source, + BiPredicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + public void subscribeActual(Subscriber s) { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + s.onSubscribe(sa); + + RetryBiSubscriber rs = new RetryBiSubscriber(s, predicate, sa, source); + rs.subscribeNext(); + } + + static final class RetryBiSubscriber extends AtomicInteger implements FlowableSubscriber { + + private static final long serialVersionUID = -7098360935104053232L; + + final Subscriber downstream; + final SubscriptionArbiter sa; + final Publisher source; + final BiPredicate predicate; + int retries; + + long produced; + + RetryBiSubscriber(Subscriber actual, + BiPredicate predicate, SubscriptionArbiter sa, Publisher source) { + this.downstream = actual; + this.sa = sa; + this.source = source; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Subscription s) { + sa.setSubscription(s); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + boolean b; + try { + b = predicate.test(++retries, t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + return; + } + if (!b) { + downstream.onError(t); + return; + } + subscribeNext(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + /** + * Subscribes to the source again via trampolining. + */ + void subscribeNext() { + if (getAndIncrement() == 0) { + int missed = 1; + for (;;) { + if (sa.isCancelled()) { + return; + } + + long p = produced; + if (p != 0L) { + produced = 0L; + sa.produced(p); + } + + source.subscribe(this); + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java new file mode 100755 index 0000000..8d035aa --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryPredicate.java @@ -0,0 +1,134 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.subscriptions.SubscriptionArbiter; + +public final class FlowableRetryPredicate extends AbstractFlowableWithUpstream { + final Predicate predicate; + final long count; + public FlowableRetryPredicate(Flowable source, + long count, + Predicate predicate) { + super(source); + this.predicate = predicate; + this.count = count; + } + + @Override + public void subscribeActual(Subscriber s) { + SubscriptionArbiter sa = new SubscriptionArbiter(false); + s.onSubscribe(sa); + + RetrySubscriber rs = new RetrySubscriber(s, count, predicate, sa, source); + rs.subscribeNext(); + } + + static final class RetrySubscriber extends AtomicInteger implements FlowableSubscriber { + + private static final long serialVersionUID = -7098360935104053232L; + + final Subscriber downstream; + final SubscriptionArbiter sa; + final Publisher source; + final Predicate predicate; + long remaining; + + long produced; + + RetrySubscriber(Subscriber actual, long count, + Predicate predicate, SubscriptionArbiter sa, Publisher source) { + this.downstream = actual; + this.sa = sa; + this.source = source; + this.predicate = predicate; + this.remaining = count; + } + + @Override + public void onSubscribe(Subscription s) { + sa.setSubscription(s); + } + + @Override + public void onNext(T t) { + produced++; + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + long r = remaining; + if (r != Long.MAX_VALUE) { + remaining = r - 1; + } + if (r == 0) { + downstream.onError(t); + } else { + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + return; + } + if (!b) { + downstream.onError(t); + return; + } + subscribeNext(); + } + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + /** + * Subscribes to the source again via trampolining. + */ + void subscribeNext() { + if (getAndIncrement() == 0) { + int missed = 1; + for (;;) { + if (sa.isCancelled()) { + return; + } + + long p = produced; + if (p != 0L) { + produced = 0L; + sa.produced(p); + } + + source.subscribe(this); + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java new file mode 100755 index 0000000..de0f735 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRetryWhen.java @@ -0,0 +1,86 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.flowable.FlowableRepeatWhen.*; +import io.reactivex.internal.subscriptions.EmptySubscription; +import io.reactivex.processors.*; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableRetryWhen extends AbstractFlowableWithUpstream { + final Function, ? extends Publisher> handler; + + public FlowableRetryWhen(Flowable source, + Function, ? extends Publisher> handler) { + super(source); + this.handler = handler; + } + + @Override + public void subscribeActual(Subscriber s) { + SerializedSubscriber z = new SerializedSubscriber(s); + + FlowableProcessor processor = UnicastProcessor.create(8).toSerialized(); + + Publisher when; + + try { + when = ObjectHelper.requireNonNull(handler.apply(processor), "handler returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, s); + return; + } + + WhenReceiver receiver = new WhenReceiver(source); + + RetryWhenSubscriber subscriber = new RetryWhenSubscriber(z, processor, receiver); + + receiver.subscriber = subscriber; + + s.onSubscribe(subscriber); + + when.subscribe(receiver); + + receiver.onNext(0); + } + + static final class RetryWhenSubscriber extends WhenSourceSubscriber { + + private static final long serialVersionUID = -2680129890138081029L; + + RetryWhenSubscriber(Subscriber actual, FlowableProcessor processor, + Subscription receiver) { + super(actual, processor, receiver); + } + + @Override + public void onError(Throwable t) { + again(t); + } + + @Override + public void onComplete() { + receiver.cancel(); + downstream.onComplete(); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java new file mode 100755 index 0000000..66b9c48 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java @@ -0,0 +1,225 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableSamplePublisher extends Flowable { + final Publisher source; + final Publisher other; + + final boolean emitLast; + + public FlowableSamplePublisher(Publisher source, Publisher other, boolean emitLast) { + this.source = source; + this.other = other; + this.emitLast = emitLast; + } + + @Override + protected void subscribeActual(Subscriber s) { + SerializedSubscriber serial = new SerializedSubscriber(s); + if (emitLast) { + source.subscribe(new SampleMainEmitLast(serial, other)); + } else { + source.subscribe(new SampleMainNoLast(serial, other)); + } + } + + abstract static class SamplePublisherSubscriber extends AtomicReference implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -3517602651313910099L; + + final Subscriber downstream; + final Publisher sampler; + + final AtomicLong requested = new AtomicLong(); + + final AtomicReference other = new AtomicReference(); + + Subscription upstream; + + SamplePublisherSubscriber(Subscriber actual, Publisher other) { + this.downstream = actual; + this.sampler = other; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + if (other.get() == null) { + sampler.subscribe(new SamplerSubscriber(this)); + s.request(Long.MAX_VALUE); + } + } + + } + + @Override + public void onNext(T t) { + lazySet(t); + } + + @Override + public void onError(Throwable t) { + SubscriptionHelper.cancel(other); + downstream.onError(t); + } + + @Override + public void onComplete() { + SubscriptionHelper.cancel(other); + completion(); + } + + void setOther(Subscription o) { + SubscriptionHelper.setOnce(other, o, Long.MAX_VALUE); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + } + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(other); + upstream.cancel(); + } + + public void error(Throwable e) { + upstream.cancel(); + downstream.onError(e); + } + + public void complete() { + upstream.cancel(); + completion(); + } + + void emit() { + T value = getAndSet(null); + if (value != null) { + long r = requested.get(); + if (r != 0L) { + downstream.onNext(value); + BackpressureHelper.produced(requested, 1); + } else { + cancel(); + downstream.onError(new MissingBackpressureException("Couldn't emit value due to lack of requests!")); + } + } + } + + abstract void completion(); + + abstract void run(); + } + + static final class SamplerSubscriber implements FlowableSubscriber { + final SamplePublisherSubscriber parent; + SamplerSubscriber(SamplePublisherSubscriber parent) { + this.parent = parent; + + } + + @Override + public void onSubscribe(Subscription s) { + parent.setOther(s); + } + + @Override + public void onNext(Object t) { + parent.run(); + } + + @Override + public void onError(Throwable t) { + parent.error(t); + } + + @Override + public void onComplete() { + parent.complete(); + } + } + + static final class SampleMainNoLast extends SamplePublisherSubscriber { + + private static final long serialVersionUID = -3029755663834015785L; + + SampleMainNoLast(Subscriber actual, Publisher other) { + super(actual, other); + } + + @Override + void completion() { + downstream.onComplete(); + } + + @Override + void run() { + emit(); + } + } + + static final class SampleMainEmitLast extends SamplePublisherSubscriber { + + private static final long serialVersionUID = -3029755663834015785L; + + final AtomicInteger wip; + + volatile boolean done; + + SampleMainEmitLast(Subscriber actual, Publisher other) { + super(actual, other); + this.wip = new AtomicInteger(); + } + + @Override + void completion() { + done = true; + if (wip.getAndIncrement() == 0) { + emit(); + downstream.onComplete(); + } + } + + @Override + void run() { + if (wip.getAndIncrement() == 0) { + do { + boolean d = done; + emit(); + if (d) { + downstream.onComplete(); + return; + } + } while (wip.decrementAndGet() != 0); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSampleTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSampleTimed.java new file mode 100755 index 0000000..47ed31f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSampleTimed.java @@ -0,0 +1,184 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableSampleTimed extends AbstractFlowableWithUpstream { + final long period; + final TimeUnit unit; + final Scheduler scheduler; + + final boolean emitLast; + + public FlowableSampleTimed(Flowable source, long period, TimeUnit unit, Scheduler scheduler, boolean emitLast) { + super(source); + this.period = period; + this.unit = unit; + this.scheduler = scheduler; + this.emitLast = emitLast; + } + + @Override + protected void subscribeActual(Subscriber s) { + SerializedSubscriber serial = new SerializedSubscriber(s); + if (emitLast) { + source.subscribe(new SampleTimedEmitLast(serial, period, unit, scheduler)); + } else { + source.subscribe(new SampleTimedNoLast(serial, period, unit, scheduler)); + } + } + + abstract static class SampleTimedSubscriber extends AtomicReference implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = -3517602651313910099L; + + final Subscriber downstream; + final long period; + final TimeUnit unit; + final Scheduler scheduler; + + final AtomicLong requested = new AtomicLong(); + + final SequentialDisposable timer = new SequentialDisposable(); + + Subscription upstream; + + SampleTimedSubscriber(Subscriber actual, long period, TimeUnit unit, Scheduler scheduler) { + this.downstream = actual; + this.period = period; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + timer.replace(scheduler.schedulePeriodicallyDirect(this, period, period, unit)); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + lazySet(t); + } + + @Override + public void onError(Throwable t) { + cancelTimer(); + downstream.onError(t); + } + + @Override + public void onComplete() { + cancelTimer(); + complete(); + } + + void cancelTimer() { + DisposableHelper.dispose(timer); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + } + } + + @Override + public void cancel() { + cancelTimer(); + upstream.cancel(); + } + + void emit() { + T value = getAndSet(null); + if (value != null) { + long r = requested.get(); + if (r != 0L) { + downstream.onNext(value); + BackpressureHelper.produced(requested, 1); + } else { + cancel(); + downstream.onError(new MissingBackpressureException("Couldn't emit value due to lack of requests!")); + } + } + } + + abstract void complete(); + } + + static final class SampleTimedNoLast extends SampleTimedSubscriber { + + private static final long serialVersionUID = -7139995637533111443L; + + SampleTimedNoLast(Subscriber actual, long period, TimeUnit unit, Scheduler scheduler) { + super(actual, period, unit, scheduler); + } + + @Override + void complete() { + downstream.onComplete(); + } + + @Override + public void run() { + emit(); + } + } + + static final class SampleTimedEmitLast extends SampleTimedSubscriber { + + private static final long serialVersionUID = -7139995637533111443L; + + final AtomicInteger wip; + + SampleTimedEmitLast(Subscriber actual, long period, TimeUnit unit, Scheduler scheduler) { + super(actual, period, unit, scheduler); + this.wip = new AtomicInteger(1); + } + + @Override + void complete() { + emit(); + if (wip.decrementAndGet() == 0) { + downstream.onComplete(); + } + } + + @Override + public void run() { + if (wip.incrementAndGet() == 2) { + emit(); + if (wip.decrementAndGet() == 0) { + downstream.onComplete(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableScalarXMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScalarXMap.java new file mode 100755 index 0000000..1acf93f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScalarXMap.java @@ -0,0 +1,164 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Utility classes to work with scalar-sourced XMap operators (where X == { flat, concat, switch }). + */ +public final class FlowableScalarXMap { + + /** Utility class. */ + private FlowableScalarXMap() { + throw new IllegalStateException("No instances!"); + } + + /** + * Tries to subscribe to a possibly Callable source's mapped Publisher. + * @param the input value type + * @param the output value type + * @param source the source Publisher + * @param subscriber the subscriber + * @param mapper the function mapping a scalar value into a Publisher + * @return true if successful, false if the caller should continue with the regular path. + */ + @SuppressWarnings("unchecked") + public static boolean tryScalarXMapSubscribe(Publisher source, + Subscriber subscriber, + Function> mapper) { + if (source instanceof Callable) { + T t; + + try { + t = ((Callable)source).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, subscriber); + return true; + } + + if (t == null) { + EmptySubscription.complete(subscriber); + return true; + } + + Publisher r; + + try { + r = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, subscriber); + return true; + } + + if (r instanceof Callable) { + R u; + + try { + u = ((Callable)r).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, subscriber); + return true; + } + + if (u == null) { + EmptySubscription.complete(subscriber); + return true; + } + subscriber.onSubscribe(new ScalarSubscription(subscriber, u)); + } else { + r.subscribe(subscriber); + } + + return true; + } + return false; + } + + /** + * Maps a scalar value into a Publisher and emits its values. + * + * @param the scalar value type + * @param the output value type + * @param value the scalar value to map + * @param mapper the function that gets the scalar value and should return + * a Publisher that gets streamed + * @return the new Flowable instance + */ + public static Flowable scalarXMap(final T value, final Function> mapper) { + return RxJavaPlugins.onAssembly(new ScalarXMapFlowable(value, mapper)); + } + + /** + * Maps a scalar value to a Publisher and subscribes to it. + * + * @param the scalar value type + * @param the mapped Publisher's element type. + */ + static final class ScalarXMapFlowable extends Flowable { + + final T value; + + final Function> mapper; + + ScalarXMapFlowable(T value, + Function> mapper) { + this.value = value; + this.mapper = mapper; + } + + @SuppressWarnings("unchecked") + @Override + public void subscribeActual(Subscriber s) { + Publisher other; + try { + other = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null Publisher"); + } catch (Throwable e) { + EmptySubscription.error(e, s); + return; + } + if (other instanceof Callable) { + R u; + + try { + u = ((Callable)other).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, s); + return; + } + + if (u == null) { + EmptySubscription.complete(s); + return; + } + s.onSubscribe(new ScalarSubscription(s, u)); + } else { + other.subscribe(s); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableScan.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScan.java new file mode 100755 index 0000000..0c5aca6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScan.java @@ -0,0 +1,116 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableScan extends AbstractFlowableWithUpstream { + final BiFunction accumulator; + public FlowableScan(Flowable source, BiFunction accumulator) { + super(source); + this.accumulator = accumulator; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ScanSubscriber(s, accumulator)); + } + + static final class ScanSubscriber implements FlowableSubscriber, Subscription { + final Subscriber downstream; + final BiFunction accumulator; + + Subscription upstream; + + T value; + + boolean done; + + ScanSubscriber(Subscriber actual, BiFunction accumulator) { + this.downstream = actual; + this.accumulator = accumulator; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + final Subscriber a = downstream; + T v = value; + if (v == null) { + value = t; + a.onNext(t); + } else { + T u; + + try { + u = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The value returned by the accumulator is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + onError(e); + return; + } + + value = u; + a.onNext(u); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableScanSeed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScanSeed.java new file mode 100755 index 0000000..6586b55 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableScanSeed.java @@ -0,0 +1,243 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableScanSeed extends AbstractFlowableWithUpstream { + final BiFunction accumulator; + final Callable seedSupplier; + + public FlowableScanSeed(Flowable source, Callable seedSupplier, BiFunction accumulator) { + super(source); + this.accumulator = accumulator; + this.seedSupplier = seedSupplier; + } + + @Override + protected void subscribeActual(Subscriber s) { + R r; + + try { + r = ObjectHelper.requireNonNull(seedSupplier.call(), "The seed supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + + source.subscribe(new ScanSeedSubscriber(s, accumulator, r, bufferSize())); + } + + static final class ScanSeedSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription { + private static final long serialVersionUID = -1776795561228106469L; + + final Subscriber downstream; + + final BiFunction accumulator; + + final SimplePlainQueue queue; + + final AtomicLong requested; + + final int prefetch; + + final int limit; + + volatile boolean cancelled; + + volatile boolean done; + Throwable error; + + Subscription upstream; + + R value; + + int consumed; + + ScanSeedSubscriber(Subscriber actual, BiFunction accumulator, R value, int prefetch) { + this.downstream = actual; + this.accumulator = accumulator; + this.value = value; + this.prefetch = prefetch; + this.limit = prefetch - (prefetch >> 2); + this.queue = new SpscArrayQueue(prefetch); + this.queue.offer(value); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(prefetch - 1); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + R v = value; + try { + v = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The accumulator returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + value = v; + queue.offer(v); + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber a = downstream; + SimplePlainQueue q = queue; + int lim = limit; + int c = consumed; + + for (;;) { + + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + q.clear(); + return; + } + boolean d = done; + + if (d) { + Throwable ex = error; + if (ex != null) { + q.clear(); + a.onError(ex); + return; + } + } + + R v = q.poll(); + boolean empty = v == null; + + if (d && empty) { + a.onComplete(); + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + if (++c == lim) { + c = 0; + upstream.request(lim); + } + } + + if (e == r) { + if (done) { + Throwable ex = error; + if (ex != null) { + q.clear(); + a.onError(ex); + return; + } + if (q.isEmpty()) { + a.onComplete(); + return; + } + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + } + + consumed = c; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqual.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqual.java new file mode 100755 index 0000000..fd10367 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqual.java @@ -0,0 +1,345 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.BiPredicate; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableSequenceEqual extends Flowable { + final Publisher first; + final Publisher second; + final BiPredicate comparer; + final int prefetch; + + public FlowableSequenceEqual(Publisher first, Publisher second, + BiPredicate comparer, int prefetch) { + this.first = first; + this.second = second; + this.comparer = comparer; + this.prefetch = prefetch; + } + + @Override + public void subscribeActual(Subscriber s) { + EqualCoordinator parent = new EqualCoordinator(s, prefetch, comparer); + s.onSubscribe(parent); + parent.subscribe(first, second); + } + + /** + * Provides callbacks for the EqualSubscribers. + */ + interface EqualCoordinatorHelper { + + void drain(); + + void innerError(Throwable ex); + } + + static final class EqualCoordinator extends DeferredScalarSubscription + implements EqualCoordinatorHelper { + + private static final long serialVersionUID = -6178010334400373240L; + + final BiPredicate comparer; + + final EqualSubscriber first; + + final EqualSubscriber second; + + final AtomicThrowable error; + + final AtomicInteger wip; + + T v1; + + T v2; + + EqualCoordinator(Subscriber actual, int prefetch, BiPredicate comparer) { + super(actual); + this.comparer = comparer; + this.wip = new AtomicInteger(); + this.first = new EqualSubscriber(this, prefetch); + this.second = new EqualSubscriber(this, prefetch); + this.error = new AtomicThrowable(); + } + + void subscribe(Publisher source1, Publisher source2) { + source1.subscribe(first); + source2.subscribe(second); + } + + @Override + public void cancel() { + super.cancel(); + first.cancel(); + second.cancel(); + if (wip.getAndIncrement() == 0) { + first.clear(); + second.clear(); + } + } + + void cancelAndClear() { + first.cancel(); + first.clear(); + second.cancel(); + second.clear(); + } + + @Override + public void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + + for (;;) { + SimpleQueue q1 = first.queue; + SimpleQueue q2 = second.queue; + + if (q1 != null && q2 != null) { + for (;;) { + if (isCancelled()) { + first.clear(); + second.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + cancelAndClear(); + + downstream.onError(error.terminate()); + return; + } + + boolean d1 = first.done; + + T a = v1; + if (a == null) { + try { + a = q1.poll(); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAndClear(); + error.addThrowable(exc); + downstream.onError(error.terminate()); + return; + } + v1 = a; + } + boolean e1 = a == null; + + boolean d2 = second.done; + T b = v2; + if (b == null) { + try { + b = q2.poll(); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAndClear(); + error.addThrowable(exc); + downstream.onError(error.terminate()); + return; + } + v2 = b; + } + + boolean e2 = b == null; + + if (d1 && d2 && e1 && e2) { + complete(true); + return; + } + if ((d1 && d2) && (e1 != e2)) { + cancelAndClear(); + complete(false); + return; + } + + if (e1 || e2) { + break; + } + + boolean c; + + try { + c = comparer.test(a, b); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAndClear(); + error.addThrowable(exc); + downstream.onError(error.terminate()); + return; + } + + if (!c) { + cancelAndClear(); + complete(false); + return; + } + + v1 = null; + v2 = null; + + first.request(); + second.request(); + } + + } else { + if (isCancelled()) { + first.clear(); + second.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + cancelAndClear(); + + downstream.onError(error.terminate()); + return; + } + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void innerError(Throwable t) { + if (error.addThrowable(t)) { + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + } + + static final class EqualSubscriber + extends AtomicReference + implements FlowableSubscriber { + + private static final long serialVersionUID = 4804128302091633067L; + + final EqualCoordinatorHelper parent; + + final int prefetch; + + final int limit; + + long produced; + + volatile SimpleQueue queue; + + volatile boolean done; + + int sourceMode; + + EqualSubscriber(EqualCoordinatorHelper parent, int prefetch) { + this.parent = parent; + this.limit = prefetch - (prefetch >> 2); + this.prefetch = prefetch; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY); + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = qs; + done = true; + parent.drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = qs; + s.request(prefetch); + return; + } + } + + queue = new SpscArrayQueue(prefetch); + + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == QueueSubscription.NONE) { + if (!queue.offer(t)) { + onError(new MissingBackpressureException()); + return; + } + } + parent.drain(); + } + + @Override + public void onError(Throwable t) { + parent.innerError(t); + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + + public void request() { + if (sourceMode != QueueSubscription.SYNC) { + long p = produced + 1; + if (p >= limit) { + produced = 0; + get().request(p); + } else { + produced = p; + } + } + } + + public void cancel() { + SubscriptionHelper.cancel(this); + } + + void clear() { + SimpleQueue sq = queue; + if (sq != null) { + sq.clear(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java new file mode 100755 index 0000000..bcda903 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualSingle.java @@ -0,0 +1,244 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiPredicate; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.operators.flowable.FlowableSequenceEqual.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableSequenceEqualSingle extends Single implements FuseToFlowable { + final Publisher first; + final Publisher second; + final BiPredicate comparer; + final int prefetch; + + public FlowableSequenceEqualSingle(Publisher first, Publisher second, + BiPredicate comparer, int prefetch) { + this.first = first; + this.second = second; + this.comparer = comparer; + this.prefetch = prefetch; + } + + @Override + public void subscribeActual(SingleObserver observer) { + EqualCoordinator parent = new EqualCoordinator(observer, prefetch, comparer); + observer.onSubscribe(parent); + parent.subscribe(first, second); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableSequenceEqual(first, second, comparer, prefetch)); + } + + static final class EqualCoordinator + extends AtomicInteger + implements Disposable, EqualCoordinatorHelper { + + private static final long serialVersionUID = -6178010334400373240L; + + final SingleObserver downstream; + + final BiPredicate comparer; + + final EqualSubscriber first; + + final EqualSubscriber second; + + final AtomicThrowable error; + + T v1; + + T v2; + + EqualCoordinator(SingleObserver actual, int prefetch, BiPredicate comparer) { + this.downstream = actual; + this.comparer = comparer; + this.first = new EqualSubscriber(this, prefetch); + this.second = new EqualSubscriber(this, prefetch); + this.error = new AtomicThrowable(); + } + + void subscribe(Publisher source1, Publisher source2) { + source1.subscribe(first); + source2.subscribe(second); + } + + @Override + public void dispose() { + first.cancel(); + second.cancel(); + if (getAndIncrement() == 0) { + first.clear(); + second.clear(); + } + } + + @Override + public boolean isDisposed() { + return first.get() == SubscriptionHelper.CANCELLED; + } + + void cancelAndClear() { + first.cancel(); + first.clear(); + second.cancel(); + second.clear(); + } + + @Override + public void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + for (;;) { + SimpleQueue q1 = first.queue; + SimpleQueue q2 = second.queue; + + if (q1 != null && q2 != null) { + for (;;) { + if (isDisposed()) { + first.clear(); + second.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + cancelAndClear(); + + downstream.onError(error.terminate()); + return; + } + + boolean d1 = first.done; + + T a = v1; + if (a == null) { + try { + a = q1.poll(); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAndClear(); + error.addThrowable(exc); + downstream.onError(error.terminate()); + return; + } + v1 = a; + } + boolean e1 = a == null; + + boolean d2 = second.done; + T b = v2; + if (b == null) { + try { + b = q2.poll(); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAndClear(); + error.addThrowable(exc); + downstream.onError(error.terminate()); + return; + } + v2 = b; + } + + boolean e2 = b == null; + + if (d1 && d2 && e1 && e2) { + downstream.onSuccess(true); + return; + } + if ((d1 && d2) && (e1 != e2)) { + cancelAndClear(); + downstream.onSuccess(false); + return; + } + + if (e1 || e2) { + break; + } + + boolean c; + + try { + c = comparer.test(a, b); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAndClear(); + error.addThrowable(exc); + downstream.onError(error.terminate()); + return; + } + + if (!c) { + cancelAndClear(); + downstream.onSuccess(false); + return; + } + + v1 = null; + v2 = null; + + first.request(); + second.request(); + } + + } else { + if (isDisposed()) { + first.clear(); + second.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + cancelAndClear(); + + downstream.onError(error.terminate()); + return; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void innerError(Throwable t) { + if (error.addThrowable(t)) { + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSerialized.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSerialized.java new file mode 100755 index 0000000..fc4832a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSerialized.java @@ -0,0 +1,29 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Flowable; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableSerialized extends AbstractFlowableWithUpstream { + public FlowableSerialized(Flowable source) { + super(source); + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new SerializedSubscriber(s)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingle.java new file mode 100755 index 0000000..7b4ace5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingle.java @@ -0,0 +1,121 @@ +/** + * 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.internal.operators.flowable; + +import java.util.NoSuchElementException; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableSingle extends AbstractFlowableWithUpstream { + + final T defaultValue; + + final boolean failOnEmpty; + + public FlowableSingle(Flowable source, T defaultValue, boolean failOnEmpty) { + super(source); + this.defaultValue = defaultValue; + this.failOnEmpty = failOnEmpty; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new SingleElementSubscriber(s, defaultValue, failOnEmpty)); + } + + static final class SingleElementSubscriber extends DeferredScalarSubscription + implements FlowableSubscriber { + + private static final long serialVersionUID = -5526049321428043809L; + + final T defaultValue; + + final boolean failOnEmpty; + + Subscription upstream; + + boolean done; + + SingleElementSubscriber(Subscriber actual, T defaultValue, boolean failOnEmpty) { + super(actual); + this.defaultValue = defaultValue; + this.failOnEmpty = failOnEmpty; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (value != null) { + done = true; + upstream.cancel(); + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); + return; + } + value = t; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + T v = value; + value = null; + if (v == null) { + v = defaultValue; + } + if (v == null) { + if (failOnEmpty) { + downstream.onError(new NoSuchElementException()); + } else { + downstream.onComplete(); + } + } else { + complete(v); + } + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleMaybe.java new file mode 100755 index 0000000..14be539 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleMaybe.java @@ -0,0 +1,119 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableSingleMaybe extends Maybe implements FuseToFlowable { + + final Flowable source; + + public FlowableSingleMaybe(Flowable source) { + this.source = source; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new SingleElementSubscriber(observer)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableSingle(source, null, false)); + } + + static final class SingleElementSubscriber + implements FlowableSubscriber, Disposable { + + final MaybeObserver downstream; + + Subscription upstream; + + boolean done; + + T value; + + SingleElementSubscriber(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (value != null) { + done = true; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); + return; + } + value = t; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + T v = value; + value = null; + if (v == null) { + downstream.onComplete(); + } else { + downstream.onSuccess(v); + } + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleSingle.java new file mode 100755 index 0000000..cb4bce5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSingleSingle.java @@ -0,0 +1,131 @@ +/** + * 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.internal.operators.flowable; + +import java.util.NoSuchElementException; + +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableSingleSingle extends Single implements FuseToFlowable { + + final Flowable source; + + final T defaultValue; + + public FlowableSingleSingle(Flowable source, T defaultValue) { + this.source = source; + this.defaultValue = defaultValue; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new SingleElementSubscriber(observer, defaultValue)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableSingle(source, defaultValue, true)); + } + + static final class SingleElementSubscriber + implements FlowableSubscriber, Disposable { + + final SingleObserver downstream; + + final T defaultValue; + + Subscription upstream; + + boolean done; + + T value; + + SingleElementSubscriber(SingleObserver actual, T defaultValue) { + this.downstream = actual; + this.defaultValue = defaultValue; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (value != null) { + done = true; + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); + return; + } + value = t; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + upstream = SubscriptionHelper.CANCELLED; + T v = value; + value = null; + if (v == null) { + v = defaultValue; + } + + if (v != null) { + downstream.onSuccess(v); + } else { + downstream.onError(new NoSuchElementException()); + } + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkip.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkip.java new file mode 100755 index 0000000..58a0446 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkip.java @@ -0,0 +1,83 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +public final class FlowableSkip extends AbstractFlowableWithUpstream { + final long n; + public FlowableSkip(Flowable source, long n) { + super(source); + this.n = n; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new SkipSubscriber(s, n)); + } + + static final class SkipSubscriber implements FlowableSubscriber, Subscription { + final Subscriber downstream; + long remaining; + + Subscription upstream; + + SkipSubscriber(Subscriber actual, long n) { + this.downstream = actual; + this.remaining = n; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + long n = remaining; + this.upstream = s; + downstream.onSubscribe(this); + s.request(n); + } + } + + @Override + public void onNext(T t) { + if (remaining != 0L) { + remaining--; + } else { + downstream.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLast.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLast.java new file mode 100755 index 0000000..bbda349 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLast.java @@ -0,0 +1,88 @@ +/** + * 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.internal.operators.flowable; + +import java.util.ArrayDeque; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +public final class FlowableSkipLast extends AbstractFlowableWithUpstream { + final int skip; + + public FlowableSkipLast(Flowable source, int skip) { + super(source); + this.skip = skip; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new SkipLastSubscriber(s, skip)); + } + + static final class SkipLastSubscriber extends ArrayDeque implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -3807491841935125653L; + final Subscriber downstream; + final int skip; + + Subscription upstream; + + SkipLastSubscriber(Subscriber actual, int skip) { + super(skip); + this.downstream = actual; + this.skip = skip; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (skip == size()) { + downstream.onNext(poll()); + } else { + upstream.request(1); + } + offer(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimed.java new file mode 100755 index 0000000..0ffd249 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimed.java @@ -0,0 +1,218 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +public final class FlowableSkipLastTimed extends AbstractFlowableWithUpstream { + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final int bufferSize; + final boolean delayError; + + public FlowableSkipLastTimed(Flowable source, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { + super(source); + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSize = bufferSize; + this.delayError = delayError; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new SkipLastTimedSubscriber(s, time, unit, scheduler, bufferSize, delayError)); + } + + static final class SkipLastTimedSubscriber extends AtomicInteger implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -5677354903406201275L; + final Subscriber downstream; + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final SpscLinkedArrayQueue queue; + final boolean delayError; + + Subscription upstream; + + final AtomicLong requested = new AtomicLong(); + + volatile boolean cancelled; + + volatile boolean done; + Throwable error; + + SkipLastTimedSubscriber(Subscriber actual, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { + this.downstream = actual; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.queue = new SpscLinkedArrayQueue(bufferSize); + this.delayError = delayError; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + long now = scheduler.now(unit); + + queue.offer(now, t); + + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + upstream.cancel(); + + if (getAndIncrement() == 0) { + queue.clear(); + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + final Subscriber a = downstream; + final SpscLinkedArrayQueue q = queue; + final boolean delayError = this.delayError; + final TimeUnit unit = this.unit; + final Scheduler scheduler = this.scheduler; + final long time = this.time; + + for (;;) { + + long r = requested.get(); + long e = 0L; + + while (e != r) { + boolean d = done; + + Long ts = (Long)q.peek(); + + boolean empty = ts == null; + + long now = scheduler.now(unit); + + if (!empty && ts > now - time) { + empty = true; + } + + if (checkTerminated(d, empty, a, delayError)) { + return; + } + + if (empty) { + break; + } + + q.poll(); + @SuppressWarnings("unchecked") + T v = (T)q.poll(); + + a.onNext(v); + + e++; + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, boolean delayError) { + if (cancelled) { + queue.clear(); + return true; + } + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + return true; + } + } else { + Throwable e = error; + if (e != null) { + queue.clear(); + a.onError(e); + return true; + } else + if (empty) { + a.onComplete(); + return true; + } + } + } + return false; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java new file mode 100755 index 0000000..0324c72 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipUntil.java @@ -0,0 +1,138 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; + +public final class FlowableSkipUntil extends AbstractFlowableWithUpstream { + final Publisher other; + public FlowableSkipUntil(Flowable source, Publisher other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber child) { + SkipUntilMainSubscriber parent = new SkipUntilMainSubscriber(child); + child.onSubscribe(parent); + + other.subscribe(parent.other); + + source.subscribe(parent); + } + + static final class SkipUntilMainSubscriber extends AtomicInteger + implements ConditionalSubscriber, Subscription { + private static final long serialVersionUID = -6270983465606289181L; + + final Subscriber downstream; + + final AtomicReference upstream; + + final AtomicLong requested; + + final OtherSubscriber other; + + final AtomicThrowable error; + + volatile boolean gate; + + SkipUntilMainSubscriber(Subscriber downstream) { + this.downstream = downstream; + this.upstream = new AtomicReference(); + this.requested = new AtomicLong(); + this.other = new OtherSubscriber(); + this.error = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t)) { + upstream.get().request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (gate) { + HalfSerializer.onNext(downstream, t, this, error); + return true; + } + return false; + } + + @Override + public void onError(Throwable t) { + SubscriptionHelper.cancel(other); + HalfSerializer.onError(downstream, t, SkipUntilMainSubscriber.this, error); + } + + @Override + public void onComplete() { + SubscriptionHelper.cancel(other); + HalfSerializer.onComplete(downstream, this, error); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + SubscriptionHelper.cancel(other); + } + + final class OtherSubscriber extends AtomicReference + implements FlowableSubscriber { + + private static final long serialVersionUID = -5592042965931999169L; + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + gate = true; + get().cancel(); + } + + @Override + public void onError(Throwable t) { + SubscriptionHelper.cancel(upstream); + HalfSerializer.onError(downstream, t, SkipUntilMainSubscriber.this, error); + } + + @Override + public void onComplete() { + gate = true; + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipWhile.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipWhile.java new file mode 100755 index 0000000..8123be6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSkipWhile.java @@ -0,0 +1,97 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +public final class FlowableSkipWhile extends AbstractFlowableWithUpstream { + final Predicate predicate; + public FlowableSkipWhile(Flowable source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new SkipWhileSubscriber(s, predicate)); + } + + static final class SkipWhileSubscriber implements FlowableSubscriber, Subscription { + final Subscriber downstream; + final Predicate predicate; + Subscription upstream; + boolean notSkipping; + SkipWhileSubscriber(Subscriber actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (notSkipping) { + downstream.onNext(t); + } else { + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + downstream.onError(e); + return; + } + if (b) { + upstream.request(1); + } else { + notSkipping = true; + downstream.onNext(t); + } + } + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOn.java new file mode 100755 index 0000000..f603aca --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSubscribeOn.java @@ -0,0 +1,160 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +/** + * Subscribes to the source Flowable on the specified Scheduler and makes + * sure downstream requests are scheduled there as well. + * + * @param the value type emitted + */ +public final class FlowableSubscribeOn extends AbstractFlowableWithUpstream { + + final Scheduler scheduler; + + final boolean nonScheduledRequests; + + public FlowableSubscribeOn(Flowable source, Scheduler scheduler, boolean nonScheduledRequests) { + super(source); + this.scheduler = scheduler; + this.nonScheduledRequests = nonScheduledRequests; + } + + @Override + public void subscribeActual(final Subscriber s) { + Scheduler.Worker w = scheduler.createWorker(); + final SubscribeOnSubscriber sos = new SubscribeOnSubscriber(s, w, source, nonScheduledRequests); + s.onSubscribe(sos); + + w.schedule(sos); + } + + static final class SubscribeOnSubscriber extends AtomicReference + implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = 8094547886072529208L; + + final Subscriber downstream; + + final Scheduler.Worker worker; + + final AtomicReference upstream; + + final AtomicLong requested; + + final boolean nonScheduledRequests; + + Publisher source; + + SubscribeOnSubscriber(Subscriber actual, Scheduler.Worker worker, Publisher source, boolean requestOn) { + this.downstream = actual; + this.worker = worker; + this.source = source; + this.upstream = new AtomicReference(); + this.requested = new AtomicLong(); + this.nonScheduledRequests = !requestOn; + } + + @Override + public void run() { + lazySet(Thread.currentThread()); + Publisher src = source; + source = null; + src.subscribe(this); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { + long r = requested.getAndSet(0L); + if (r != 0L) { + requestUpstream(r, s); + } + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + worker.dispose(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + worker.dispose(); + } + + @Override + public void request(final long n) { + if (SubscriptionHelper.validate(n)) { + Subscription s = this.upstream.get(); + if (s != null) { + requestUpstream(n, s); + } else { + BackpressureHelper.add(requested, n); + s = this.upstream.get(); + if (s != null) { + long r = requested.getAndSet(0L); + if (r != 0L) { + requestUpstream(r, s); + } + } + } + } + } + + void requestUpstream(final long n, final Subscription s) { + if (nonScheduledRequests || Thread.currentThread() == get()) { + s.request(n); + } else { + worker.schedule(new Request(s, n)); + } + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + worker.dispose(); + } + + static final class Request implements Runnable { + final Subscription upstream; + final long n; + + Request(Subscription s, long n) { + this.upstream = s; + this.n = n; + } + + @Override + public void run() { + upstream.request(n); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java new file mode 100755 index 0000000..be8febd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchIfEmpty.java @@ -0,0 +1,77 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionArbiter; + +public final class FlowableSwitchIfEmpty extends AbstractFlowableWithUpstream { + final Publisher other; + public FlowableSwitchIfEmpty(Flowable source, Publisher other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber s) { + SwitchIfEmptySubscriber parent = new SwitchIfEmptySubscriber(s, other); + s.onSubscribe(parent.arbiter); + source.subscribe(parent); + } + + static final class SwitchIfEmptySubscriber implements FlowableSubscriber { + final Subscriber downstream; + final Publisher other; + final SubscriptionArbiter arbiter; + + boolean empty; + + SwitchIfEmptySubscriber(Subscriber actual, Publisher other) { + this.downstream = actual; + this.other = other; + this.empty = true; + this.arbiter = new SubscriptionArbiter(false); + } + + @Override + public void onSubscribe(Subscription s) { + arbiter.setSubscription(s); + } + + @Override + public void onNext(T t) { + if (empty) { + empty = false; + } + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + if (empty) { + empty = false; + other.subscribe(this); + } else { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java new file mode 100755 index 0000000..caf8c23 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java @@ -0,0 +1,428 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableSwitchMap extends AbstractFlowableWithUpstream { + final Function> mapper; + final int bufferSize; + final boolean delayErrors; + + public FlowableSwitchMap(Flowable source, + Function> mapper, int bufferSize, + boolean delayErrors) { + super(source); + this.mapper = mapper; + this.bufferSize = bufferSize; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (FlowableScalarXMap.tryScalarXMapSubscribe(source, s, mapper)) { + return; + } + source.subscribe(new SwitchMapSubscriber(s, mapper, bufferSize, delayErrors)); + } + + static final class SwitchMapSubscriber extends AtomicInteger implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -3491074160481096299L; + final Subscriber downstream; + final Function> mapper; + final int bufferSize; + final boolean delayErrors; + + volatile boolean done; + final AtomicThrowable error; + + volatile boolean cancelled; + + Subscription upstream; + + final AtomicReference> active = new AtomicReference>(); + + final AtomicLong requested = new AtomicLong(); + + static final SwitchMapInnerSubscriber CANCELLED; + static { + CANCELLED = new SwitchMapInnerSubscriber(null, -1L, 1); + CANCELLED.cancel(); + } + + volatile long unique; + + SwitchMapSubscriber(Subscriber actual, + Function> mapper, int bufferSize, + boolean delayErrors) { + this.downstream = actual; + this.mapper = mapper; + this.bufferSize = bufferSize; + this.delayErrors = delayErrors; + this.error = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + long c = unique + 1; + unique = c; + + SwitchMapInnerSubscriber inner = active.get(); + if (inner != null) { + inner.cancel(); + } + + Publisher p; + try { + p = ObjectHelper.requireNonNull(mapper.apply(t), "The publisher returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + onError(e); + return; + } + + SwitchMapInnerSubscriber nextInner = new SwitchMapInnerSubscriber(this, c, bufferSize); + + for (;;) { + inner = active.get(); + if (inner == CANCELLED) { + break; + } + if (active.compareAndSet(inner, nextInner)) { + p.subscribe(nextInner); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (!done && error.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + drain(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + if (unique == 0L) { + upstream.request(Long.MAX_VALUE); + } else { + drain(); + } + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + upstream.cancel(); + + disposeInner(); + } + } + + @SuppressWarnings("unchecked") + void disposeInner() { + SwitchMapInnerSubscriber a = active.get(); + if (a != CANCELLED) { + a = active.getAndSet((SwitchMapInnerSubscriber)CANCELLED); + if (a != CANCELLED && a != null) { + a.cancel(); + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + final Subscriber a = downstream; + + int missing = 1; + + for (;;) { + + if (cancelled) { + return; + } + + if (done) { + if (delayErrors) { + if (active.get() == null) { + Throwable err = error.get(); + if (err != null) { + a.onError(error.terminate()); + } else { + a.onComplete(); + } + return; + } + } else { + Throwable err = error.get(); + if (err != null) { + disposeInner(); + a.onError(error.terminate()); + return; + } else + if (active.get() == null) { + a.onComplete(); + return; + } + } + } + + SwitchMapInnerSubscriber inner = active.get(); + SimpleQueue q = inner != null ? inner.queue : null; + if (q != null) { + if (inner.done) { + if (!delayErrors) { + Throwable err = error.get(); + if (err != null) { + disposeInner(); + a.onError(error.terminate()); + return; + } else + if (q.isEmpty()) { + active.compareAndSet(inner, null); + continue; + } + } else { + if (q.isEmpty()) { + active.compareAndSet(inner, null); + continue; + } + } + } + + long r = requested.get(); + long e = 0L; + boolean retry = false; + + while (e != r) { + if (cancelled) { + return; + } + + boolean d = inner.done; + R v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + inner.cancel(); + error.addThrowable(ex); + d = true; + v = null; + } + boolean empty = v == null; + + if (inner != active.get()) { + retry = true; + break; + } + + if (d) { + if (!delayErrors) { + Throwable err = error.get(); + if (err != null) { + a.onError(error.terminate()); + return; + } else + if (empty) { + active.compareAndSet(inner, null); + retry = true; + break; + } + } else { + if (empty) { + active.compareAndSet(inner, null); + retry = true; + break; + } + } + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e != 0L) { + if (!cancelled) { + if (r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + inner.request(e); + } + } + + if (retry) { + continue; + } + } + + missing = addAndGet(-missing); + if (missing == 0) { + break; + } + } + } + } + + static final class SwitchMapInnerSubscriber + extends AtomicReference implements FlowableSubscriber { + + private static final long serialVersionUID = 3837284832786408377L; + final SwitchMapSubscriber parent; + final long index; + final int bufferSize; + + volatile SimpleQueue queue; + + volatile boolean done; + + int fusionMode; + + SwitchMapInnerSubscriber(SwitchMapSubscriber parent, long index, int bufferSize) { + this.parent = parent; + this.index = index; + this.bufferSize = bufferSize; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); + if (m == QueueSubscription.SYNC) { + fusionMode = m; + queue = qs; + done = true; + parent.drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + fusionMode = m; + queue = qs; + s.request(bufferSize); + return; + } + } + + queue = new SpscArrayQueue(bufferSize); + + s.request(bufferSize); + } + } + + @Override + public void onNext(R t) { + SwitchMapSubscriber p = parent; + if (index == p.unique) { + if (fusionMode == QueueSubscription.NONE && !queue.offer(t)) { + onError(new MissingBackpressureException("Queue full?!")); + return; + } + p.drain(); + } + } + + @Override + public void onError(Throwable t) { + SwitchMapSubscriber p = parent; + if (index == p.unique && p.error.addThrowable(t)) { + if (!p.delayErrors) { + p.upstream.cancel(); + p.done = true; + } + done = true; + p.drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + SwitchMapSubscriber p = parent; + if (index == p.unique) { + done = true; + p.drain(); + } + } + + public void cancel() { + SubscriptionHelper.cancel(this); + } + + public void request(long n) { + if (fusionMode != QueueSubscription.SYNC) { + get().request(n); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java new file mode 100755 index 0000000..5d33c14 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTake.java @@ -0,0 +1,120 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableTake extends AbstractFlowableWithUpstream { + final long limit; + public FlowableTake(Flowable source, long limit) { + super(source); + this.limit = limit; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new TakeSubscriber(s, limit)); + } + + static final class TakeSubscriber extends AtomicBoolean implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -5636543848937116287L; + + final Subscriber downstream; + + final long limit; + + boolean done; + + Subscription upstream; + + long remaining; + + TakeSubscriber(Subscriber actual, long limit) { + this.downstream = actual; + this.limit = limit; + this.remaining = limit; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + upstream = s; + if (limit == 0L) { + s.cancel(); + done = true; + EmptySubscription.complete(downstream); + } else { + downstream.onSubscribe(this); + } + } + } + + @Override + public void onNext(T t) { + if (!done && remaining-- > 0) { + boolean stop = remaining == 0; + downstream.onNext(t); + if (stop) { + upstream.cancel(); + onComplete(); + } + } + } + + @Override + public void onError(Throwable t) { + if (!done) { + done = true; + upstream.cancel(); + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + } + } + + @Override + public void request(long n) { + if (!SubscriptionHelper.validate(n)) { + return; + } + if (!get() && compareAndSet(false, true)) { + if (n >= limit) { + upstream.request(Long.MAX_VALUE); + return; + } + } + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLast.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLast.java new file mode 100755 index 0000000..60318d0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLast.java @@ -0,0 +1,130 @@ +/** + * 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.internal.operators.flowable; + +import java.util.ArrayDeque; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +public final class FlowableTakeLast extends AbstractFlowableWithUpstream { + final int count; + + public FlowableTakeLast(Flowable source, int count) { + super(source); + this.count = count; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new TakeLastSubscriber(s, count)); + } + + static final class TakeLastSubscriber extends ArrayDeque implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 7240042530241604978L; + final Subscriber downstream; + final int count; + + Subscription upstream; + volatile boolean done; + volatile boolean cancelled; + + final AtomicLong requested = new AtomicLong(); + + final AtomicInteger wip = new AtomicInteger(); + + TakeLastSubscriber(Subscriber actual, int count) { + this.downstream = actual; + this.count = count; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (count == size()) { + poll(); + } + offer(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + } + + void drain() { + if (wip.getAndIncrement() == 0) { + Subscriber a = downstream; + long r = requested.get(); + do { + if (cancelled) { + return; + } + if (done) { + long e = 0L; + + while (e != r) { + if (cancelled) { + return; + } + T v = poll(); + if (v == null) { + a.onComplete(); + return; + } + a.onNext(v); + e++; + } + if (e != 0L && r != Long.MAX_VALUE) { + r = requested.addAndGet(-e); + } + } + } while (wip.decrementAndGet() != 0); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOne.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOne.java new file mode 100755 index 0000000..570bf3c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastOne.java @@ -0,0 +1,78 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.*; + +public final class FlowableTakeLastOne extends AbstractFlowableWithUpstream { + + public FlowableTakeLastOne(Flowable source) { + super(source); + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new TakeLastOneSubscriber(s)); + } + + static final class TakeLastOneSubscriber extends DeferredScalarSubscription + implements FlowableSubscriber { + + private static final long serialVersionUID = -5467847744262967226L; + + Subscription upstream; + + TakeLastOneSubscriber(Subscriber downstream) { + super(downstream); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + value = t; + } + + @Override + public void onError(Throwable t) { + value = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + T v = value; + if (v != null) { + complete(v); + } else { + downstream.onComplete(); + } + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimed.java new file mode 100755 index 0000000..b48cddd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimed.java @@ -0,0 +1,240 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +public final class FlowableTakeLastTimed extends AbstractFlowableWithUpstream { + final long count; + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final int bufferSize; + final boolean delayError; + + public FlowableTakeLastTimed(Flowable source, + long count, long time, TimeUnit unit, Scheduler scheduler, + int bufferSize, boolean delayError) { + super(source); + this.count = count; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSize = bufferSize; + this.delayError = delayError; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new TakeLastTimedSubscriber(s, count, time, unit, scheduler, bufferSize, delayError)); + } + + static final class TakeLastTimedSubscriber extends AtomicInteger implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -5677354903406201275L; + final Subscriber downstream; + final long count; + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final SpscLinkedArrayQueue queue; + final boolean delayError; + + Subscription upstream; + + final AtomicLong requested = new AtomicLong(); + + volatile boolean cancelled; + + volatile boolean done; + Throwable error; + + TakeLastTimedSubscriber(Subscriber actual, long count, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { + this.downstream = actual; + this.count = count; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.queue = new SpscLinkedArrayQueue(bufferSize); + this.delayError = delayError; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + final SpscLinkedArrayQueue q = queue; + + long now = scheduler.now(unit); + + q.offer(now, t); + + trim(now, q); + } + + @Override + public void onError(Throwable t) { + if (delayError) { + trim(scheduler.now(unit), queue); + } + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + trim(scheduler.now(unit), queue); + done = true; + drain(); + } + + void trim(long now, SpscLinkedArrayQueue q) { + long time = this.time; + long c = count; + boolean unbounded = c == Long.MAX_VALUE; + + while (!q.isEmpty()) { + long ts = (Long)q.peek(); + if (ts < now - time || (!unbounded && (q.size() >> 1) > c)) { + q.poll(); + q.poll(); + } else { + break; + } + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + upstream.cancel(); + + if (getAndIncrement() == 0) { + queue.clear(); + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + final Subscriber a = downstream; + final SpscLinkedArrayQueue q = queue; + final boolean delayError = this.delayError; + + for (;;) { + + if (done) { + boolean empty = q.isEmpty(); + + if (checkTerminated(empty, a, delayError)) { + return; + } + + long r = requested.get(); + long e = 0L; + + for (;;) { + Object ts = q.peek(); // the timestamp long + empty = ts == null; + + if (checkTerminated(empty, a, delayError)) { + return; + } + + if (r == e) { + break; + } + + q.poll(); + @SuppressWarnings("unchecked") + T o = (T)q.poll(); + + a.onNext(o); + + e++; + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean empty, Subscriber a, boolean delayError) { + if (cancelled) { + queue.clear(); + return true; + } + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + return true; + } + } else { + Throwable e = error; + if (e != null) { + queue.clear(); + a.onError(e); + return true; + } else + if (empty) { + a.onComplete(); + return true; + } + } + return false; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakePublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakePublisher.java new file mode 100755 index 0000000..a79501d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakePublisher.java @@ -0,0 +1,40 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.internal.operators.flowable.FlowableTake.TakeSubscriber; + +/** + * Take with a generic Publisher source. + *

History: 2.0.7 - experimental + * @param the value type + * @since 2.1 + */ +public final class FlowableTakePublisher extends Flowable { + + final Publisher source; + final long limit; + public FlowableTakePublisher(Publisher source, long limit) { + this.source = source; + this.limit = limit; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new TakeSubscriber(s, limit)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java new file mode 100755 index 0000000..180a77e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntil.java @@ -0,0 +1,125 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; + +public final class FlowableTakeUntil extends AbstractFlowableWithUpstream { + final Publisher other; + public FlowableTakeUntil(Flowable source, Publisher other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber child) { + TakeUntilMainSubscriber parent = new TakeUntilMainSubscriber(child); + child.onSubscribe(parent); + + other.subscribe(parent.other); + + source.subscribe(parent); + } + + static final class TakeUntilMainSubscriber extends AtomicInteger implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -4945480365982832967L; + + final Subscriber downstream; + + final AtomicLong requested; + + final AtomicReference upstream; + + final AtomicThrowable error; + + final OtherSubscriber other; + + TakeUntilMainSubscriber(Subscriber downstream) { + this.downstream = downstream; + this.requested = new AtomicLong(); + this.upstream = new AtomicReference(); + this.other = new OtherSubscriber(); + this.error = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); + } + + @Override + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); + } + + @Override + public void onError(Throwable t) { + SubscriptionHelper.cancel(other); + HalfSerializer.onError(downstream, t, this, error); + } + + @Override + public void onComplete() { + SubscriptionHelper.cancel(other); + HalfSerializer.onComplete(downstream, this, error); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + SubscriptionHelper.cancel(other); + } + + final class OtherSubscriber extends AtomicReference implements FlowableSubscriber { + + private static final long serialVersionUID = -3592821756711087922L; + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + SubscriptionHelper.cancel(this); + onComplete(); + } + + @Override + public void onError(Throwable t) { + SubscriptionHelper.cancel(upstream); + HalfSerializer.onError(downstream, t, TakeUntilMainSubscriber.this, error); + } + + @Override + public void onComplete() { + SubscriptionHelper.cancel(upstream); + HalfSerializer.onComplete(downstream, TakeUntilMainSubscriber.this, error); + } + + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicate.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicate.java new file mode 100755 index 0000000..cad253b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicate.java @@ -0,0 +1,104 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableTakeUntilPredicate extends AbstractFlowableWithUpstream { + final Predicate predicate; + public FlowableTakeUntilPredicate(Flowable source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new InnerSubscriber(s, predicate)); + } + + static final class InnerSubscriber implements FlowableSubscriber, Subscription { + final Subscriber downstream; + final Predicate predicate; + Subscription upstream; + boolean done; + InnerSubscriber(Subscriber actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!done) { + downstream.onNext(t); + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + onError(e); + return; + } + if (b) { + done = true; + upstream.cancel(); + downstream.onComplete(); + } + } + } + + @Override + public void onError(Throwable t) { + if (!done) { + done = true; + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + } + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeWhile.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeWhile.java new file mode 100755 index 0000000..d4436bd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTakeWhile.java @@ -0,0 +1,111 @@ +/** + * 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.internal.operators.flowable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableTakeWhile extends AbstractFlowableWithUpstream { + final Predicate predicate; + public FlowableTakeWhile(Flowable source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new TakeWhileSubscriber(s, predicate)); + } + + static final class TakeWhileSubscriber implements FlowableSubscriber, Subscription { + final Subscriber downstream; + final Predicate predicate; + + Subscription upstream; + + boolean done; + + TakeWhileSubscriber(Subscriber actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.cancel(); + onError(e); + return; + } + + if (!b) { + done = true; + upstream.cancel(); + downstream.onComplete(); + return; + } + + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java new file mode 100755 index 0000000..3aede97 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTimed.java @@ -0,0 +1,151 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.disposables.SequentialDisposable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableThrottleFirstTimed extends AbstractFlowableWithUpstream { + final long timeout; + final TimeUnit unit; + final Scheduler scheduler; + + public FlowableThrottleFirstTimed(Flowable source, long timeout, TimeUnit unit, Scheduler scheduler) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new DebounceTimedSubscriber( + new SerializedSubscriber(s), + timeout, unit, scheduler.createWorker())); + } + + static final class DebounceTimedSubscriber + extends AtomicLong + implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = -9102637559663639004L; + final Subscriber downstream; + final long timeout; + final TimeUnit unit; + final Worker worker; + + Subscription upstream; + + final SequentialDisposable timer = new SequentialDisposable(); + + volatile boolean gate; + + boolean done; + + DebounceTimedSubscriber(Subscriber actual, long timeout, TimeUnit unit, Worker worker) { + this.downstream = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + if (!gate) { + gate = true; + long r = get(); + if (r != 0L) { + downstream.onNext(t); + BackpressureHelper.produced(this, 1); + } else { + done = true; + cancel(); + downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); + return; + } + + Disposable d = timer.get(); + if (d != null) { + d.dispose(); + } + + timer.replace(worker.schedule(this, timeout, unit)); + } + } + + @Override + public void run() { + gate = false; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + worker.dispose(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + worker.dispose(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(this, n); + } + } + + @Override + public void cancel() { + upstream.cancel(); + worker.dispose(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatest.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatest.java new file mode 100755 index 0000000..b3fe22e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableThrottleLatest.java @@ -0,0 +1,246 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +/** + * Emits the next or latest item when the given time elapses. + *

+ * The operator emits the next item, then starts a timer. When the timer fires, + * it tries to emit the latest item from upstream. If there was no upstream item, + * in the meantime, the next upstream item is emitted immediately and the + * timed process repeats. + *

History: 2.1.14 - experimental + * @param the upstream and downstream value type + * @since 2.2 + */ +public final class FlowableThrottleLatest extends AbstractFlowableWithUpstream { + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + final boolean emitLast; + + public FlowableThrottleLatest(Flowable source, + long timeout, TimeUnit unit, Scheduler scheduler, + boolean emitLast) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.emitLast = emitLast; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ThrottleLatestSubscriber(s, timeout, unit, scheduler.createWorker(), emitLast)); + } + + static final class ThrottleLatestSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = -8296689127439125014L; + + final Subscriber downstream; + + final long timeout; + + final TimeUnit unit; + + final Scheduler.Worker worker; + + final boolean emitLast; + + final AtomicReference latest; + + final AtomicLong requested; + + Subscription upstream; + + volatile boolean done; + Throwable error; + + volatile boolean cancelled; + + volatile boolean timerFired; + + long emitted; + + boolean timerRunning; + + ThrottleLatestSubscriber(Subscriber downstream, + long timeout, TimeUnit unit, Scheduler.Worker worker, + boolean emitLast) { + this.downstream = downstream; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.emitLast = emitLast; + this.latest = new AtomicReference(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + latest.set(t); + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + } + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + worker.dispose(); + if (getAndIncrement() == 0) { + latest.lazySet(null); + } + } + + @Override + public void run() { + timerFired = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + AtomicReference latest = this.latest; + AtomicLong requested = this.requested; + Subscriber downstream = this.downstream; + + for (;;) { + + for (;;) { + if (cancelled) { + latest.lazySet(null); + return; + } + + boolean d = done; + + if (d && error != null) { + latest.lazySet(null); + downstream.onError(error); + worker.dispose(); + return; + } + + T v = latest.get(); + boolean empty = v == null; + + if (d) { + if (!empty && emitLast) { + v = latest.getAndSet(null); + long e = emitted; + if (e != requested.get()) { + emitted = e + 1; + downstream.onNext(v); + downstream.onComplete(); + } else { + downstream.onError(new MissingBackpressureException( + "Could not emit final value due to lack of requests")); + } + } else { + latest.lazySet(null); + downstream.onComplete(); + } + worker.dispose(); + return; + } + + if (empty) { + if (timerFired) { + timerRunning = false; + timerFired = false; + } + break; + } + + if (!timerRunning || timerFired) { + v = latest.getAndSet(null); + long e = emitted; + if (e != requested.get()) { + downstream.onNext(v); + emitted = e + 1; + } else { + upstream.cancel(); + downstream.onError(new MissingBackpressureException( + "Could not emit value due to lack of requests")); + worker.dispose(); + return; + } + + timerFired = false; + timerRunning = true; + worker.schedule(this, timeout, unit); + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java new file mode 100755 index 0000000..cf8b708 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeInterval.java @@ -0,0 +1,92 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.schedulers.Timed; + +public final class FlowableTimeInterval extends AbstractFlowableWithUpstream> { + final Scheduler scheduler; + final TimeUnit unit; + + public FlowableTimeInterval(Flowable source, TimeUnit unit, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + this.unit = unit; + } + + @Override + protected void subscribeActual(Subscriber> s) { + source.subscribe(new TimeIntervalSubscriber(s, unit, scheduler)); + } + + static final class TimeIntervalSubscriber implements FlowableSubscriber, Subscription { + final Subscriber> downstream; + final TimeUnit unit; + final Scheduler scheduler; + + Subscription upstream; + + long lastTime; + + TimeIntervalSubscriber(Subscriber> actual, TimeUnit unit, Scheduler scheduler) { + this.downstream = actual; + this.scheduler = scheduler; + this.unit = unit; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + lastTime = scheduler.now(unit); + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + long now = scheduler.now(unit); + long last = lastTime; + lastTime = now; + long delta = now - last; + downstream.onNext(new Timed(t, delta, unit)); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java new file mode 100755 index 0000000..8363a5d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeout.java @@ -0,0 +1,389 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.SequentialDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.flowable.FlowableTimeoutTimed.TimeoutSupport; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableTimeout extends AbstractFlowableWithUpstream { + final Publisher firstTimeoutIndicator; + final Function> itemTimeoutIndicator; + final Publisher other; + + public FlowableTimeout( + Flowable source, + Publisher firstTimeoutIndicator, + Function> itemTimeoutIndicator, + Publisher other) { + super(source); + this.firstTimeoutIndicator = firstTimeoutIndicator; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (other == null) { + TimeoutSubscriber parent = new TimeoutSubscriber(s, itemTimeoutIndicator); + s.onSubscribe(parent); + parent.startFirstTimeout(firstTimeoutIndicator); + source.subscribe(parent); + } else { + TimeoutFallbackSubscriber parent = new TimeoutFallbackSubscriber(s, itemTimeoutIndicator, other); + s.onSubscribe(parent); + parent.startFirstTimeout(firstTimeoutIndicator); + source.subscribe(parent); + } + } + + interface TimeoutSelectorSupport extends TimeoutSupport { + void onTimeoutError(long idx, Throwable ex); + } + + static final class TimeoutSubscriber extends AtomicLong + implements FlowableSubscriber, Subscription, TimeoutSelectorSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Subscriber downstream; + + final Function> itemTimeoutIndicator; + + final SequentialDisposable task; + + final AtomicReference upstream; + + final AtomicLong requested; + + TimeoutSubscriber(Subscriber actual, Function> itemTimeoutIndicator) { + this.downstream = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(upstream, requested, s); + } + + @Override + public void onNext(T t) { + long idx = get(); + if (idx == Long.MAX_VALUE || !compareAndSet(idx, idx + 1)) { + return; + } + + Disposable d = task.get(); + if (d != null) { + d.dispose(); + } + + downstream.onNext(t); + + Publisher itemTimeoutPublisher; + + try { + itemTimeoutPublisher = ObjectHelper.requireNonNull( + itemTimeoutIndicator.apply(t), + "The itemTimeoutIndicator returned a null Publisher."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().cancel(); + getAndSet(Long.MAX_VALUE); + downstream.onError(ex); + return; + } + + TimeoutConsumer consumer = new TimeoutConsumer(idx + 1, this); + if (task.replace(consumer)) { + itemTimeoutPublisher.subscribe(consumer); + } + } + + void startFirstTimeout(Publisher firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer consumer = new TimeoutConsumer(0L, this); + if (task.replace(consumer)) { + firstTimeoutIndicator.subscribe(consumer); + } + } + } + + @Override + public void onError(Throwable t) { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + } + } + + @Override + public void onTimeout(long idx) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); + + downstream.onError(new TimeoutException()); + } + } + + @Override + public void onTimeoutError(long idx, Throwable ex) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); + + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + task.dispose(); + } + } + + static final class TimeoutFallbackSubscriber extends SubscriptionArbiter + implements FlowableSubscriber, TimeoutSelectorSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Subscriber downstream; + + final Function> itemTimeoutIndicator; + + final SequentialDisposable task; + + final AtomicReference upstream; + + final AtomicLong index; + + Publisher fallback; + + long consumed; + + TimeoutFallbackSubscriber(Subscriber actual, + Function> itemTimeoutIndicator, + Publisher fallback) { + super(true); + this.downstream = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference(); + this.fallback = fallback; + this.index = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { + setSubscription(s); + } + } + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + Disposable d = task.get(); + if (d != null) { + d.dispose(); + } + + consumed++; + + downstream.onNext(t); + + Publisher itemTimeoutPublisher; + + try { + itemTimeoutPublisher = ObjectHelper.requireNonNull( + itemTimeoutIndicator.apply(t), + "The itemTimeoutIndicator returned a null Publisher."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().cancel(); + index.getAndSet(Long.MAX_VALUE); + downstream.onError(ex); + return; + } + + TimeoutConsumer consumer = new TimeoutConsumer(idx + 1, this); + if (task.replace(consumer)) { + itemTimeoutPublisher.subscribe(consumer); + } + } + + void startFirstTimeout(Publisher firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer consumer = new TimeoutConsumer(0L, this); + if (task.replace(consumer)) { + firstTimeoutIndicator.subscribe(consumer); + } + } + } + + @Override + public void onError(Throwable t) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + task.dispose(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + task.dispose(); + } + } + + @Override + public void onTimeout(long idx) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); + + Publisher f = fallback; + fallback = null; + + long c = consumed; + if (c != 0L) { + produced(c); + } + + f.subscribe(new FlowableTimeoutTimed.FallbackSubscriber(downstream, this)); + } + } + + @Override + public void onTimeoutError(long idx, Throwable ex) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); + + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void cancel() { + super.cancel(); + task.dispose(); + } + } + + static final class TimeoutConsumer extends AtomicReference + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = 8708641127342403073L; + + final TimeoutSelectorSupport parent; + + final long idx; + + TimeoutConsumer(long idx, TimeoutSelectorSupport parent) { + this.idx = idx; + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + Subscription upstream = get(); + if (upstream != SubscriptionHelper.CANCELLED) { + upstream.cancel(); + lazySet(SubscriptionHelper.CANCELLED); + parent.onTimeout(idx); + } + } + + @Override + public void onError(Throwable t) { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + parent.onTimeoutError(idx, t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + parent.onTimeout(idx); + } + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean isDisposed() { + return this.get() == SubscriptionHelper.CANCELLED; + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java new file mode 100755 index 0000000..d25acdc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTimed.java @@ -0,0 +1,324 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.disposables.SequentialDisposable; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + +public final class FlowableTimeoutTimed extends AbstractFlowableWithUpstream { + final long timeout; + final TimeUnit unit; + final Scheduler scheduler; + final Publisher other; + + public FlowableTimeoutTimed(Flowable source, + long timeout, TimeUnit unit, Scheduler scheduler, Publisher other) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber s) { + if (other == null) { + TimeoutSubscriber parent = new TimeoutSubscriber(s, timeout, unit, scheduler.createWorker()); + s.onSubscribe(parent); + parent.startTimeout(0L); + source.subscribe(parent); + } else { + TimeoutFallbackSubscriber parent = new TimeoutFallbackSubscriber(s, timeout, unit, scheduler.createWorker(), other); + s.onSubscribe(parent); + parent.startTimeout(0L); + source.subscribe(parent); + } + } + + static final class TimeoutSubscriber extends AtomicLong + implements FlowableSubscriber, Subscription, TimeoutSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Subscriber downstream; + + final long timeout; + + final TimeUnit unit; + + final Scheduler.Worker worker; + + final SequentialDisposable task; + + final AtomicReference upstream; + + final AtomicLong requested; + + TimeoutSubscriber(Subscriber actual, long timeout, TimeUnit unit, Scheduler.Worker worker) { + this.downstream = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(upstream, requested, s); + } + + @Override + public void onNext(T t) { + long idx = get(); + if (idx == Long.MAX_VALUE || !compareAndSet(idx, idx + 1)) { + return; + } + + task.get().dispose(); + + downstream.onNext(t); + + startTimeout(idx + 1); + } + + void startTimeout(long nextIndex) { + task.replace(worker.schedule(new TimeoutTask(nextIndex, this), timeout, unit)); + } + + @Override + public void onError(Throwable t) { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + worker.dispose(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + worker.dispose(); + } + } + + @Override + public void onTimeout(long idx) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); + + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); + + worker.dispose(); + } + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + worker.dispose(); + } + } + + static final class TimeoutTask implements Runnable { + + final TimeoutSupport parent; + + final long idx; + + TimeoutTask(long idx, TimeoutSupport parent) { + this.idx = idx; + this.parent = parent; + } + + @Override + public void run() { + parent.onTimeout(idx); + } + } + + static final class TimeoutFallbackSubscriber extends SubscriptionArbiter + implements FlowableSubscriber, TimeoutSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Subscriber downstream; + + final long timeout; + + final TimeUnit unit; + + final Scheduler.Worker worker; + + final SequentialDisposable task; + + final AtomicReference upstream; + + final AtomicLong index; + + long consumed; + + Publisher fallback; + + TimeoutFallbackSubscriber(Subscriber actual, long timeout, TimeUnit unit, + Scheduler.Worker worker, Publisher fallback) { + super(true); + this.downstream = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.fallback = fallback; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference(); + this.index = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(upstream, s)) { + setSubscription(s); + } + } + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + task.get().dispose(); + + consumed++; + + downstream.onNext(t); + + startTimeout(idx + 1); + } + + void startTimeout(long nextIndex) { + task.replace(worker.schedule(new TimeoutTask(nextIndex, this), timeout, unit)); + } + + @Override + public void onError(Throwable t) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + worker.dispose(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + worker.dispose(); + } + } + + @Override + public void onTimeout(long idx) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + SubscriptionHelper.cancel(upstream); + + long c = consumed; + if (c != 0L) { + produced(c); + } + + Publisher f = fallback; + fallback = null; + + f.subscribe(new FallbackSubscriber(downstream, this)); + + worker.dispose(); + } + } + + @Override + public void cancel() { + super.cancel(); + worker.dispose(); + } + } + + static final class FallbackSubscriber implements FlowableSubscriber { + + final Subscriber downstream; + + final SubscriptionArbiter arbiter; + + FallbackSubscriber(Subscriber actual, SubscriptionArbiter arbiter) { + this.downstream = actual; + this.arbiter = arbiter; + } + + @Override + public void onSubscribe(Subscription s) { + arbiter.setSubscription(s); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } + + interface TimeoutSupport { + + void onTimeout(long idx); + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimer.java new file mode 100755 index 0000000..7c82926 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableTimer.java @@ -0,0 +1,90 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +public final class FlowableTimer extends Flowable { + final Scheduler scheduler; + final long delay; + final TimeUnit unit; + public FlowableTimer(long delay, TimeUnit unit, Scheduler scheduler) { + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void subscribeActual(Subscriber s) { + TimerSubscriber ios = new TimerSubscriber(s); + s.onSubscribe(ios); + + Disposable d = scheduler.scheduleDirect(ios, delay, unit); + + ios.setResource(d); + } + + static final class TimerSubscriber extends AtomicReference + implements Subscription, Runnable { + + private static final long serialVersionUID = -2809475196591179431L; + + final Subscriber downstream; + + volatile boolean requested; + + TimerSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + requested = true; + } + } + + @Override + public void cancel() { + DisposableHelper.dispose(this); + } + + @Override + public void run() { + if (get() != DisposableHelper.DISPOSED) { + if (requested) { + downstream.onNext(0L); + lazySet(EmptyDisposable.INSTANCE); + downstream.onComplete(); + } else { + lazySet(EmptyDisposable.INSTANCE); + downstream.onError(new MissingBackpressureException("Can't deliver value due to lack of requests")); + } + } + } + + public void setResource(Disposable d) { + DisposableHelper.trySet(this, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java new file mode 100755 index 0000000..60508e3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToList.java @@ -0,0 +1,93 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; + +public final class FlowableToList> extends AbstractFlowableWithUpstream { + final Callable collectionSupplier; + + public FlowableToList(Flowable source, Callable collectionSupplier) { + super(source); + this.collectionSupplier = collectionSupplier; + } + + @Override + protected void subscribeActual(Subscriber s) { + U coll; + try { + coll = ObjectHelper.requireNonNull(collectionSupplier.call(), "The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + source.subscribe(new ToListSubscriber(s, coll)); + } + + static final class ToListSubscriber> + extends DeferredScalarSubscription + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -8134157938864266736L; + Subscription upstream; + + ToListSubscriber(Subscriber actual, U collection) { + super(actual); + this.value = collection; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + U v = value; + if (v != null) { + v.add(t); + } + } + + @Override + public void onError(Throwable t) { + value = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + complete(value); + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableToListSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToListSingle.java new file mode 100755 index 0000000..e4a8abd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableToListSingle.java @@ -0,0 +1,117 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.FuseToFlowable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.ArrayListSupplier; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableToListSingle> extends Single implements FuseToFlowable { + + final Flowable source; + + final Callable collectionSupplier; + + @SuppressWarnings("unchecked") + public FlowableToListSingle(Flowable source) { + this(source, (Callable)ArrayListSupplier.asCallable()); + } + + public FlowableToListSingle(Flowable source, Callable collectionSupplier) { + this.source = source; + this.collectionSupplier = collectionSupplier; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + U coll; + try { + coll = ObjectHelper.requireNonNull(collectionSupplier.call(), "The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + source.subscribe(new ToListSubscriber(observer, coll)); + } + + @Override + public Flowable fuseToFlowable() { + return RxJavaPlugins.onAssembly(new FlowableToList(source, collectionSupplier)); + } + + static final class ToListSubscriber> + implements FlowableSubscriber, Disposable { + + final SingleObserver downstream; + + Subscription upstream; + + U value; + + ToListSubscriber(SingleObserver actual, U collection) { + this.downstream = actual; + this.value = collection; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + value.add(t); + } + + @Override + public void onError(Throwable t) { + value = null; + upstream = SubscriptionHelper.CANCELLED; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = SubscriptionHelper.CANCELLED; + downstream.onSuccess(value); + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOn.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOn.java new file mode 100755 index 0000000..0790d4b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOn.java @@ -0,0 +1,100 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableUnsubscribeOn extends AbstractFlowableWithUpstream { + final Scheduler scheduler; + public FlowableUnsubscribeOn(Flowable source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new UnsubscribeSubscriber(s, scheduler)); + } + + static final class UnsubscribeSubscriber extends AtomicBoolean implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 1015244841293359600L; + + final Subscriber downstream; + final Scheduler scheduler; + + Subscription upstream; + + UnsubscribeSubscriber(Subscriber actual, Scheduler scheduler) { + this.downstream = actual; + this.scheduler = scheduler; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!get()) { + downstream.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + if (get()) { + RxJavaPlugins.onError(t); + return; + } + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!get()) { + downstream.onComplete(); + } + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + if (compareAndSet(false, true)) { + scheduler.scheduleDirect(new Cancellation()); + } + } + + final class Cancellation implements Runnable { + @Override + public void run() { + upstream.cancel(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableUsing.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableUsing.java new file mode 100755 index 0000000..2d3a83f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableUsing.java @@ -0,0 +1,179 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableUsing extends Flowable { + final Callable resourceSupplier; + final Function> sourceSupplier; + final Consumer disposer; + final boolean eager; + + public FlowableUsing(Callable resourceSupplier, + Function> sourceSupplier, + Consumer disposer, + boolean eager) { + this.resourceSupplier = resourceSupplier; + this.sourceSupplier = sourceSupplier; + this.disposer = disposer; + this.eager = eager; + } + + @Override + public void subscribeActual(Subscriber s) { + D resource; + + try { + resource = resourceSupplier.call(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, s); + return; + } + + Publisher source; + try { + source = ObjectHelper.requireNonNull(sourceSupplier.apply(resource), "The sourceSupplier returned a null Publisher"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + try { + disposer.accept(resource); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(new CompositeException(e, ex), s); + return; + } + EmptySubscription.error(e, s); + return; + } + + UsingSubscriber us = new UsingSubscriber(s, resource, disposer, eager); + + source.subscribe(us); + } + + static final class UsingSubscriber extends AtomicBoolean implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 5904473792286235046L; + + final Subscriber downstream; + final D resource; + final Consumer disposer; + final boolean eager; + + Subscription upstream; + + UsingSubscriber(Subscriber actual, D resource, Consumer disposer, boolean eager) { + this.downstream = actual; + this.resource = resource; + this.disposer = disposer; + this.eager = eager; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (eager) { + Throwable innerError = null; + if (compareAndSet(false, true)) { + try { + disposer.accept(resource); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + innerError = e; + } + } + + upstream.cancel(); + if (innerError != null) { + downstream.onError(new CompositeException(t, innerError)); + } else { + downstream.onError(t); + } + } else { + downstream.onError(t); + upstream.cancel(); + disposeAfter(); + } + } + + @Override + public void onComplete() { + if (eager) { + if (compareAndSet(false, true)) { + try { + disposer.accept(resource); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + } + + upstream.cancel(); + downstream.onComplete(); + } else { + downstream.onComplete(); + upstream.cancel(); + disposeAfter(); + } + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + disposeAfter(); + upstream.cancel(); + } + + void disposeAfter() { + if (compareAndSet(false, true)) { + try { + disposer.accept(resource); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call actual.onError unless it is serialized, which is expensive + RxJavaPlugins.onError(e); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java new file mode 100755 index 0000000..e9c1259 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindow.java @@ -0,0 +1,532 @@ +/** + * 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.internal.operators.flowable; + +import java.util.ArrayDeque; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.UnicastProcessor; + +public final class FlowableWindow extends AbstractFlowableWithUpstream> { + final long size; + + final long skip; + + final int bufferSize; + + public FlowableWindow(Flowable source, long size, long skip, int bufferSize) { + super(source); + this.size = size; + this.skip = skip; + this.bufferSize = bufferSize; + } + + @Override + public void subscribeActual(Subscriber> s) { + if (skip == size) { + source.subscribe(new WindowExactSubscriber(s, size, bufferSize)); + } else + if (skip > size) { + source.subscribe(new WindowSkipSubscriber(s, size, skip, bufferSize)); + } else { + source.subscribe(new WindowOverlapSubscriber(s, size, skip, bufferSize)); + } + } + + static final class WindowExactSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = -2365647875069161133L; + + final Subscriber> downstream; + + final long size; + + final AtomicBoolean once; + + final int bufferSize; + + long index; + + Subscription upstream; + + UnicastProcessor window; + + WindowExactSubscriber(Subscriber> actual, long size, int bufferSize) { + super(1); + this.downstream = actual; + this.size = size; + this.once = new AtomicBoolean(); + this.bufferSize = bufferSize; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + long i = index; + + UnicastProcessor w = window; + if (i == 0) { + getAndIncrement(); + + w = UnicastProcessor.create(bufferSize, this); + window = w; + + downstream.onNext(w); + } + + i++; + + w.onNext(t); + + if (i == size) { + index = 0; + window = null; + w.onComplete(); + } else { + index = i; + } + } + + @Override + public void onError(Throwable t) { + Processor w = window; + if (w != null) { + window = null; + w.onError(t); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + Processor w = window; + if (w != null) { + window = null; + w.onComplete(); + } + + downstream.onComplete(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + long u = BackpressureHelper.multiplyCap(size, n); + upstream.request(u); + } + } + + @Override + public void cancel() { + if (once.compareAndSet(false, true)) { + run(); + } + } + + @Override + public void run() { + if (decrementAndGet() == 0) { + upstream.cancel(); + } + } + } + + static final class WindowSkipSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = -8792836352386833856L; + + final Subscriber> downstream; + + final long size; + + final long skip; + + final AtomicBoolean once; + + final AtomicBoolean firstRequest; + + final int bufferSize; + + long index; + + Subscription upstream; + + UnicastProcessor window; + + WindowSkipSubscriber(Subscriber> actual, long size, long skip, int bufferSize) { + super(1); + this.downstream = actual; + this.size = size; + this.skip = skip; + this.once = new AtomicBoolean(); + this.firstRequest = new AtomicBoolean(); + this.bufferSize = bufferSize; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + long i = index; + + UnicastProcessor w = window; + if (i == 0) { + getAndIncrement(); + + w = UnicastProcessor.create(bufferSize, this); + window = w; + + downstream.onNext(w); + } + + i++; + + if (w != null) { + w.onNext(t); + } + + if (i == size) { + window = null; + w.onComplete(); + } + + if (i == skip) { + index = 0; + } else { + index = i; + } + } + + @Override + public void onError(Throwable t) { + Processor w = window; + if (w != null) { + window = null; + w.onError(t); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + Processor w = window; + if (w != null) { + window = null; + w.onComplete(); + } + + downstream.onComplete(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + if (!firstRequest.get() && firstRequest.compareAndSet(false, true)) { + long u = BackpressureHelper.multiplyCap(size, n); + long v = BackpressureHelper.multiplyCap(skip - size, n - 1); + long w = BackpressureHelper.addCap(u, v); + upstream.request(w); + } else { + long u = BackpressureHelper.multiplyCap(skip, n); + upstream.request(u); + } + } + } + + @Override + public void cancel() { + if (once.compareAndSet(false, true)) { + run(); + } + } + + @Override + public void run() { + if (decrementAndGet() == 0) { + upstream.cancel(); + } + } + } + + static final class WindowOverlapSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = 2428527070996323976L; + + final Subscriber> downstream; + + final SpscLinkedArrayQueue> queue; + + final long size; + + final long skip; + + final ArrayDeque> windows; + + final AtomicBoolean once; + + final AtomicBoolean firstRequest; + + final AtomicLong requested; + + final AtomicInteger wip; + + final int bufferSize; + + long index; + + long produced; + + Subscription upstream; + + volatile boolean done; + Throwable error; + + volatile boolean cancelled; + + WindowOverlapSubscriber(Subscriber> actual, long size, long skip, int bufferSize) { + super(1); + this.downstream = actual; + this.size = size; + this.skip = skip; + this.queue = new SpscLinkedArrayQueue>(bufferSize); + this.windows = new ArrayDeque>(); + this.once = new AtomicBoolean(); + this.firstRequest = new AtomicBoolean(); + this.requested = new AtomicLong(); + this.wip = new AtomicInteger(); + this.bufferSize = bufferSize; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + long i = index; + + if (i == 0) { + if (!cancelled) { + getAndIncrement(); + + UnicastProcessor w = UnicastProcessor.create(bufferSize, this); + + windows.offer(w); + + queue.offer(w); + drain(); + } + } + + i++; + + for (Processor w : windows) { + w.onNext(t); + } + + long p = produced + 1; + if (p == size) { + produced = p - skip; + + Processor w = windows.poll(); + if (w != null) { + w.onComplete(); + } + } else { + produced = p; + } + + if (i == skip) { + index = 0; + } else { + index = i; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + + for (Processor w : windows) { + w.onError(t); + } + windows.clear(); + + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + + for (Processor w : windows) { + w.onComplete(); + } + windows.clear(); + + done = true; + drain(); + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + final Subscriber> a = downstream; + final SpscLinkedArrayQueue> q = queue; + int missed = 1; + + for (;;) { + + long r = requested.get(); + long e = 0; + + while (e != r) { + boolean d = done; + + UnicastProcessor t = q.poll(); + + boolean empty = t == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + e++; + } + + if (e == r) { + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + } + + if (e != 0L && r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, SpscLinkedArrayQueue q) { + if (cancelled) { + q.clear(); + return true; + } + + if (d) { + Throwable e = error; + + if (e != null) { + q.clear(); + a.onError(e); + return true; + } else + if (empty) { + a.onComplete(); + return true; + } + } + + return false; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + + if (!firstRequest.get() && firstRequest.compareAndSet(false, true)) { + long u = BackpressureHelper.multiplyCap(skip, n - 1); + long v = BackpressureHelper.addCap(size, u); + upstream.request(v); + } else { + long u = BackpressureHelper.multiplyCap(skip, n); + upstream.request(u); + } + + drain(); + } + } + + @Override + public void cancel() { + cancelled = true; + if (once.compareAndSet(false, true)) { + run(); + } + } + + @Override + public void run() { + if (decrementAndGet() == 0) { + upstream.cancel(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java new file mode 100755 index 0000000..a398eb0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundary.java @@ -0,0 +1,303 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.UnicastProcessor; +import io.reactivex.subscribers.DisposableSubscriber; + +public final class FlowableWindowBoundary extends AbstractFlowableWithUpstream> { + final Publisher other; + final int capacityHint; + + public FlowableWindowBoundary(Flowable source, Publisher other, int capacityHint) { + super(source); + this.other = other; + this.capacityHint = capacityHint; + } + + @Override + protected void subscribeActual(Subscriber> subscriber) { + WindowBoundaryMainSubscriber parent = new WindowBoundaryMainSubscriber(subscriber, capacityHint); + + subscriber.onSubscribe(parent); + + parent.innerNext(); + + other.subscribe(parent.boundarySubscriber); + + source.subscribe(parent); + } + + static final class WindowBoundaryMainSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = 2233020065421370272L; + + final Subscriber> downstream; + + final int capacityHint; + + final WindowBoundaryInnerSubscriber boundarySubscriber; + + final AtomicReference upstream; + + final AtomicInteger windows; + + final MpscLinkedQueue queue; + + final AtomicThrowable errors; + + final AtomicBoolean stopWindows; + + final AtomicLong requested; + + static final Object NEXT_WINDOW = new Object(); + + volatile boolean done; + + UnicastProcessor window; + + long emitted; + + WindowBoundaryMainSubscriber(Subscriber> downstream, int capacityHint) { + this.downstream = downstream; + this.capacityHint = capacityHint; + this.boundarySubscriber = new WindowBoundaryInnerSubscriber(this); + this.upstream = new AtomicReference(); + this.windows = new AtomicInteger(1); + this.queue = new MpscLinkedQueue(); + this.errors = new AtomicThrowable(); + this.stopWindows = new AtomicBoolean(); + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(upstream, s, Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable e) { + boundarySubscriber.dispose(); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + boundarySubscriber.dispose(); + done = true; + drain(); + } + + @Override + public void cancel() { + if (stopWindows.compareAndSet(false, true)) { + boundarySubscriber.dispose(); + if (windows.decrementAndGet() == 0) { + SubscriptionHelper.cancel(upstream); + } + } + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + } + + @Override + public void run() { + if (windows.decrementAndGet() == 0) { + SubscriptionHelper.cancel(upstream); + } + } + + void innerNext() { + queue.offer(NEXT_WINDOW); + drain(); + } + + void innerError(Throwable e) { + SubscriptionHelper.cancel(upstream); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + void innerComplete() { + SubscriptionHelper.cancel(upstream); + done = true; + drain(); + } + + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber> downstream = this.downstream; + MpscLinkedQueue queue = this.queue; + AtomicThrowable errors = this.errors; + long emitted = this.emitted; + + for (;;) { + + for (;;) { + if (windows.get() == 0) { + queue.clear(); + window = null; + return; + } + + UnicastProcessor w = window; + + boolean d = done; + + if (d && errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + return; + } + + Object v = queue.poll(); + + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + if (w != null) { + window = null; + w.onComplete(); + } + downstream.onComplete(); + } else { + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + if (v != NEXT_WINDOW) { + w.onNext((T)v); + continue; + } + + if (w != null) { + window = null; + w.onComplete(); + } + + if (!stopWindows.get()) { + w = UnicastProcessor.create(capacityHint, this); + window = w; + windows.getAndIncrement(); + + if (emitted != requested.get()) { + emitted++; + downstream.onNext(w); + } else { + SubscriptionHelper.cancel(upstream); + boundarySubscriber.dispose(); + errors.addThrowable(new MissingBackpressureException("Could not deliver a window due to lack of requests")); + done = true; + } + } + } + + this.emitted = emitted; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class WindowBoundaryInnerSubscriber extends DisposableSubscriber { + + final WindowBoundaryMainSubscriber parent; + + boolean done; + + WindowBoundaryInnerSubscriber(WindowBoundaryMainSubscriber parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + if (done) { + return; + } + parent.innerNext(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + parent.innerError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.innerComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java new file mode 100755 index 0000000..d9d6ffa --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySelector.java @@ -0,0 +1,387 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.Flowable; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.subscribers.QueueDrainSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.NotificationLite; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.UnicastProcessor; +import io.reactivex.subscribers.*; + +public final class FlowableWindowBoundarySelector extends AbstractFlowableWithUpstream> { + final Publisher open; + final Function> close; + final int bufferSize; + + public FlowableWindowBoundarySelector( + Flowable source, + Publisher open, Function> close, + int bufferSize) { + super(source); + this.open = open; + this.close = close; + this.bufferSize = bufferSize; + } + + @Override + protected void subscribeActual(Subscriber> s) { + source.subscribe(new WindowBoundaryMainSubscriber( + new SerializedSubscriber>(s), + open, close, bufferSize)); + } + + static final class WindowBoundaryMainSubscriber + extends QueueDrainSubscriber> + implements Subscription { + final Publisher open; + final Function> close; + final int bufferSize; + final CompositeDisposable resources; + + Subscription upstream; + + final AtomicReference boundary = new AtomicReference(); + + final List> ws; + + final AtomicLong windows = new AtomicLong(); + + final AtomicBoolean stopWindows = new AtomicBoolean(); + + WindowBoundaryMainSubscriber(Subscriber> actual, + Publisher open, Function> close, int bufferSize) { + super(actual, new MpscLinkedQueue()); + this.open = open; + this.close = close; + this.bufferSize = bufferSize; + this.resources = new CompositeDisposable(); + this.ws = new ArrayList>(); + windows.lazySet(1); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + if (stopWindows.get()) { + return; + } + + OperatorWindowBoundaryOpenSubscriber os = new OperatorWindowBoundaryOpenSubscriber(this); + + if (boundary.compareAndSet(null, os)) { + s.request(Long.MAX_VALUE); + open.subscribe(os); + } + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (fastEnter()) { + for (UnicastProcessor w : ws) { + w.onNext(t); + } + if (leave(-1) == 0) { + return; + } + } else { + queue.offer(NotificationLite.next(t)); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + + if (enter()) { + drainLoop(); + } + + if (windows.decrementAndGet() == 0) { + resources.dispose(); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + if (enter()) { + drainLoop(); + } + + if (windows.decrementAndGet() == 0) { + resources.dispose(); + } + + downstream.onComplete(); + } + + void error(Throwable t) { + upstream.cancel(); + resources.dispose(); + DisposableHelper.dispose(boundary); + + downstream.onError(t); + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + if (stopWindows.compareAndSet(false, true)) { + DisposableHelper.dispose(boundary); + if (windows.decrementAndGet() == 0) { + upstream.cancel(); + } + } + } + + void dispose() { + resources.dispose(); + DisposableHelper.dispose(boundary); + } + + void drainLoop() { + final SimplePlainQueue q = queue; + final Subscriber> a = downstream; + final List> ws = this.ws; + int missed = 1; + + for (;;) { + + for (;;) { + boolean d = done; + Object o = q.poll(); + + boolean empty = o == null; + + if (d && empty) { + dispose(); + Throwable e = error; + if (e != null) { + for (UnicastProcessor w : ws) { + w.onError(e); + } + } else { + for (UnicastProcessor w : ws) { + w.onComplete(); + } + } + ws.clear(); + return; + } + + if (empty) { + break; + } + + if (o instanceof WindowOperation) { + @SuppressWarnings("unchecked") + WindowOperation wo = (WindowOperation) o; + + UnicastProcessor w = wo.w; + if (w != null) { + if (ws.remove(wo.w)) { + wo.w.onComplete(); + + if (windows.decrementAndGet() == 0) { + dispose(); + return; + } + } + continue; + } + + if (stopWindows.get()) { + continue; + } + + w = UnicastProcessor.create(bufferSize); + + long r = requested(); + if (r != 0L) { + ws.add(w); + a.onNext(w); + if (r != Long.MAX_VALUE) { + produced(1); + } + } else { + cancel(); + a.onError(new MissingBackpressureException("Could not deliver new window due to lack of requests")); + continue; + } + + Publisher p; + + try { + p = ObjectHelper.requireNonNull(close.apply(wo.open), "The publisher supplied is null"); + } catch (Throwable e) { + cancel(); + a.onError(e); + continue; + } + + OperatorWindowBoundaryCloseSubscriber cl = new OperatorWindowBoundaryCloseSubscriber(this, w); + + if (resources.add(cl)) { + windows.getAndIncrement(); + + p.subscribe(cl); + } + + continue; + } + + for (UnicastProcessor w : ws) { + w.onNext(NotificationLite.getValue(o)); + } + } + + missed = leave(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public boolean accept(Subscriber> a, Object v) { + // not used by this operator + return false; + } + + void open(B b) { + queue.offer(new WindowOperation(null, b)); + if (enter()) { + drainLoop(); + } + } + + void close(OperatorWindowBoundaryCloseSubscriber w) { + resources.delete(w); + queue.offer(new WindowOperation(w.w, null)); + if (enter()) { + drainLoop(); + } + } + } + + static final class WindowOperation { + final UnicastProcessor w; + final B open; + WindowOperation(UnicastProcessor w, B open) { + this.w = w; + this.open = open; + } + } + + static final class OperatorWindowBoundaryOpenSubscriber extends DisposableSubscriber { + final WindowBoundaryMainSubscriber parent; + + OperatorWindowBoundaryOpenSubscriber(WindowBoundaryMainSubscriber parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + parent.open(t); + } + + @Override + public void onError(Throwable t) { + parent.error(t); + } + + @Override + public void onComplete() { + parent.onComplete(); + } + } + + static final class OperatorWindowBoundaryCloseSubscriber extends DisposableSubscriber { + final WindowBoundaryMainSubscriber parent; + final UnicastProcessor w; + + boolean done; + + OperatorWindowBoundaryCloseSubscriber(WindowBoundaryMainSubscriber parent, UnicastProcessor w) { + this.parent = parent; + this.w = w; + } + + @Override + public void onNext(V t) { + cancel(); + onComplete(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + parent.error(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.close(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySupplier.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySupplier.java new file mode 100755 index 0000000..f82c56a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowBoundarySupplier.java @@ -0,0 +1,338 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.UnicastProcessor; +import io.reactivex.subscribers.DisposableSubscriber; + +public final class FlowableWindowBoundarySupplier extends AbstractFlowableWithUpstream> { + final Callable> other; + final int capacityHint; + + public FlowableWindowBoundarySupplier(Flowable source, + Callable> other, int capacityHint) { + super(source); + this.other = other; + this.capacityHint = capacityHint; + } + + @Override + protected void subscribeActual(Subscriber> subscriber) { + WindowBoundaryMainSubscriber parent = new WindowBoundaryMainSubscriber(subscriber, capacityHint, other); + + source.subscribe(parent); + } + + static final class WindowBoundaryMainSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = 2233020065421370272L; + + final Subscriber> downstream; + + final int capacityHint; + + final AtomicReference> boundarySubscriber; + + static final WindowBoundaryInnerSubscriber BOUNDARY_DISPOSED = new WindowBoundaryInnerSubscriber(null); + + final AtomicInteger windows; + + final MpscLinkedQueue queue; + + final AtomicThrowable errors; + + final AtomicBoolean stopWindows; + + final Callable> other; + + static final Object NEXT_WINDOW = new Object(); + + final AtomicLong requested; + + Subscription upstream; + + volatile boolean done; + + UnicastProcessor window; + + long emitted; + + WindowBoundaryMainSubscriber(Subscriber> downstream, int capacityHint, Callable> other) { + this.downstream = downstream; + this.capacityHint = capacityHint; + this.boundarySubscriber = new AtomicReference>(); + this.windows = new AtomicInteger(1); + this.queue = new MpscLinkedQueue(); + this.errors = new AtomicThrowable(); + this.stopWindows = new AtomicBoolean(); + this.other = other; + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + queue.offer(NEXT_WINDOW); + drain(); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable e) { + disposeBoundary(); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + disposeBoundary(); + done = true; + drain(); + } + + @Override + public void cancel() { + if (stopWindows.compareAndSet(false, true)) { + disposeBoundary(); + if (windows.decrementAndGet() == 0) { + upstream.cancel(); + } + } + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + void disposeBoundary() { + Disposable d = boundarySubscriber.getAndSet((WindowBoundaryInnerSubscriber)BOUNDARY_DISPOSED); + if (d != null && d != BOUNDARY_DISPOSED) { + d.dispose(); + } + } + + @Override + public void run() { + if (windows.decrementAndGet() == 0) { + upstream.cancel(); + } + } + + void innerNext(WindowBoundaryInnerSubscriber sender) { + boundarySubscriber.compareAndSet(sender, null); + queue.offer(NEXT_WINDOW); + drain(); + } + + void innerError(Throwable e) { + upstream.cancel(); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + void innerComplete() { + upstream.cancel(); + done = true; + drain(); + } + + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber> downstream = this.downstream; + MpscLinkedQueue queue = this.queue; + AtomicThrowable errors = this.errors; + long emitted = this.emitted; + + for (;;) { + + for (;;) { + if (windows.get() == 0) { + queue.clear(); + window = null; + return; + } + + UnicastProcessor w = window; + + boolean d = done; + + if (d && errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + return; + } + + Object v = queue.poll(); + + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + if (w != null) { + window = null; + w.onComplete(); + } + downstream.onComplete(); + } else { + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + if (v != NEXT_WINDOW) { + w.onNext((T)v); + continue; + } + + if (w != null) { + window = null; + w.onComplete(); + } + + if (!stopWindows.get()) { + if (emitted != requested.get()) { + w = UnicastProcessor.create(capacityHint, this); + window = w; + windows.getAndIncrement(); + + Publisher otherSource; + + try { + otherSource = ObjectHelper.requireNonNull(other.call(), "The other Callable returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errors.addThrowable(ex); + done = true; + continue; + } + + WindowBoundaryInnerSubscriber bo = new WindowBoundaryInnerSubscriber(this); + + if (boundarySubscriber.compareAndSet(null, bo)) { + otherSource.subscribe(bo); + + emitted++; + downstream.onNext(w); + } + } else { + upstream.cancel(); + disposeBoundary(); + errors.addThrowable(new MissingBackpressureException("Could not deliver a window due to lack of requests")); + done = true; + } + } + } + + this.emitted = emitted; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class WindowBoundaryInnerSubscriber extends DisposableSubscriber { + final WindowBoundaryMainSubscriber parent; + + boolean done; + + WindowBoundaryInnerSubscriber(WindowBoundaryMainSubscriber parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + if (done) { + return; + } + done = true; + dispose(); + parent.innerNext(this); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + parent.innerError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.innerComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java new file mode 100755 index 0000000..01976f5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java @@ -0,0 +1,828 @@ +/** + * 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.internal.operators.flowable; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.subscribers.QueueDrainSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.NotificationLite; +import io.reactivex.processors.UnicastProcessor; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableWindowTimed extends AbstractFlowableWithUpstream> { + final long timespan; + final long timeskip; + final TimeUnit unit; + final Scheduler scheduler; + final long maxSize; + final int bufferSize; + final boolean restartTimerOnMaxSize; + + public FlowableWindowTimed(Flowable source, + long timespan, long timeskip, TimeUnit unit, Scheduler scheduler, long maxSize, + int bufferSize, boolean restartTimerOnMaxSize) { + super(source); + this.timespan = timespan; + this.timeskip = timeskip; + this.unit = unit; + this.scheduler = scheduler; + this.maxSize = maxSize; + this.bufferSize = bufferSize; + this.restartTimerOnMaxSize = restartTimerOnMaxSize; + } + + @Override + protected void subscribeActual(Subscriber> s) { + SerializedSubscriber> actual = new SerializedSubscriber>(s); + + if (timespan == timeskip) { + if (maxSize == Long.MAX_VALUE) { + source.subscribe(new WindowExactUnboundedSubscriber( + actual, + timespan, unit, scheduler, bufferSize)); + return; + } + source.subscribe(new WindowExactBoundedSubscriber( + actual, + timespan, unit, scheduler, + bufferSize, maxSize, restartTimerOnMaxSize)); + return; + } + source.subscribe(new WindowSkipSubscriber(actual, + timespan, timeskip, unit, scheduler.createWorker(), bufferSize)); + } + + static final class WindowExactUnboundedSubscriber + extends QueueDrainSubscriber> + implements FlowableSubscriber, Subscription, Runnable { + final long timespan; + final TimeUnit unit; + final Scheduler scheduler; + final int bufferSize; + + Subscription upstream; + + UnicastProcessor window; + + final SequentialDisposable timer = new SequentialDisposable(); + + static final Object NEXT = new Object(); + + volatile boolean terminated; + + WindowExactUnboundedSubscriber(Subscriber> actual, long timespan, TimeUnit unit, + Scheduler scheduler, int bufferSize) { + super(actual, new MpscLinkedQueue()); + this.timespan = timespan; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSize = bufferSize; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + window = UnicastProcessor.create(bufferSize); + + Subscriber> a = downstream; + a.onSubscribe(this); + + long r = requested(); + if (r != 0L) { + a.onNext(window); + if (r != Long.MAX_VALUE) { + produced(1); + } + } else { + cancelled = true; + s.cancel(); + a.onError(new MissingBackpressureException("Could not deliver first window due to lack of requests.")); + return; + } + + if (!cancelled) { + if (timer.replace(scheduler.schedulePeriodicallyDirect(this, timespan, timespan, unit))) { + s.request(Long.MAX_VALUE); + } + } + } + } + + @Override + public void onNext(T t) { + if (terminated) { + return; + } + if (fastEnter()) { + window.onNext(t); + if (leave(-1) == 0) { + return; + } + } else { + queue.offer(NotificationLite.next(t)); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onComplete(); + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + cancelled = true; + } + + @Override + public void run() { + if (cancelled) { + terminated = true; + } + queue.offer(NEXT); + if (enter()) { + drainLoop(); + } + } + + void drainLoop() { + + final SimplePlainQueue q = queue; + final Subscriber> a = downstream; + UnicastProcessor w = window; + + int missed = 1; + for (;;) { + + for (;;) { + boolean term = terminated; // NOPMD + + boolean d = done; + + Object o = q.poll(); + + if (d && (o == null || o == NEXT)) { + window = null; + q.clear(); + Throwable err = error; + if (err != null) { + w.onError(err); + } else { + w.onComplete(); + } + timer.dispose(); + return; + } + + if (o == null) { + break; + } + + if (o == NEXT) { + w.onComplete(); + if (!term) { + w = UnicastProcessor.create(bufferSize); + window = w; + + long r = requested(); + if (r != 0L) { + a.onNext(w); + if (r != Long.MAX_VALUE) { + produced(1); + } + } else { + window = null; + queue.clear(); + upstream.cancel(); + a.onError(new MissingBackpressureException("Could not deliver first window due to lack of requests.")); + timer.dispose(); + return; + } + } else { + upstream.cancel(); + } + continue; + } + + w.onNext(NotificationLite.getValue(o)); + } + + missed = leave(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class WindowExactBoundedSubscriber + extends QueueDrainSubscriber> + implements Subscription { + final long timespan; + final TimeUnit unit; + final Scheduler scheduler; + final int bufferSize; + final boolean restartTimerOnMaxSize; + final long maxSize; + final Worker worker; + + long count; + + long producerIndex; + + Subscription upstream; + + UnicastProcessor window; + + volatile boolean terminated; + + final SequentialDisposable timer = new SequentialDisposable(); + + WindowExactBoundedSubscriber( + Subscriber> actual, + long timespan, TimeUnit unit, Scheduler scheduler, + int bufferSize, long maxSize, boolean restartTimerOnMaxSize) { + super(actual, new MpscLinkedQueue()); + this.timespan = timespan; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSize = bufferSize; + this.maxSize = maxSize; + this.restartTimerOnMaxSize = restartTimerOnMaxSize; + if (restartTimerOnMaxSize) { + worker = scheduler.createWorker(); + } else { + worker = null; + } + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + + this.upstream = s; + + Subscriber> a = downstream; + + a.onSubscribe(this); + + if (cancelled) { + return; + } + + UnicastProcessor w = UnicastProcessor.create(bufferSize); + window = w; + + long r = requested(); + if (r != 0L) { + a.onNext(w); + if (r != Long.MAX_VALUE) { + produced(1); + } + } else { + cancelled = true; + s.cancel(); + a.onError(new MissingBackpressureException("Could not deliver initial window due to lack of requests.")); + return; + } + + Disposable task; + ConsumerIndexHolder consumerIndexHolder = new ConsumerIndexHolder(producerIndex, this); + if (restartTimerOnMaxSize) { + task = worker.schedulePeriodically(consumerIndexHolder, timespan, timespan, unit); + } else { + task = scheduler.schedulePeriodicallyDirect(consumerIndexHolder, timespan, timespan, unit); + } + + if (timer.replace(task)) { + s.request(Long.MAX_VALUE); + } + } + } + + @Override + public void onNext(T t) { + if (terminated) { + return; + } + + if (fastEnter()) { + UnicastProcessor w = window; + w.onNext(t); + + long c = count + 1; + + if (c >= maxSize) { + producerIndex++; + count = 0; + + w.onComplete(); + + long r = requested(); + + if (r != 0L) { + w = UnicastProcessor.create(bufferSize); + window = w; + downstream.onNext(w); + if (r != Long.MAX_VALUE) { + produced(1); + } + if (restartTimerOnMaxSize) { + Disposable tm = timer.get(); + + tm.dispose(); + Disposable task = worker.schedulePeriodically( + new ConsumerIndexHolder(producerIndex, this), timespan, timespan, unit); + timer.replace(task); + } + } else { + window = null; + upstream.cancel(); + downstream.onError(new MissingBackpressureException("Could not deliver window due to lack of requests")); + disposeTimer(); + return; + } + } else { + count = c; + } + + if (leave(-1) == 0) { + return; + } + } else { + queue.offer(NotificationLite.next(t)); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onComplete(); + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + cancelled = true; + } + + public void disposeTimer() { + timer.dispose(); + Worker w = worker; + if (w != null) { + w.dispose(); + } + } + + void drainLoop() { + final SimplePlainQueue q = queue; + final Subscriber> a = downstream; + UnicastProcessor w = window; + + int missed = 1; + for (;;) { + + for (;;) { + if (terminated) { + upstream.cancel(); + q.clear(); + disposeTimer(); + return; + } + + boolean d = done; + + Object o = q.poll(); + + boolean empty = o == null; + boolean isHolder = o instanceof ConsumerIndexHolder; + + if (d && (empty || isHolder)) { + window = null; + q.clear(); + Throwable err = error; + if (err != null) { + w.onError(err); + } else { + w.onComplete(); + } + disposeTimer(); + return; + } + + if (empty) { + break; + } + + if (isHolder) { + ConsumerIndexHolder consumerIndexHolder = (ConsumerIndexHolder) o; + if (!restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { + w.onComplete(); + count = 0; + w = UnicastProcessor.create(bufferSize); + window = w; + + long r = requested(); + if (r != 0L) { + a.onNext(w); + if (r != Long.MAX_VALUE) { + produced(1); + } + } else { + window = null; + queue.clear(); + upstream.cancel(); + a.onError(new MissingBackpressureException("Could not deliver first window due to lack of requests.")); + disposeTimer(); + return; + } + } + continue; + } + + w.onNext(NotificationLite.getValue(o)); + long c = count + 1; + + if (c >= maxSize) { + producerIndex++; + count = 0; + + w.onComplete(); + + long r = requested(); + + if (r != 0L) { + w = UnicastProcessor.create(bufferSize); + window = w; + downstream.onNext(w); + if (r != Long.MAX_VALUE) { + produced(1); + } + + if (restartTimerOnMaxSize) { + Disposable tm = timer.get(); + tm.dispose(); + + Disposable task = worker.schedulePeriodically( + new ConsumerIndexHolder(producerIndex, this), timespan, timespan, unit); + timer.replace(task); + } + + } else { + window = null; + upstream.cancel(); + downstream.onError(new MissingBackpressureException("Could not deliver window due to lack of requests")); + disposeTimer(); + return; + } + } else { + count = c; + } + } + + missed = leave(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConsumerIndexHolder implements Runnable { + final long index; + final WindowExactBoundedSubscriber parent; + ConsumerIndexHolder(long index, WindowExactBoundedSubscriber parent) { + this.index = index; + this.parent = parent; + } + + @Override + public void run() { + WindowExactBoundedSubscriber p = parent; + + if (!p.cancelled) { + p.queue.offer(this); + } else { + p.terminated = true; + } + if (p.enter()) { + p.drainLoop(); + } + } + } + } + + static final class WindowSkipSubscriber + extends QueueDrainSubscriber> + implements Subscription, Runnable { + final long timespan; + final long timeskip; + final TimeUnit unit; + final Worker worker; + final int bufferSize; + + final List> windows; + + Subscription upstream; + + volatile boolean terminated; + + WindowSkipSubscriber(Subscriber> actual, + long timespan, long timeskip, TimeUnit unit, + Worker worker, int bufferSize) { + super(actual, new MpscLinkedQueue()); + this.timespan = timespan; + this.timeskip = timeskip; + this.unit = unit; + this.worker = worker; + this.bufferSize = bufferSize; + this.windows = new LinkedList>(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + + this.upstream = s; + + downstream.onSubscribe(this); + + if (cancelled) { + return; + } + + long r = requested(); + if (r != 0L) { + final UnicastProcessor w = UnicastProcessor.create(bufferSize); + windows.add(w); + + downstream.onNext(w); + if (r != Long.MAX_VALUE) { + produced(1); + } + worker.schedule(new Completion(w), timespan, unit); + + worker.schedulePeriodically(this, timeskip, timeskip, unit); + + s.request(Long.MAX_VALUE); + + } else { + s.cancel(); + downstream.onError(new MissingBackpressureException("Could not emit the first window due to lack of requests")); + } + } + } + + @Override + public void onNext(T t) { + if (fastEnter()) { + for (UnicastProcessor w : windows) { + w.onNext(t); + } + if (leave(-1) == 0) { + return; + } + } else { + queue.offer(t); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onComplete(); + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + cancelled = true; + } + + void complete(UnicastProcessor w) { + queue.offer(new SubjectWork(w, false)); + if (enter()) { + drainLoop(); + } + } + + @SuppressWarnings("unchecked") + void drainLoop() { + final SimplePlainQueue q = queue; + final Subscriber> a = downstream; + final List> ws = windows; + + int missed = 1; + + for (;;) { + + for (;;) { + if (terminated) { + upstream.cancel(); + q.clear(); + ws.clear(); + worker.dispose(); + return; + } + + boolean d = done; + + Object v = q.poll(); + + boolean empty = v == null; + boolean sw = v instanceof SubjectWork; + + if (d && (empty || sw)) { + q.clear(); + Throwable e = error; + if (e != null) { + for (UnicastProcessor w : ws) { + w.onError(e); + } + } else { + for (UnicastProcessor w : ws) { + w.onComplete(); + } + } + ws.clear(); + worker.dispose(); + return; + } + + if (empty) { + break; + } + + if (sw) { + SubjectWork work = (SubjectWork)v; + + if (work.open) { + if (cancelled) { + continue; + } + + long r = requested(); + if (r != 0L) { + final UnicastProcessor w = UnicastProcessor.create(bufferSize); + ws.add(w); + a.onNext(w); + if (r != Long.MAX_VALUE) { + produced(1); + } + + worker.schedule(new Completion(w), timespan, unit); + } else { + a.onError(new MissingBackpressureException("Can't emit window due to lack of requests")); + } + } else { + ws.remove(work.w); + work.w.onComplete(); + if (ws.isEmpty() && cancelled) { + terminated = true; + } + } + } else { + for (UnicastProcessor w : ws) { + w.onNext((T)v); + } + } + } + + missed = leave(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void run() { + + UnicastProcessor w = UnicastProcessor.create(bufferSize); + + SubjectWork sw = new SubjectWork(w, true); + if (!cancelled) { + queue.offer(sw); + } + if (enter()) { + drainLoop(); + } + } + + static final class SubjectWork { + final UnicastProcessor w; + final boolean open; + SubjectWork(UnicastProcessor w, boolean open) { + this.w = w; + this.open = open; + } + } + + final class Completion implements Runnable { + private final UnicastProcessor processor; + + Completion(UnicastProcessor processor) { + this.processor = processor; + } + + @Override + public void run() { + complete(processor); + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java new file mode 100755 index 0000000..00017d4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFrom.java @@ -0,0 +1,163 @@ +/** + * 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.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.subscribers.SerializedSubscriber; + +public final class FlowableWithLatestFrom extends AbstractFlowableWithUpstream { + final BiFunction combiner; + final Publisher other; + public FlowableWithLatestFrom(Flowable source, BiFunction combiner, Publisher other) { + super(source); + this.combiner = combiner; + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber s) { + final SerializedSubscriber serial = new SerializedSubscriber(s); + final WithLatestFromSubscriber wlf = new WithLatestFromSubscriber(serial, combiner); + + serial.onSubscribe(wlf); + + other.subscribe(new FlowableWithLatestSubscriber(wlf)); + + source.subscribe(wlf); + } + + static final class WithLatestFromSubscriber extends AtomicReference + implements ConditionalSubscriber, Subscription { + + private static final long serialVersionUID = -312246233408980075L; + + final Subscriber downstream; + + final BiFunction combiner; + + final AtomicReference upstream = new AtomicReference(); + + final AtomicLong requested = new AtomicLong(); + + final AtomicReference other = new AtomicReference(); + + WithLatestFromSubscriber(Subscriber actual, BiFunction combiner) { + this.downstream = actual; + this.combiner = combiner; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t)) { + upstream.get().request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + U u = get(); + if (u != null) { + R r; + try { + r = ObjectHelper.requireNonNull(combiner.apply(t, u), "The combiner returned a null value"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancel(); + downstream.onError(e); + return false; + } + downstream.onNext(r); + return true; + } else { + return false; + } + } + + @Override + public void onError(Throwable t) { + SubscriptionHelper.cancel(other); + downstream.onError(t); + } + + @Override + public void onComplete() { + SubscriptionHelper.cancel(other); + downstream.onComplete(); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + SubscriptionHelper.cancel(other); + } + + public boolean setOther(Subscription o) { + return SubscriptionHelper.setOnce(other, o); + } + + public void otherError(Throwable e) { + SubscriptionHelper.cancel(upstream); + downstream.onError(e); + } + } + + final class FlowableWithLatestSubscriber implements FlowableSubscriber { + private final WithLatestFromSubscriber wlf; + + FlowableWithLatestSubscriber(WithLatestFromSubscriber wlf) { + this.wlf = wlf; + } + + @Override + public void onSubscribe(Subscription s) { + if (wlf.setOther(s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(U t) { + wlf.lazySet(t); + } + + @Override + public void onError(Throwable t) { + wlf.otherError(t); + } + + @Override + public void onComplete() { + // nothing to do, the wlf will complete on its own pace + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java new file mode 100755 index 0000000..6d6b949 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromMany.java @@ -0,0 +1,303 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Arrays; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.annotations.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Combines a main sequence of values with the latest from multiple other sequences via + * a selector function. + * + * @param the main sequence's type + * @param the output type + */ +public final class FlowableWithLatestFromMany extends AbstractFlowableWithUpstream { + @Nullable + final Publisher[] otherArray; + + @Nullable + final Iterable> otherIterable; + + final Function combiner; + + public FlowableWithLatestFromMany(@NonNull Flowable source, @NonNull Publisher[] otherArray, Function combiner) { + super(source); + this.otherArray = otherArray; + this.otherIterable = null; + this.combiner = combiner; + } + + public FlowableWithLatestFromMany(@NonNull Flowable source, @NonNull Iterable> otherIterable, @NonNull Function combiner) { + super(source); + this.otherArray = null; + this.otherIterable = otherIterable; + this.combiner = combiner; + } + + @Override + protected void subscribeActual(Subscriber s) { + Publisher[] others = otherArray; + int n = 0; + if (others == null) { + others = new Publisher[8]; + + try { + for (Publisher p : otherIterable) { + if (n == others.length) { + others = Arrays.copyOf(others, n + (n >> 1)); + } + others[n++] = p; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, s); + return; + } + + } else { + n = others.length; + } + + if (n == 0) { + new FlowableMap(source, new SingletonArrayFunc()).subscribeActual(s); + return; + } + + WithLatestFromSubscriber parent = new WithLatestFromSubscriber(s, combiner, n); + s.onSubscribe(parent); + parent.subscribe(others, n); + + source.subscribe(parent); + } + + static final class WithLatestFromSubscriber + extends AtomicInteger + implements ConditionalSubscriber, Subscription { + + private static final long serialVersionUID = 1577321883966341961L; + + final Subscriber downstream; + + final Function combiner; + + final WithLatestInnerSubscriber[] subscribers; + + final AtomicReferenceArray values; + + final AtomicReference upstream; + + final AtomicLong requested; + + final AtomicThrowable error; + + volatile boolean done; + + WithLatestFromSubscriber(Subscriber actual, Function combiner, int n) { + this.downstream = actual; + this.combiner = combiner; + WithLatestInnerSubscriber[] s = new WithLatestInnerSubscriber[n]; + for (int i = 0; i < n; i++) { + s[i] = new WithLatestInnerSubscriber(this, i); + } + this.subscribers = s; + this.values = new AtomicReferenceArray(n); + this.upstream = new AtomicReference(); + this.requested = new AtomicLong(); + this.error = new AtomicThrowable(); + } + + void subscribe(Publisher[] others, int n) { + WithLatestInnerSubscriber[] subscribers = this.subscribers; + AtomicReference upstream = this.upstream; + for (int i = 0; i < n; i++) { + if (upstream.get() == SubscriptionHelper.CANCELLED) { + return; + } + others[i].subscribe(subscribers[i]); + } + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t) && !done) { + upstream.get().request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + AtomicReferenceArray ara = values; + int n = ara.length(); + Object[] objects = new Object[n + 1]; + objects[0] = t; + + for (int i = 0; i < n; i++) { + Object o = ara.get(i); + if (o == null) { + // somebody hasn't signalled yet, skip this T + return false; + } + objects[i + 1] = o; + } + + R v; + + try { + v = ObjectHelper.requireNonNull(combiner.apply(objects), "The combiner returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(); + onError(ex); + return false; + } + + HalfSerializer.onNext(downstream, v, this, error); + return true; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + cancelAllBut(-1); + HalfSerializer.onError(downstream, t, this, error); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + cancelAllBut(-1); + HalfSerializer.onComplete(downstream, this, error); + } + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(upstream); + for (WithLatestInnerSubscriber s : subscribers) { + s.dispose(); + } + } + + void innerNext(int index, Object o) { + values.set(index, o); + } + + void innerError(int index, Throwable t) { + done = true; + SubscriptionHelper.cancel(upstream); + cancelAllBut(index); + HalfSerializer.onError(downstream, t, this, error); + } + + void innerComplete(int index, boolean nonEmpty) { + if (!nonEmpty) { + done = true; + SubscriptionHelper.cancel(upstream); + cancelAllBut(index); + HalfSerializer.onComplete(downstream, this, error); + } + } + + void cancelAllBut(int index) { + WithLatestInnerSubscriber[] subscribers = this.subscribers; + for (int i = 0; i < subscribers.length; i++) { + if (i != index) { + subscribers[i].dispose(); + } + } + } + } + + static final class WithLatestInnerSubscriber + extends AtomicReference + implements FlowableSubscriber { + + private static final long serialVersionUID = 3256684027868224024L; + + final WithLatestFromSubscriber parent; + + final int index; + + boolean hasValue; + + WithLatestInnerSubscriber(WithLatestFromSubscriber parent, int index) { + this.parent = parent; + this.index = index; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + if (!hasValue) { + hasValue = true; + } + parent.innerNext(index, t); + } + + @Override + public void onError(Throwable t) { + parent.innerError(index, t); + } + + @Override + public void onComplete() { + parent.innerComplete(index, hasValue); + } + + void dispose() { + SubscriptionHelper.cancel(this); + } + } + + final class SingletonArrayFunc implements Function { + @Override + public R apply(T t) throws Exception { + return ObjectHelper.requireNonNull(combiner.apply(new Object[] { t }), "The combiner returned a null value"); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java new file mode 100755 index 0000000..b8516a9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZip.java @@ -0,0 +1,413 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Arrays; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableZip extends Flowable { + + final Publisher[] sources; + final Iterable> sourcesIterable; + final Function zipper; + final int bufferSize; + final boolean delayError; + + public FlowableZip(Publisher[] sources, + Iterable> sourcesIterable, + Function zipper, + int bufferSize, + boolean delayError) { + this.sources = sources; + this.sourcesIterable = sourcesIterable; + this.zipper = zipper; + this.bufferSize = bufferSize; + this.delayError = delayError; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribeActual(Subscriber s) { + Publisher[] sources = this.sources; + int count = 0; + if (sources == null) { + sources = new Publisher[8]; + for (Publisher p : sourcesIterable) { + if (count == sources.length) { + Publisher[] b = new Publisher[count + (count >> 2)]; + System.arraycopy(sources, 0, b, 0, count); + sources = b; + } + sources[count++] = p; + } + } else { + count = sources.length; + } + + if (count == 0) { + EmptySubscription.complete(s); + return; + } + + ZipCoordinator coordinator = new ZipCoordinator(s, zipper, count, bufferSize, delayError); + + s.onSubscribe(coordinator); + + coordinator.subscribe(sources, count); + } + + static final class ZipCoordinator + extends AtomicInteger + implements Subscription { + + private static final long serialVersionUID = -2434867452883857743L; + + final Subscriber downstream; + + final ZipSubscriber[] subscribers; + + final Function zipper; + + final AtomicLong requested; + + final AtomicThrowable errors; + + final boolean delayErrors; + + volatile boolean cancelled; + + final Object[] current; + + ZipCoordinator(Subscriber actual, + Function zipper, int n, int prefetch, boolean delayErrors) { + this.downstream = actual; + this.zipper = zipper; + this.delayErrors = delayErrors; + @SuppressWarnings("unchecked") + ZipSubscriber[] a = new ZipSubscriber[n]; + for (int i = 0; i < n; i++) { + a[i] = new ZipSubscriber(this, prefetch); + } + this.current = new Object[n]; + this.subscribers = a; + this.requested = new AtomicLong(); + this.errors = new AtomicThrowable(); + } + + void subscribe(Publisher[] sources, int n) { + ZipSubscriber[] a = subscribers; + for (int i = 0; i < n; i++) { + if (cancelled || (!delayErrors && errors.get() != null)) { + return; + } + sources[i].subscribe(a[i]); + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + cancelAll(); + } + } + + void error(ZipSubscriber inner, Throwable e) { + if (errors.addThrowable(e)) { + inner.done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + void cancelAll() { + for (ZipSubscriber s : subscribers) { + s.cancel(); + } + } + + void drain() { + + if (getAndIncrement() != 0) { + return; + } + + final Subscriber a = downstream; + final ZipSubscriber[] qs = subscribers; + final int n = qs.length; + Object[] values = current; + + int missed = 1; + + for (;;) { + + long r = requested.get(); + long e = 0L; + + while (r != e) { + + if (cancelled) { + return; + } + + if (!delayErrors && errors.get() != null) { + cancelAll(); + a.onError(errors.terminate()); + return; + } + + boolean empty = false; + + for (int j = 0; j < n; j++) { + ZipSubscriber inner = qs[j]; + if (values[j] == null) { + try { + boolean d = inner.done; + SimpleQueue q = inner.queue; + + T v = q != null ? q.poll() : null; + + boolean sourceEmpty = v == null; + if (d && sourceEmpty) { + cancelAll(); + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + } else { + a.onComplete(); + } + return; + } + if (!sourceEmpty) { + values[j] = v; + } else { + empty = true; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + errors.addThrowable(ex); + if (!delayErrors) { + cancelAll(); + a.onError(errors.terminate()); + return; + } + empty = true; + } + } + } + + if (empty) { + break; + } + + R v; + + try { + v = ObjectHelper.requireNonNull(zipper.apply(values.clone()), "The zipper returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelAll(); + errors.addThrowable(ex); + a.onError(errors.terminate()); + return; + } + + a.onNext(v); + + e++; + + Arrays.fill(values, null); + } + + if (r == e) { + if (cancelled) { + return; + } + + if (!delayErrors && errors.get() != null) { + cancelAll(); + a.onError(errors.terminate()); + return; + } + + for (int j = 0; j < n; j++) { + ZipSubscriber inner = qs[j]; + if (values[j] == null) { + try { + boolean d = inner.done; + SimpleQueue q = inner.queue; + T v = q != null ? q.poll() : null; + + boolean empty = v == null; + if (d && empty) { + cancelAll(); + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + } else { + a.onComplete(); + } + return; + } + if (!empty) { + values[j] = v; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errors.addThrowable(ex); + if (!delayErrors) { + cancelAll(); + a.onError(errors.terminate()); + return; + } + } + } + } + + } + + if (e != 0L) { + + for (ZipSubscriber inner : qs) { + inner.request(e); + } + + if (r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class ZipSubscriber extends AtomicReference implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -4627193790118206028L; + + final ZipCoordinator parent; + + final int prefetch; + + final int limit; + + SimpleQueue queue; + + long produced; + + volatile boolean done; + + int sourceMode; + + ZipSubscriber(ZipCoordinator parent, int prefetch) { + this.parent = parent; + this.prefetch = prefetch; + this.limit = prefetch - (prefetch >> 2); + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + if (s instanceof QueueSubscription) { + QueueSubscription f = (QueueSubscription) s; + + int m = f.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); + + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = f; + done = true; + parent.drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = f; + s.request(prefetch); + return; + } + } + + queue = new SpscArrayQueue(prefetch); + + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (sourceMode != QueueSubscription.ASYNC) { + queue.offer(t); + } + parent.drain(); + } + + @Override + public void onError(Throwable t) { + parent.error(this, t); + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(this); + } + + @Override + public void request(long n) { + if (sourceMode != QueueSubscription.SYNC) { + long p = produced + n; + if (p >= limit) { + produced = 0L; + get().request(p); + } else { + produced = p; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableZipIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZipIterable.java new file mode 100755 index 0000000..7a81c2d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableZipIterable.java @@ -0,0 +1,171 @@ +/** + * 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.internal.operators.flowable; + +import java.util.Iterator; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class FlowableZipIterable extends AbstractFlowableWithUpstream { + final Iterable other; + final BiFunction zipper; + + public FlowableZipIterable( + Flowable source, + Iterable other, BiFunction zipper) { + super(source); + this.other = other; + this.zipper = zipper; + } + + @Override + public void subscribeActual(Subscriber t) { + Iterator it; + + try { + it = ObjectHelper.requireNonNull(other.iterator(), "The iterator returned by other is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, t); + return; + } + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptySubscription.error(e, t); + return; + } + + if (!b) { + EmptySubscription.complete(t); + return; + } + + source.subscribe(new ZipIterableSubscriber(t, it, zipper)); + } + + static final class ZipIterableSubscriber implements FlowableSubscriber, Subscription { + final Subscriber downstream; + final Iterator iterator; + final BiFunction zipper; + + Subscription upstream; + + boolean done; + + ZipIterableSubscriber(Subscriber actual, Iterator iterator, + BiFunction zipper) { + this.downstream = actual; + this.iterator = iterator; + this.zipper = zipper; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + U u; + + try { + u = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null value"); + } catch (Throwable e) { + error(e); + return; + } + + V v; + try { + v = ObjectHelper.requireNonNull(zipper.apply(t, u), "The zipper function returned a null value"); + } catch (Throwable e) { + error(e); + return; + } + + downstream.onNext(v); + + boolean b; + + try { + b = iterator.hasNext(); + } catch (Throwable e) { + error(e); + return; + } + + if (!b) { + done = true; + upstream.cancel(); + downstream.onComplete(); + } + } + + void error(Throwable e) { + Exceptions.throwIfFatal(e); + done = true; + upstream.cancel(); + downstream.onError(e); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/AbstractMaybeWithUpstream.java b/src/main/java/io/reactivex/internal/operators/maybe/AbstractMaybeWithUpstream.java new file mode 100755 index 0000000..7daf5ff --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/AbstractMaybeWithUpstream.java @@ -0,0 +1,37 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; + +/** + * Abstract base class for intermediate Maybe operators that take an upstream MaybeSource. + * + * @param the source value type + * @param the output value type + */ +abstract class AbstractMaybeWithUpstream extends Maybe implements HasUpstreamMaybeSource { + + protected final MaybeSource source; + + AbstractMaybeWithUpstream(MaybeSource source) { + this.source = source; + } + + @Override + public final MaybeSource source() { + return source; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java new file mode 100755 index 0000000..8efc69b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeAmb.java @@ -0,0 +1,152 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Signals the event of the first MaybeSource that signals. + * + * @param the value type emitted + */ +public final class MaybeAmb extends Maybe { + private final MaybeSource[] sources; + private final Iterable> sourcesIterable; + + public MaybeAmb(MaybeSource[] sources, Iterable> sourcesIterable) { + this.sources = sources; + this.sourcesIterable = sourcesIterable; + } + + @Override + @SuppressWarnings("unchecked") + protected void subscribeActual(MaybeObserver observer) { + MaybeSource[] sources = this.sources; + int count = 0; + if (sources == null) { + sources = new MaybeSource[8]; + try { + for (MaybeSource element : sourcesIterable) { + if (element == null) { + EmptyDisposable.error(new NullPointerException("One of the sources is null"), observer); + return; + } + if (count == sources.length) { + MaybeSource[] b = new MaybeSource[count + (count >> 2)]; + System.arraycopy(sources, 0, b, 0, count); + sources = b; + } + sources[count++] = element; + } + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + } else { + count = sources.length; + } + + CompositeDisposable set = new CompositeDisposable(); + observer.onSubscribe(set); + + AtomicBoolean winner = new AtomicBoolean(); + + for (int i = 0; i < count; i++) { + MaybeSource s = sources[i]; + if (set.isDisposed()) { + return; + } + + if (s == null) { + set.dispose(); + NullPointerException ex = new NullPointerException("One of the MaybeSources is null"); + if (winner.compareAndSet(false, true)) { + observer.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + return; + } + + s.subscribe(new AmbMaybeObserver(observer, set, winner)); + } + + if (count == 0) { + observer.onComplete(); + } + } + + static final class AmbMaybeObserver + implements MaybeObserver { + + final MaybeObserver downstream; + + final AtomicBoolean winner; + + final CompositeDisposable set; + + Disposable upstream; + + AmbMaybeObserver(MaybeObserver downstream, CompositeDisposable set, AtomicBoolean winner) { + this.downstream = downstream; + this.set = set; + this.winner = winner; + } + + @Override + public void onSubscribe(Disposable d) { + upstream = d; + set.add(d); + } + + @Override + public void onSuccess(T value) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); + set.dispose(); + + downstream.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); + set.dispose(); + + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); + set.dispose(); + + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCache.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCache.java new file mode 100755 index 0000000..c8adbd7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCache.java @@ -0,0 +1,198 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; + +/** + * Consumes the source once and replays its signal to any current or future MaybeObservers. + * + * @param the value type + */ +public final class MaybeCache extends Maybe implements MaybeObserver { + + @SuppressWarnings("rawtypes") + static final CacheDisposable[] EMPTY = new CacheDisposable[0]; + + @SuppressWarnings("rawtypes") + static final CacheDisposable[] TERMINATED = new CacheDisposable[0]; + + final AtomicReference> source; + + final AtomicReference[]> observers; + + T value; + + Throwable error; + + @SuppressWarnings("unchecked") + public MaybeCache(MaybeSource source) { + this.source = new AtomicReference>(source); + this.observers = new AtomicReference[]>(EMPTY); + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + CacheDisposable parent = new CacheDisposable(observer, this); + observer.onSubscribe(parent); + + if (add(parent)) { + if (parent.isDisposed()) { + remove(parent); + return; + } + } else { + if (!parent.isDisposed()) { + Throwable ex = error; + if (ex != null) { + observer.onError(ex); + } else { + T v = value; + if (v != null) { + observer.onSuccess(v); + } else { + observer.onComplete(); + } + } + } + return; + } + + MaybeSource src = source.getAndSet(null); + if (src != null) { + src.subscribe(this); + } + } + + @Override + public void onSubscribe(Disposable d) { + // deliberately ignored + } + + @SuppressWarnings("unchecked") + @Override + public void onSuccess(T value) { + this.value = value; + for (CacheDisposable inner : observers.getAndSet(TERMINATED)) { + if (!inner.isDisposed()) { + inner.downstream.onSuccess(value); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable e) { + this.error = e; + for (CacheDisposable inner : observers.getAndSet(TERMINATED)) { + if (!inner.isDisposed()) { + inner.downstream.onError(e); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + for (CacheDisposable inner : observers.getAndSet(TERMINATED)) { + if (!inner.isDisposed()) { + inner.downstream.onComplete(); + } + } + } + + boolean add(CacheDisposable inner) { + for (;;) { + CacheDisposable[] a = observers.get(); + if (a == TERMINATED) { + return false; + } + int n = a.length; + + @SuppressWarnings("unchecked") + CacheDisposable[] b = new CacheDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (observers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(CacheDisposable inner) { + for (;;) { + CacheDisposable[] a = observers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + CacheDisposable[] b; + if (n == 1) { + b = EMPTY; + } else { + b = new CacheDisposable[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (observers.compareAndSet(a, b)) { + return; + } + } + } + + static final class CacheDisposable + extends AtomicReference> + implements Disposable { + + private static final long serialVersionUID = -5791853038359966195L; + + final MaybeObserver downstream; + + CacheDisposable(MaybeObserver actual, MaybeCache parent) { + super(parent); + this.downstream = actual; + } + + @Override + public void dispose() { + MaybeCache mc = getAndSet(null); + if (mc != null) { + mc.remove(this); + } + } + + @Override + public boolean isDisposed() { + return get() == null; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java new file mode 100755 index 0000000..9dc56e1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCallbackObserver.java @@ -0,0 +1,104 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.MaybeObserver; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.LambdaConsumerIntrospection; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * MaybeObserver that delegates the onSuccess, onError and onComplete method calls to callbacks. + * + * @param the value type + */ +public final class MaybeCallbackObserver +extends AtomicReference +implements MaybeObserver, Disposable, LambdaConsumerIntrospection { + + private static final long serialVersionUID = -6076952298809384986L; + + final Consumer onSuccess; + + final Consumer onError; + + final Action onComplete; + + public MaybeCallbackObserver(Consumer onSuccess, Consumer onError, + Action onComplete) { + super(); + this.onSuccess = onSuccess; + this.onError = onError; + this.onComplete = onComplete; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + lazySet(DisposableHelper.DISPOSED); + try { + onSuccess.accept(value); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onError(Throwable e) { + lazySet(DisposableHelper.DISPOSED); + try { + onError.accept(e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(new CompositeException(e, ex)); + } + } + + @Override + public void onComplete() { + lazySet(DisposableHelper.DISPOSED); + try { + onComplete.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArray.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArray.java new file mode 100755 index 0000000..0584c47 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArray.java @@ -0,0 +1,163 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.SequentialDisposable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; + +/** + * Concatenate values of each MaybeSource provided in an array. + * + * @param the value type + */ +public final class MaybeConcatArray extends Flowable { + + final MaybeSource[] sources; + + public MaybeConcatArray(MaybeSource[] sources) { + this.sources = sources; + } + + @Override + protected void subscribeActual(Subscriber s) { + ConcatMaybeObserver parent = new ConcatMaybeObserver(s, sources); + s.onSubscribe(parent); + parent.drain(); + } + + static final class ConcatMaybeObserver + extends AtomicInteger + implements MaybeObserver, Subscription { + + private static final long serialVersionUID = 3520831347801429610L; + + final Subscriber downstream; + + final AtomicLong requested; + + final AtomicReference current; + + final SequentialDisposable disposables; + + final MaybeSource[] sources; + + int index; + + long produced; + + ConcatMaybeObserver(Subscriber actual, MaybeSource[] sources) { + this.downstream = actual; + this.sources = sources; + this.requested = new AtomicLong(); + this.disposables = new SequentialDisposable(); + this.current = new AtomicReference(NotificationLite.COMPLETE); // as if a previous completed + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + disposables.dispose(); + } + + @Override + public void onSubscribe(Disposable d) { + disposables.replace(d); + } + + @Override + public void onSuccess(T value) { + current.lazySet(value); + drain(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + current.lazySet(NotificationLite.COMPLETE); + drain(); + } + + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + + AtomicReference c = current; + Subscriber a = downstream; + Disposable cancelled = disposables; + + for (;;) { + if (cancelled.isDisposed()) { + c.lazySet(null); + return; + } + + Object o = c.get(); + + if (o != null) { + boolean goNextSource; + if (o != NotificationLite.COMPLETE) { + long p = produced; + if (p != requested.get()) { + produced = p + 1; + c.lazySet(null); + goNextSource = true; + + a.onNext((T)o); + } else { + goNextSource = false; + } + } else { + goNextSource = true; + c.lazySet(null); + } + + if (goNextSource && !cancelled.isDisposed()) { + int i = index; + if (i == sources.length) { + a.onComplete(); + return; + } + index = i + 1; + + sources[i].subscribe(this); + } + } + + if (decrementAndGet() == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayDelayError.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayDelayError.java new file mode 100755 index 0000000..6c2e160 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatArrayDelayError.java @@ -0,0 +1,178 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.SequentialDisposable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Concatenate values of each MaybeSource provided in an array and delays + * any errors till the very end. + * + * @param the value type + */ +public final class MaybeConcatArrayDelayError extends Flowable { + + final MaybeSource[] sources; + + public MaybeConcatArrayDelayError(MaybeSource[] sources) { + this.sources = sources; + } + + @Override + protected void subscribeActual(Subscriber s) { + ConcatMaybeObserver parent = new ConcatMaybeObserver(s, sources); + s.onSubscribe(parent); + parent.drain(); + } + + static final class ConcatMaybeObserver + extends AtomicInteger + implements MaybeObserver, Subscription { + + private static final long serialVersionUID = 3520831347801429610L; + + final Subscriber downstream; + + final AtomicLong requested; + + final AtomicReference current; + + final SequentialDisposable disposables; + + final MaybeSource[] sources; + + final AtomicThrowable errors; + + int index; + + long produced; + + ConcatMaybeObserver(Subscriber actual, MaybeSource[] sources) { + this.downstream = actual; + this.sources = sources; + this.requested = new AtomicLong(); + this.disposables = new SequentialDisposable(); + this.current = new AtomicReference(NotificationLite.COMPLETE); // as if a previous completed + this.errors = new AtomicThrowable(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + disposables.dispose(); + } + + @Override + public void onSubscribe(Disposable d) { + disposables.replace(d); + } + + @Override + public void onSuccess(T value) { + current.lazySet(value); + drain(); + } + + @Override + public void onError(Throwable e) { + current.lazySet(NotificationLite.COMPLETE); + if (errors.addThrowable(e)) { + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + current.lazySet(NotificationLite.COMPLETE); + drain(); + } + + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + + AtomicReference c = current; + Subscriber a = downstream; + Disposable cancelled = disposables; + + for (;;) { + if (cancelled.isDisposed()) { + c.lazySet(null); + return; + } + + Object o = c.get(); + + if (o != null) { + boolean goNextSource; + if (o != NotificationLite.COMPLETE) { + long p = produced; + if (p != requested.get()) { + produced = p + 1; + c.lazySet(null); + goNextSource = true; + + a.onNext((T)o); + } else { + goNextSource = false; + } + } else { + goNextSource = true; + c.lazySet(null); + } + + if (goNextSource && !cancelled.isDisposed()) { + int i = index; + if (i == sources.length) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + } else { + a.onComplete(); + } + return; + } + index = i + 1; + + sources[i].subscribe(this); + } + } + + if (decrementAndGet() == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatIterable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatIterable.java new file mode 100755 index 0000000..353afad --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeConcatIterable.java @@ -0,0 +1,192 @@ +/** + * 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.internal.operators.maybe; + +import java.util.Iterator; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.SequentialDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.*; + +/** + * Concatenate values of each MaybeSource provided by an Iterable. + * + * @param the value type + */ +public final class MaybeConcatIterable extends Flowable { + + final Iterable> sources; + + public MaybeConcatIterable(Iterable> sources) { + this.sources = sources; + } + + @Override + protected void subscribeActual(Subscriber s) { + + Iterator> it; + + try { + it = ObjectHelper.requireNonNull(sources.iterator(), "The sources Iterable returned a null Iterator"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptySubscription.error(ex, s); + return; + } + + ConcatMaybeObserver parent = new ConcatMaybeObserver(s, it); + s.onSubscribe(parent); + parent.drain(); + } + + static final class ConcatMaybeObserver + extends AtomicInteger + implements MaybeObserver, Subscription { + + private static final long serialVersionUID = 3520831347801429610L; + + final Subscriber downstream; + + final AtomicLong requested; + + final AtomicReference current; + + final SequentialDisposable disposables; + + final Iterator> sources; + + long produced; + + ConcatMaybeObserver(Subscriber actual, Iterator> sources) { + this.downstream = actual; + this.sources = sources; + this.requested = new AtomicLong(); + this.disposables = new SequentialDisposable(); + this.current = new AtomicReference(NotificationLite.COMPLETE); // as if a previous completed + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + disposables.dispose(); + } + + @Override + public void onSubscribe(Disposable d) { + disposables.replace(d); + } + + @Override + public void onSuccess(T value) { + current.lazySet(value); + drain(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + current.lazySet(NotificationLite.COMPLETE); + drain(); + } + + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + + AtomicReference c = current; + Subscriber a = downstream; + Disposable cancelled = disposables; + + for (;;) { + if (cancelled.isDisposed()) { + c.lazySet(null); + return; + } + + Object o = c.get(); + + if (o != null) { + boolean goNextSource; + if (o != NotificationLite.COMPLETE) { + long p = produced; + if (p != requested.get()) { + produced = p + 1; + c.lazySet(null); + goNextSource = true; + + a.onNext((T)o); + } else { + goNextSource = false; + } + } else { + goNextSource = true; + c.lazySet(null); + } + + if (goNextSource && !cancelled.isDisposed()) { + boolean b; + + try { + b = sources.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (b) { + MaybeSource source; + + try { + source = ObjectHelper.requireNonNull(sources.next(), "The source Iterator returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + source.subscribe(this); + } else { + a.onComplete(); + } + } + } + + if (decrementAndGet() == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeContains.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeContains.java new file mode 100755 index 0000000..aa167b2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeContains.java @@ -0,0 +1,99 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; + +/** + * Signals true if the source signals a value that is object-equals with the provided + * value, false otherwise or for empty sources. + * + * @param the value type + */ +public final class MaybeContains extends Single implements HasUpstreamMaybeSource { + + final MaybeSource source; + + final Object value; + + public MaybeContains(MaybeSource source, Object value) { + this.source = source; + this.value = value; + } + + @Override + public MaybeSource source() { + return source; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new ContainsMaybeObserver(observer, value)); + } + + static final class ContainsMaybeObserver implements MaybeObserver, Disposable { + + final SingleObserver downstream; + + final Object value; + + Disposable upstream; + + ContainsMaybeObserver(SingleObserver actual, Object value) { + this.downstream = actual; + this.value = value; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(Object value) { + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(ObjectHelper.equals(value, this.value)); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(false); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCount.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCount.java new file mode 100755 index 0000000..df36c7d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCount.java @@ -0,0 +1,91 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; + +/** + * Signals 1L if the source signalled an item or 0L if the source is empty. + * + * @param the source value type + */ +public final class MaybeCount extends Single implements HasUpstreamMaybeSource { + + final MaybeSource source; + + public MaybeCount(MaybeSource source) { + this.source = source; + } + + @Override + public MaybeSource source() { + return source; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new CountMaybeObserver(observer)); + } + + static final class CountMaybeObserver implements MaybeObserver, Disposable { + final SingleObserver downstream; + + Disposable upstream; + + CountMaybeObserver(SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(Object value) { + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(1L); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(0L); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java new file mode 100755 index 0000000..e328a2b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeCreate.java @@ -0,0 +1,153 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Cancellable; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Provides an API over MaybeObserver that serializes calls to onXXX and manages cancellation + * in a safe manner. + * + * @param the value type emitted + */ +public final class MaybeCreate extends Maybe { + + final MaybeOnSubscribe source; + + public MaybeCreate(MaybeOnSubscribe source) { + this.source = source; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + Emitter parent = new Emitter(observer); + observer.onSubscribe(parent); + + try { + source.subscribe(parent); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + parent.onError(ex); + } + } + + static final class Emitter + extends AtomicReference + implements MaybeEmitter, Disposable { + + final MaybeObserver downstream; + + Emitter(MaybeObserver downstream) { + this.downstream = downstream; + } + + private static final long serialVersionUID = -2467358622224974244L; + + @Override + public void onSuccess(T value) { + if (get() != DisposableHelper.DISPOSED) { + Disposable d = getAndSet(DisposableHelper.DISPOSED); + if (d != DisposableHelper.DISPOSED) { + try { + if (value == null) { + downstream.onError(new NullPointerException("onSuccess called with null. Null values are generally not allowed in 2.x operators and sources.")); + } else { + downstream.onSuccess(value); + } + } finally { + if (d != null) { + d.dispose(); + } + } + } + } + } + + @Override + public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + if (get() != DisposableHelper.DISPOSED) { + Disposable d = getAndSet(DisposableHelper.DISPOSED); + if (d != DisposableHelper.DISPOSED) { + try { + downstream.onError(t); + } finally { + if (d != null) { + d.dispose(); + } + } + return true; + } + } + return false; + } + + @Override + public void onComplete() { + if (get() != DisposableHelper.DISPOSED) { + Disposable d = getAndSet(DisposableHelper.DISPOSED); + if (d != DisposableHelper.DISPOSED) { + try { + downstream.onComplete(); + } finally { + if (d != null) { + d.dispose(); + } + } + } + } + } + + @Override + public void setDisposable(Disposable d) { + DisposableHelper.set(this, d); + } + + @Override + public void setCancellable(Cancellable c) { + setDisposable(new CancellableDisposable(c)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDefer.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDefer.java new file mode 100755 index 0000000..63c6f7c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDefer.java @@ -0,0 +1,50 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Defers the creation of the actual Maybe the incoming MaybeObserver is subscribed to. + * + * @param the value type + */ +public final class MaybeDefer extends Maybe { + + final Callable> maybeSupplier; + + public MaybeDefer(Callable> maybeSupplier) { + this.maybeSupplier = maybeSupplier; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + MaybeSource source; + + try { + source = ObjectHelper.requireNonNull(maybeSupplier.call(), "The maybeSupplier returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + source.subscribe(observer); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelay.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelay.java new file mode 100755 index 0000000..68a1508 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelay.java @@ -0,0 +1,126 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Delays all signal types by the given amount and re-emits them on the given scheduler. + * + * @param the value type + */ +public final class MaybeDelay extends AbstractMaybeWithUpstream { + + final long delay; + + final TimeUnit unit; + + final Scheduler scheduler; + + public MaybeDelay(MaybeSource source, long delay, TimeUnit unit, Scheduler scheduler) { + super(source); + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DelayMaybeObserver(observer, delay, unit, scheduler)); + } + + static final class DelayMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable, Runnable { + + private static final long serialVersionUID = 5566860102500855068L; + + final MaybeObserver downstream; + + final long delay; + + final TimeUnit unit; + + final Scheduler scheduler; + + T value; + + Throwable error; + + DelayMaybeObserver(MaybeObserver actual, long delay, TimeUnit unit, Scheduler scheduler) { + this.downstream = actual; + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void run() { + Throwable ex = error; + if (ex != null) { + downstream.onError(ex); + } else { + T v = value; + if (v != null) { + downstream.onSuccess(v); + } else { + downstream.onComplete(); + } + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + this.value = value; + schedule(); + } + + @Override + public void onError(Throwable e) { + this.error = e; + schedule(); + } + + @Override + public void onComplete() { + schedule(); + } + + void schedule() { + DisposableHelper.replace(this, scheduler.scheduleDirect(this, delay, unit)); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java new file mode 100755 index 0000000..d3a0f78 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayOtherPublisher.java @@ -0,0 +1,161 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Delay the emission of the main signal until the other signals an item or completes. + * + * @param the main value type + * @param the other value type + */ +public final class MaybeDelayOtherPublisher extends AbstractMaybeWithUpstream { + + final Publisher other; + + public MaybeDelayOtherPublisher(MaybeSource source, Publisher other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DelayMaybeObserver(observer, other)); + } + + static final class DelayMaybeObserver + implements MaybeObserver, Disposable { + final OtherSubscriber other; + + final Publisher otherSource; + + Disposable upstream; + + DelayMaybeObserver(MaybeObserver actual, Publisher otherSource) { + this.other = new OtherSubscriber(actual); + this.otherSource = otherSource; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + SubscriptionHelper.cancel(other); + } + + @Override + public boolean isDisposed() { + return other.get() == SubscriptionHelper.CANCELLED; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + other.downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + other.value = value; + subscribeNext(); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + other.error = e; + subscribeNext(); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + subscribeNext(); + } + + void subscribeNext() { + otherSource.subscribe(other); + } + } + + static final class OtherSubscriber extends + AtomicReference + implements FlowableSubscriber { + + private static final long serialVersionUID = -1215060610805418006L; + + final MaybeObserver downstream; + + T value; + + Throwable error; + + OtherSubscriber(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + Subscription s = get(); + if (s != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + s.cancel(); + onComplete(); + } + } + + @Override + public void onError(Throwable t) { + Throwable e = error; + if (e == null) { + downstream.onError(t); + } else { + downstream.onError(new CompositeException(e, t)); + } + } + + @Override + public void onComplete() { + Throwable e = error; + if (e != null) { + downstream.onError(e); + } else { + T v = value; + if (v != null) { + downstream.onSuccess(v); + } else { + downstream.onComplete(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionOtherPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionOtherPublisher.java new file mode 100755 index 0000000..c29fb3f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelaySubscriptionOtherPublisher.java @@ -0,0 +1,150 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Delay the subscription to the main Maybe until the other signals an item or completes. + * + * @param the main value type + * @param the other value type + */ +public final class MaybeDelaySubscriptionOtherPublisher extends AbstractMaybeWithUpstream { + + final Publisher other; + + public MaybeDelaySubscriptionOtherPublisher(MaybeSource source, Publisher other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + other.subscribe(new OtherSubscriber(observer, source)); + } + + static final class OtherSubscriber implements FlowableSubscriber, Disposable { + final DelayMaybeObserver main; + + MaybeSource source; + + Subscription upstream; + + OtherSubscriber(MaybeObserver actual, MaybeSource source) { + this.main = new DelayMaybeObserver(actual); + this.source = source; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + main.downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Object t) { + if (upstream != SubscriptionHelper.CANCELLED) { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + + subscribeNext(); + } + } + + @Override + public void onError(Throwable t) { + if (upstream != SubscriptionHelper.CANCELLED) { + upstream = SubscriptionHelper.CANCELLED; + + main.downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (upstream != SubscriptionHelper.CANCELLED) { + upstream = SubscriptionHelper.CANCELLED; + + subscribeNext(); + } + } + + void subscribeNext() { + MaybeSource src = source; + source = null; + + src.subscribe(main); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(main.get()); + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + DisposableHelper.dispose(main); + } + } + + static final class DelayMaybeObserver extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = 706635022205076709L; + + final MaybeObserver downstream; + + DelayMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayWithCompletable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayWithCompletable.java new file mode 100755 index 0000000..da9ff60 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDelayWithCompletable.java @@ -0,0 +1,115 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.CompletableObserver; +import io.reactivex.CompletableSource; +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.MaybeSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import java.util.concurrent.atomic.AtomicReference; + +public final class MaybeDelayWithCompletable extends Maybe { + + final MaybeSource source; + + final CompletableSource other; + + public MaybeDelayWithCompletable(MaybeSource source, CompletableSource other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + other.subscribe(new OtherObserver(observer, source)); + } + + static final class OtherObserver + extends AtomicReference + implements CompletableObserver, Disposable { + private static final long serialVersionUID = 703409937383992161L; + + final MaybeObserver downstream; + + final MaybeSource source; + + OtherObserver(MaybeObserver actual, MaybeSource source) { + this.downstream = actual; + this.source = source; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + + downstream.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + source.subscribe(new DelayWithMainObserver(this, downstream)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + + static final class DelayWithMainObserver implements MaybeObserver { + + final AtomicReference parent; + + final MaybeObserver downstream; + + DelayWithMainObserver(AtomicReference parent, MaybeObserver downstream) { + this.parent = parent; + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(parent, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDetach.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDetach.java new file mode 100755 index 0000000..c6da27b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDetach.java @@ -0,0 +1,97 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Breaks the references between the upstream and downstream when the Maybe terminates. + * + * @param the value type + */ +public final class MaybeDetach extends AbstractMaybeWithUpstream { + + public MaybeDetach(MaybeSource source) { + super(source); + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DetachMaybeObserver(observer)); + } + + static final class DetachMaybeObserver implements MaybeObserver, Disposable { + + MaybeObserver downstream; + + Disposable upstream; + + DetachMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + downstream = null; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + MaybeObserver a = downstream; + if (a != null) { + downstream = null; + a.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + MaybeObserver a = downstream; + if (a != null) { + downstream = null; + a.onError(e); + } + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + MaybeObserver a = downstream; + if (a != null) { + downstream = null; + a.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccess.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccess.java new file mode 100755 index 0000000..e877903 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoAfterSuccess.java @@ -0,0 +1,98 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Calls a consumer after pushing the current item to the downstream. + *

History: 2.0.1 - experimental + * @param the value type + * @since 2.1 + */ +public final class MaybeDoAfterSuccess extends AbstractMaybeWithUpstream { + + final Consumer onAfterSuccess; + + public MaybeDoAfterSuccess(MaybeSource source, Consumer onAfterSuccess) { + super(source); + this.onAfterSuccess = onAfterSuccess; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DoAfterObserver(observer, onAfterSuccess)); + } + + static final class DoAfterObserver implements MaybeObserver, Disposable { + + final MaybeObserver downstream; + + final Consumer onAfterSuccess; + + Disposable upstream; + + DoAfterObserver(MaybeObserver actual, Consumer onAfterSuccess) { + this.downstream = actual; + this.onAfterSuccess = onAfterSuccess; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + downstream.onSuccess(t); + + try { + onAfterSuccess.accept(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + // remember, onSuccess is a terminal event and we can't call onError + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoFinally.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoFinally.java new file mode 100755 index 0000000..7b55736 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoFinally.java @@ -0,0 +1,109 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Execute an action after an onSuccess, onError, onComplete or a dispose event. + *

History: 2.0.1 - experimental + * @param the value type + * @since 2.1 + */ +public final class MaybeDoFinally extends AbstractMaybeWithUpstream { + + final Action onFinally; + + public MaybeDoFinally(MaybeSource source, Action onFinally) { + super(source); + this.onFinally = onFinally; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DoFinallyObserver(observer, onFinally)); + } + + static final class DoFinallyObserver extends AtomicInteger implements MaybeObserver, Disposable { + + private static final long serialVersionUID = 4109457741734051389L; + + final MaybeObserver downstream; + + final Action onFinally; + + Disposable upstream; + + DoFinallyObserver(MaybeObserver actual, Action onFinally) { + this.downstream = actual; + this.onFinally = onFinally; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + downstream.onSuccess(t); + runFinally(); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + runFinally(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + runFinally(); + } + + @Override + public void dispose() { + upstream.dispose(); + runFinally(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + void runFinally() { + if (compareAndSet(0, 1)) { + try { + onFinally.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnEvent.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnEvent.java new file mode 100755 index 0000000..4769ee7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnEvent.java @@ -0,0 +1,118 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.BiConsumer; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Calls a BiConsumer with the success, error values of the upstream Maybe or with two nulls if + * the Maybe completed. + * + * @param the value type + */ +public final class MaybeDoOnEvent extends AbstractMaybeWithUpstream { + + final BiConsumer onEvent; + + public MaybeDoOnEvent(MaybeSource source, BiConsumer onEvent) { + super(source); + this.onEvent = onEvent; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DoOnEventMaybeObserver(observer, onEvent)); + } + + static final class DoOnEventMaybeObserver implements MaybeObserver, Disposable { + final MaybeObserver downstream; + + final BiConsumer onEvent; + + Disposable upstream; + + DoOnEventMaybeObserver(MaybeObserver actual, BiConsumer onEvent) { + this.downstream = actual; + this.onEvent = onEvent; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + + try { + onEvent.accept(value, null); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + + try { + onEvent.accept(null, e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + downstream.onError(e); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + + try { + onEvent.accept(null, null); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminate.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminate.java new file mode 100755 index 0000000..81d0d8a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminate.java @@ -0,0 +1,90 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.MaybeSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; + +public final class MaybeDoOnTerminate extends Maybe { + + final MaybeSource source; + + final Action onTerminate; + + public MaybeDoOnTerminate(MaybeSource source, Action onTerminate) { + this.source = source; + this.onTerminate = onTerminate; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DoOnTerminate(observer)); + } + + final class DoOnTerminate implements MaybeObserver { + final MaybeObserver downstream; + + DoOnTerminate(MaybeObserver observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + downstream.onError(e); + } + + @Override + public void onComplete() { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeEmpty.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeEmpty.java new file mode 100755 index 0000000..45514b4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeEmpty.java @@ -0,0 +1,36 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.fuseable.ScalarCallable; + +/** + * Signals an onComplete. + */ +public final class MaybeEmpty extends Maybe implements ScalarCallable { + + public static final MaybeEmpty INSTANCE = new MaybeEmpty(); + + @Override + protected void subscribeActual(MaybeObserver observer) { + EmptyDisposable.complete(observer); + } + + @Override + public Object call() { + return null; // nulls of ScalarCallable are considered empty sources + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java new file mode 100755 index 0000000..f5f83fb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeEqualSingle.java @@ -0,0 +1,166 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiPredicate; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Compares two MaybeSources to see if they are both empty or emit the same value compared + * via a BiPredicate. + * + * @param the common base type of the sources + */ +public final class MaybeEqualSingle extends Single { + final MaybeSource source1; + + final MaybeSource source2; + + final BiPredicate isEqual; + + public MaybeEqualSingle(MaybeSource source1, MaybeSource source2, + BiPredicate isEqual) { + this.source1 = source1; + this.source2 = source2; + this.isEqual = isEqual; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + EqualCoordinator parent = new EqualCoordinator(observer, isEqual); + observer.onSubscribe(parent); + parent.subscribe(source1, source2); + } + + @SuppressWarnings("serial") + static final class EqualCoordinator + extends AtomicInteger + implements Disposable { + final SingleObserver downstream; + + final EqualObserver observer1; + + final EqualObserver observer2; + + final BiPredicate isEqual; + + EqualCoordinator(SingleObserver actual, BiPredicate isEqual) { + super(2); + this.downstream = actual; + this.isEqual = isEqual; + this.observer1 = new EqualObserver(this); + this.observer2 = new EqualObserver(this); + } + + void subscribe(MaybeSource source1, MaybeSource source2) { + source1.subscribe(observer1); + source2.subscribe(observer2); + } + + @Override + public void dispose() { + observer1.dispose(); + observer2.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(observer1.get()); + } + + @SuppressWarnings("unchecked") + void done() { + if (decrementAndGet() == 0) { + Object o1 = observer1.value; + Object o2 = observer2.value; + + if (o1 != null && o2 != null) { + boolean b; + + try { + b = isEqual.test((T)o1, (T)o2); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(b); + } else { + downstream.onSuccess(o1 == null && o2 == null); + } + } + } + + void error(EqualObserver sender, Throwable ex) { + if (getAndSet(0) > 0) { + if (sender == observer1) { + observer2.dispose(); + } else { + observer1.dispose(); + } + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + } + } + + static final class EqualObserver + extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = -3031974433025990931L; + + final EqualCoordinator parent; + + Object value; + + EqualObserver(EqualCoordinator parent) { + this.parent = parent; + } + + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + this.value = value; + parent.done(); + } + + @Override + public void onError(Throwable e) { + parent.error(this, e); + } + + @Override + public void onComplete() { + parent.done(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeError.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeError.java new file mode 100755 index 0000000..6036992 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeError.java @@ -0,0 +1,37 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; + +/** + * Signals a constant Throwable. + * + * @param the value type + */ +public final class MaybeError extends Maybe { + + final Throwable error; + + public MaybeError(Throwable error) { + this.error = error; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + observer.onSubscribe(Disposables.disposed()); + observer.onError(error); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeErrorCallable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeErrorCallable.java new file mode 100755 index 0000000..2378d29 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeErrorCallable.java @@ -0,0 +1,50 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.Exceptions; + +/** + * Signals a Throwable returned by a Callable. + * + * @param the value type + */ +public final class MaybeErrorCallable extends Maybe { + + final Callable errorSupplier; + + public MaybeErrorCallable(Callable errorSupplier) { + this.errorSupplier = errorSupplier; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + observer.onSubscribe(Disposables.disposed()); + Throwable ex; + + try { + ex = ObjectHelper.requireNonNull(errorSupplier.call(), "Callable returned null throwable. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable ex1) { + Exceptions.throwIfFatal(ex1); + ex = ex1; + } + + observer.onError(ex); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java new file mode 100755 index 0000000..763ad25 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilter.java @@ -0,0 +1,105 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Filters the upstream via a predicate, returning the success item or completing if + * the predicate returns false. + * + * @param the upstream value type + */ +public final class MaybeFilter extends AbstractMaybeWithUpstream { + + final Predicate predicate; + + public MaybeFilter(MaybeSource source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new FilterMaybeObserver(observer, predicate)); + } + + static final class FilterMaybeObserver implements MaybeObserver, Disposable { + + final MaybeObserver downstream; + + final Predicate predicate; + + Disposable upstream; + + FilterMaybeObserver(MaybeObserver actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void dispose() { + Disposable d = this.upstream; + this.upstream = DisposableHelper.DISPOSED; + d.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + boolean b; + + try { + b = predicate.test(value); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + if (b) { + downstream.onSuccess(value); + } else { + downstream.onComplete(); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilterSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilterSingle.java new file mode 100755 index 0000000..820bc8b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFilterSingle.java @@ -0,0 +1,102 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Filters the upstream SingleSource via a predicate, returning the success item or completing if + * the predicate returns false. + * + * @param the upstream value type + */ +public final class MaybeFilterSingle extends Maybe { + final SingleSource source; + + final Predicate predicate; + + public MaybeFilterSingle(SingleSource source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new FilterMaybeObserver(observer, predicate)); + } + + static final class FilterMaybeObserver implements SingleObserver, Disposable { + + final MaybeObserver downstream; + + final Predicate predicate; + + Disposable upstream; + + FilterMaybeObserver(MaybeObserver actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void dispose() { + Disposable d = this.upstream; + this.upstream = DisposableHelper.DISPOSED; + d.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + boolean b; + + try { + b = predicate.test(value); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + if (b) { + downstream.onSuccess(value); + } else { + downstream.onComplete(); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelector.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelector.java new file mode 100755 index 0000000..2b9b8ec --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapBiSelector.java @@ -0,0 +1,163 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps a source item to another MaybeSource then calls a BiFunction with the + * original item and the secondary item to generate the final result. + * + * @param the main value type + * @param the second value type + * @param the result value type + */ +public final class MaybeFlatMapBiSelector extends AbstractMaybeWithUpstream { + + final Function> mapper; + + final BiFunction resultSelector; + + public MaybeFlatMapBiSelector(MaybeSource source, + Function> mapper, + BiFunction resultSelector) { + super(source); + this.mapper = mapper; + this.resultSelector = resultSelector; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new FlatMapBiMainObserver(observer, mapper, resultSelector)); + } + + static final class FlatMapBiMainObserver + implements MaybeObserver, Disposable { + + final Function> mapper; + + final InnerObserver inner; + + FlatMapBiMainObserver(MaybeObserver actual, + Function> mapper, + BiFunction resultSelector) { + this.inner = new InnerObserver(actual, resultSelector); + this.mapper = mapper; + } + + @Override + public void dispose() { + DisposableHelper.dispose(inner); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(inner.get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(inner, d)) { + inner.downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + MaybeSource next; + + try { + next = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + inner.downstream.onError(ex); + return; + } + + if (DisposableHelper.replace(inner, null)) { + inner.value = value; + next.subscribe(inner); + } + } + + @Override + public void onError(Throwable e) { + inner.downstream.onError(e); + } + + @Override + public void onComplete() { + inner.downstream.onComplete(); + } + + static final class InnerObserver + extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = -2897979525538174559L; + + final MaybeObserver downstream; + + final BiFunction resultSelector; + + T value; + + InnerObserver(MaybeObserver actual, + BiFunction resultSelector) { + this.downstream = actual; + this.resultSelector = resultSelector; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(U value) { + T t = this.value; + this.value = null; + + R r; + + try { + r = ObjectHelper.requireNonNull(resultSelector.apply(t, value), "The resultSelector returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(r); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapCompletable.java new file mode 100755 index 0000000..aa0a7f9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapCompletable.java @@ -0,0 +1,105 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of the source MaybeSource into a Completable. + * @param the value type of the source MaybeSource + */ +public final class MaybeFlatMapCompletable extends Completable { + + final MaybeSource source; + + final Function mapper; + + public MaybeFlatMapCompletable(MaybeSource source, Function mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + FlatMapCompletableObserver parent = new FlatMapCompletableObserver(observer, mapper); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class FlatMapCompletableObserver + extends AtomicReference + implements MaybeObserver, CompletableObserver, Disposable { + + private static final long serialVersionUID = -2177128922851101253L; + + final CompletableObserver downstream; + + final Function mapper; + + FlatMapCompletableObserver(CompletableObserver actual, + Function mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(T value) { + CompletableSource cs; + + try { + cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onError(ex); + return; + } + + if (!isDisposed()) { + cs.subscribe(this); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java new file mode 100755 index 0000000..f4d174d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableFlowable.java @@ -0,0 +1,296 @@ +/** + * 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.internal.operators.maybe; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicLong; + +import io.reactivex.annotations.Nullable; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; + +/** + * Maps a success value into an Iterable and streams it back as a Flowable. + * + * @param the source value type + * @param the element type of the Iterable + */ +public final class MaybeFlatMapIterableFlowable extends Flowable { + + final MaybeSource source; + + final Function> mapper; + + public MaybeFlatMapIterableFlowable(MaybeSource source, + Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new FlatMapIterableObserver(s, mapper)); + } + + static final class FlatMapIterableObserver + extends BasicIntQueueSubscription + implements MaybeObserver { + + private static final long serialVersionUID = -8938804753851907758L; + + final Subscriber downstream; + + final Function> mapper; + + final AtomicLong requested; + + Disposable upstream; + + volatile Iterator it; + + volatile boolean cancelled; + + boolean outputFused; + + FlatMapIterableObserver(Subscriber actual, + Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + Iterator iterator; + boolean has; + try { + iterator = mapper.apply(value).iterator(); + + has = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + if (!has) { + downstream.onComplete(); + return; + } + + this.it = iterator; + drain(); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + cancelled = true; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + void fastPath(Subscriber a, Iterator iterator) { + for (;;) { + if (cancelled) { + return; + } + + R v; + + try { + v = iterator.next(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + a.onNext(v); + + if (cancelled) { + return; + } + + boolean b; + + try { + b = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!b) { + a.onComplete(); + return; + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + Subscriber a = downstream; + Iterator iterator = this.it; + + if (outputFused && iterator != null) { + a.onNext(null); + a.onComplete(); + return; + } + + int missed = 1; + + for (;;) { + + if (iterator != null) { + long r = requested.get(); + + if (r == Long.MAX_VALUE) { + fastPath(a, iterator); + return; + } + + long e = 0L; + + while (e != r) { + if (cancelled) { + return; + } + + R v; + + try { + v = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + a.onNext(v); + + if (cancelled) { + return; + } + + e++; + + boolean b; + + try { + b = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!b) { + a.onComplete(); + return; + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + + if (iterator == null) { + iterator = it; + } + } + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Override + public void clear() { + it = null; + } + + @Override + public boolean isEmpty() { + return it == null; + } + + @Nullable + @Override + public R poll() throws Exception { + Iterator iterator = it; + + if (iterator != null) { + R v = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null value"); + if (!iterator.hasNext()) { + it = null; + } + return v; + } + return null; + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java new file mode 100755 index 0000000..5513eb5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapIterableObservable.java @@ -0,0 +1,206 @@ +/** + * 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.internal.operators.maybe; + +import java.util.Iterator; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.BasicQueueDisposable; + +/** + * Maps a success value into an Iterable and streams it back as a Flowable. + * + * @param the source value type + * @param the element type of the Iterable + */ +public final class MaybeFlatMapIterableObservable extends Observable { + + final MaybeSource source; + + final Function> mapper; + + public MaybeFlatMapIterableObservable(MaybeSource source, + Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new FlatMapIterableObserver(observer, mapper)); + } + + static final class FlatMapIterableObserver + extends BasicQueueDisposable + implements MaybeObserver { + + final Observer downstream; + + final Function> mapper; + + Disposable upstream; + + volatile Iterator it; + + volatile boolean cancelled; + + boolean outputFused; + + FlatMapIterableObserver(Observer actual, + Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + Observer a = downstream; + + Iterator iterator; + boolean has; + try { + iterator = mapper.apply(value).iterator(); + + has = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!has) { + a.onComplete(); + return; + } + + this.it = iterator; + + if (outputFused) { + a.onNext(null); + a.onComplete(); + return; + } + + for (;;) { + if (cancelled) { + return; + } + + R v; + + try { + v = iterator.next(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + a.onNext(v); + + if (cancelled) { + return; + } + + boolean b; + + try { + b = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!b) { + a.onComplete(); + return; + } + } + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Override + public void clear() { + it = null; + } + + @Override + public boolean isEmpty() { + return it == null; + } + + @Nullable + @Override + public R poll() throws Exception { + Iterator iterator = it; + + if (iterator != null) { + R v = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null value"); + if (!iterator.hasNext()) { + it = null; + } + return v; + } + return null; + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java new file mode 100755 index 0000000..81eb167 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapNotification.java @@ -0,0 +1,169 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps a value into a MaybeSource and relays its signal. + * + * @param the source value type + * @param the result value type + */ +public final class MaybeFlatMapNotification extends AbstractMaybeWithUpstream { + + final Function> onSuccessMapper; + + final Function> onErrorMapper; + + final Callable> onCompleteSupplier; + + public MaybeFlatMapNotification(MaybeSource source, + Function> onSuccessMapper, + Function> onErrorMapper, + Callable> onCompleteSupplier) { + super(source); + this.onSuccessMapper = onSuccessMapper; + this.onErrorMapper = onErrorMapper; + this.onCompleteSupplier = onCompleteSupplier; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new FlatMapMaybeObserver(observer, onSuccessMapper, onErrorMapper, onCompleteSupplier)); + } + + static final class FlatMapMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = 4375739915521278546L; + + final MaybeObserver downstream; + + final Function> onSuccessMapper; + + final Function> onErrorMapper; + + final Callable> onCompleteSupplier; + + Disposable upstream; + + FlatMapMaybeObserver(MaybeObserver actual, + Function> onSuccessMapper, + Function> onErrorMapper, + Callable> onCompleteSupplier) { + this.downstream = actual; + this.onSuccessMapper = onSuccessMapper; + this.onErrorMapper = onErrorMapper; + this.onCompleteSupplier = onCompleteSupplier; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + MaybeSource source; + + try { + source = ObjectHelper.requireNonNull(onSuccessMapper.apply(value), "The onSuccessMapper returned a null MaybeSource"); + } catch (Exception ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + source.subscribe(new InnerObserver()); + } + + @Override + public void onError(Throwable e) { + MaybeSource source; + + try { + source = ObjectHelper.requireNonNull(onErrorMapper.apply(e), "The onErrorMapper returned a null MaybeSource"); + } catch (Exception ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(new CompositeException(e, ex)); + return; + } + + source.subscribe(new InnerObserver()); + } + + @Override + public void onComplete() { + MaybeSource source; + + try { + source = ObjectHelper.requireNonNull(onCompleteSupplier.call(), "The onCompleteSupplier returned a null MaybeSource"); + } catch (Exception ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + source.subscribe(new InnerObserver()); + } + + final class InnerObserver implements MaybeObserver { + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(FlatMapMaybeObserver.this, d); + } + + @Override + public void onSuccess(R value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingle.java new file mode 100755 index 0000000..af55a7a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingle.java @@ -0,0 +1,136 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.MaybeObserver; +import io.reactivex.MaybeSource; +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Maps the success value of the source MaybeSource into a Single. + * @param the input value type + * @param the result value type + */ +public final class MaybeFlatMapSingle extends Single { + + final MaybeSource source; + + final Function> mapper; + + public MaybeFlatMapSingle(MaybeSource source, Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(SingleObserver downstream) { + source.subscribe(new FlatMapMaybeObserver(downstream, mapper)); + } + + static final class FlatMapMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = 4827726964688405508L; + + final SingleObserver downstream; + + final Function> mapper; + + FlatMapMaybeObserver(SingleObserver actual, Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + SingleSource ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onError(ex); + return; + } + + if (!isDisposed()) { + ss.subscribe(new FlatMapSingleObserver(this, downstream)); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onError(new NoSuchElementException()); + } + } + + static final class FlatMapSingleObserver implements SingleObserver { + + final AtomicReference parent; + + final SingleObserver downstream; + + FlatMapSingleObserver(AtomicReference parent, SingleObserver downstream) { + this.parent = parent; + this.downstream = downstream; + } + + @Override + public void onSubscribe(final Disposable d) { + DisposableHelper.replace(parent, d); + } + + @Override + public void onSuccess(final R value) { + downstream.onSuccess(value); + } + + @Override + public void onError(final Throwable e) { + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingleElement.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingleElement.java new file mode 100755 index 0000000..3779572 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatMapSingleElement.java @@ -0,0 +1,132 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of the source MaybeSource into a Single. + *

History: 2.0.2 - experimental + * @param the input value type + * @param the result value type + * @since 2.1 + */ +public final class MaybeFlatMapSingleElement extends Maybe { + + final MaybeSource source; + + final Function> mapper; + + public MaybeFlatMapSingleElement(MaybeSource source, Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(MaybeObserver downstream) { + source.subscribe(new FlatMapMaybeObserver(downstream, mapper)); + } + + static final class FlatMapMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = 4827726964688405508L; + + final MaybeObserver downstream; + + final Function> mapper; + + FlatMapMaybeObserver(MaybeObserver actual, Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + SingleSource ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onError(ex); + return; + } + + ss.subscribe(new FlatMapSingleObserver(this, downstream)); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } + + static final class FlatMapSingleObserver implements SingleObserver { + + final AtomicReference parent; + + final MaybeObserver downstream; + + FlatMapSingleObserver(AtomicReference parent, MaybeObserver downstream) { + this.parent = parent; + this.downstream = downstream; + } + + @Override + public void onSubscribe(final Disposable d) { + DisposableHelper.replace(parent, d); + } + + @Override + public void onSuccess(final R value) { + downstream.onSuccess(value); + } + + @Override + public void onError(final Throwable e) { + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java new file mode 100755 index 0000000..6463ef2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFlatten.java @@ -0,0 +1,133 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps a value into a MaybeSource and relays its signal. + * + * @param the source value type + * @param the result value type + */ +public final class MaybeFlatten extends AbstractMaybeWithUpstream { + + final Function> mapper; + + public MaybeFlatten(MaybeSource source, Function> mapper) { + super(source); + this.mapper = mapper; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new FlatMapMaybeObserver(observer, mapper)); + } + + static final class FlatMapMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = 4375739915521278546L; + + final MaybeObserver downstream; + + final Function> mapper; + + Disposable upstream; + + FlatMapMaybeObserver(MaybeObserver actual, + Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + MaybeSource source; + + try { + source = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null MaybeSource"); + } catch (Exception ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + if (!isDisposed()) { + source.subscribe(new InnerObserver()); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + final class InnerObserver implements MaybeObserver { + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(FlatMapMaybeObserver.this, d); + } + + @Override + public void onSuccess(R value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromAction.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromAction.java new file mode 100755 index 0000000..a4acb44 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromAction.java @@ -0,0 +1,67 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Executes an Action and signals its exception or completes normally. + * + * @param the value type + */ +public final class MaybeFromAction extends Maybe implements Callable { + + final Action action; + + public MaybeFromAction(Action action) { + this.action = action; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + Disposable d = Disposables.empty(); + observer.onSubscribe(d); + + if (!d.isDisposed()) { + + try { + action.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (!d.isDisposed()) { + observer.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + return; + } + + if (!d.isDisposed()) { + observer.onComplete(); + } + } + } + + @Override + public T call() throws Exception { + action.run(); + return null; // considered as onComplete() + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCallable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCallable.java new file mode 100755 index 0000000..6ac9333 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCallable.java @@ -0,0 +1,71 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Executes a callable and signals its value as success or signals an exception. + * + * @param the value type + */ +public final class MaybeFromCallable extends Maybe implements Callable { + + final Callable callable; + + public MaybeFromCallable(Callable callable) { + this.callable = callable; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + Disposable d = Disposables.empty(); + observer.onSubscribe(d); + + if (!d.isDisposed()) { + + T v; + + try { + v = callable.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (!d.isDisposed()) { + observer.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + return; + } + + if (!d.isDisposed()) { + if (v == null) { + observer.onComplete(); + } else { + observer.onSuccess(v); + } + } + } + } + + @Override + public T call() throws Exception { + return callable.call(); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCompletable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCompletable.java new file mode 100755 index 0000000..2753d99 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromCompletable.java @@ -0,0 +1,85 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamCompletableSource; + +/** + * Wrap a Single into a Maybe. + * + * @param the value type + */ +public final class MaybeFromCompletable extends Maybe implements HasUpstreamCompletableSource { + + final CompletableSource source; + + public MaybeFromCompletable(CompletableSource source) { + this.source = source; + } + + @Override + public CompletableSource source() { + return source; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new FromCompletableObserver(observer)); + } + + static final class FromCompletableObserver implements CompletableObserver, Disposable { + final MaybeObserver downstream; + + Disposable upstream; + + FromCompletableObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java new file mode 100755 index 0000000..7a20600 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromFuture.java @@ -0,0 +1,73 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; + +/** + * Waits until the source Future completes or the wait times out; treats a {@code null} + * result as indication to signal {@code onComplete} instead of {@code onSuccess}. + * + * @param the value type + */ +public final class MaybeFromFuture extends Maybe { + + final Future future; + + final long timeout; + + final TimeUnit unit; + + public MaybeFromFuture(Future future, long timeout, TimeUnit unit) { + this.future = future; + this.timeout = timeout; + this.unit = unit; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + Disposable d = Disposables.empty(); + observer.onSubscribe(d); + if (!d.isDisposed()) { + T v; + try { + if (timeout <= 0L) { + v = future.get(); + } else { + v = future.get(timeout, unit); + } + } catch (Throwable ex) { + if (ex instanceof ExecutionException) { + ex = ex.getCause(); + } + Exceptions.throwIfFatal(ex); + if (!d.isDisposed()) { + observer.onError(ex); + } + return; + } + if (!d.isDisposed()) { + if (v == null) { + observer.onComplete(); + } else { + observer.onSuccess(v); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromRunnable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromRunnable.java new file mode 100755 index 0000000..1514fab --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromRunnable.java @@ -0,0 +1,66 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Executes an Runnable and signals its exception or completes normally. + * + * @param the value type + */ +public final class MaybeFromRunnable extends Maybe implements Callable { + + final Runnable runnable; + + public MaybeFromRunnable(Runnable runnable) { + this.runnable = runnable; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + Disposable d = Disposables.empty(); + observer.onSubscribe(d); + + if (!d.isDisposed()) { + + try { + runnable.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (!d.isDisposed()) { + observer.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + return; + } + + if (!d.isDisposed()) { + observer.onComplete(); + } + } + } + + @Override + public T call() throws Exception { + runnable.run(); + return null; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromSingle.java new file mode 100755 index 0000000..b610a06 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeFromSingle.java @@ -0,0 +1,85 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamSingleSource; + +/** + * Wrap a Single into a Maybe. + * + * @param the value type + */ +public final class MaybeFromSingle extends Maybe implements HasUpstreamSingleSource { + + final SingleSource source; + + public MaybeFromSingle(SingleSource source) { + this.source = source; + } + + @Override + public SingleSource source() { + return source; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new FromSingleObserver(observer)); + } + + static final class FromSingleObserver implements SingleObserver, Disposable { + final MaybeObserver downstream; + + Disposable upstream; + + FromSingleObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeHide.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeHide.java new file mode 100755 index 0000000..805f7cd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeHide.java @@ -0,0 +1,81 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Hides the identity of the upstream Maybe and its Disposable sent through onSubscribe. + * + * @param the value type + */ +public final class MaybeHide extends AbstractMaybeWithUpstream { + + public MaybeHide(MaybeSource source) { + super(source); + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new HideMaybeObserver(observer)); + } + + static final class HideMaybeObserver implements MaybeObserver, Disposable { + + final MaybeObserver downstream; + + Disposable upstream; + + HideMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElement.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElement.java new file mode 100755 index 0000000..000de8c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElement.java @@ -0,0 +1,86 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Turns an onSuccess into an onComplete, onError and onComplete is relayed as is. + * + * @param the value type + */ +public final class MaybeIgnoreElement extends AbstractMaybeWithUpstream { + + public MaybeIgnoreElement(MaybeSource source) { + super(source); + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new IgnoreMaybeObserver(observer)); + } + + static final class IgnoreMaybeObserver implements MaybeObserver, Disposable { + + final MaybeObserver downstream; + + Disposable upstream; + + IgnoreMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElementCompletable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElementCompletable.java new file mode 100755 index 0000000..ac49d66 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIgnoreElementCompletable.java @@ -0,0 +1,95 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.FuseToMaybe; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Turns an onSuccess into an onComplete, onError and onComplete is relayed as is. + * + * @param the value type + */ +public final class MaybeIgnoreElementCompletable extends Completable implements FuseToMaybe { + + final MaybeSource source; + + public MaybeIgnoreElementCompletable(MaybeSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new IgnoreMaybeObserver(observer)); + } + + @Override + public Maybe fuseToMaybe() { + return RxJavaPlugins.onAssembly(new MaybeIgnoreElement(source)); + } + + static final class IgnoreMaybeObserver implements MaybeObserver, Disposable { + + final CompletableObserver downstream; + + Disposable upstream; + + IgnoreMaybeObserver(CompletableObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmpty.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmpty.java new file mode 100755 index 0000000..642dc64 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmpty.java @@ -0,0 +1,82 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Signals true if the source Maybe signals onComplete, signals false if the source Maybe + * signals onSuccess. + * + * @param the value type + */ +public final class MaybeIsEmpty extends AbstractMaybeWithUpstream { + + public MaybeIsEmpty(MaybeSource source) { + super(source); + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new IsEmptyMaybeObserver(observer)); + } + + static final class IsEmptyMaybeObserver + implements MaybeObserver, Disposable { + + final MaybeObserver downstream; + + Disposable upstream; + + IsEmptyMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(false); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onSuccess(true); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmptySingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmptySingle.java new file mode 100755 index 0000000..c8acdff --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeIsEmptySingle.java @@ -0,0 +1,101 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Signals true if the source Maybe signals onComplete, signals false if the source Maybe + * signals onSuccess. + * + * @param the value type + */ +public final class MaybeIsEmptySingle extends Single +implements HasUpstreamMaybeSource, FuseToMaybe { + + final MaybeSource source; + + public MaybeIsEmptySingle(MaybeSource source) { + this.source = source; + } + + @Override + public MaybeSource source() { + return source; + } + + @Override + public Maybe fuseToMaybe() { + return RxJavaPlugins.onAssembly(new MaybeIsEmpty(source)); + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new IsEmptyMaybeObserver(observer)); + } + + static final class IsEmptyMaybeObserver + implements MaybeObserver, Disposable { + + final SingleObserver downstream; + + Disposable upstream; + + IsEmptyMaybeObserver(SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(false); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(true); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeJust.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeJust.java new file mode 100755 index 0000000..4b2047b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeJust.java @@ -0,0 +1,43 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; +import io.reactivex.internal.fuseable.ScalarCallable; + +/** + * Signals a constant value. + * + * @param the value type + */ +public final class MaybeJust extends Maybe implements ScalarCallable { + + final T value; + + public MaybeJust(T value) { + this.value = value; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + observer.onSubscribe(Disposables.disposed()); + observer.onSuccess(value); + } + + @Override + public T call() { + return value; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeLift.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeLift.java new file mode 100755 index 0000000..6d33247 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeLift.java @@ -0,0 +1,50 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Calls a MaybeOperator for the incoming MaybeObserver. + * + * @param the upstream value type + * @param the downstream value type + */ +public final class MaybeLift extends AbstractMaybeWithUpstream { + + final MaybeOperator operator; + + public MaybeLift(MaybeSource source, MaybeOperator operator) { + super(source); + this.operator = operator; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + MaybeObserver lifted; + + try { + lifted = ObjectHelper.requireNonNull(operator.apply(observer), "The operator returned a null MaybeObserver"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + source.subscribe(lifted); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java new file mode 100755 index 0000000..7d7c7a4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMap.java @@ -0,0 +1,102 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the upstream success value into some other value. + * + * @param the upstream value type + * @param the downstream value type + */ +public final class MaybeMap extends AbstractMaybeWithUpstream { + + final Function mapper; + + public MaybeMap(MaybeSource source, Function mapper) { + super(source); + this.mapper = mapper; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new MapMaybeObserver(observer, mapper)); + } + + static final class MapMaybeObserver implements MaybeObserver, Disposable { + + final MaybeObserver downstream; + + final Function mapper; + + Disposable upstream; + + MapMaybeObserver(MaybeObserver actual, Function mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void dispose() { + Disposable d = this.upstream; + this.upstream = DisposableHelper.DISPOSED; + d.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + R v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null item"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(v); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMaterialize.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMaterialize.java new file mode 100755 index 0000000..2b74829 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMaterialize.java @@ -0,0 +1,40 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.internal.operators.mixed.MaterializeSingleObserver; + +/** + * Turn the signal types of a Maybe source into a single Notification of + * equal kind. + * + * @param the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class MaybeMaterialize extends Single> { + + final Maybe source; + + public MaybeMaterialize(Maybe source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver> observer) { + source.subscribe(new MaterializeSingleObserver(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java new file mode 100755 index 0000000..1edfdd6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeMergeArray.java @@ -0,0 +1,453 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.*; + +import io.reactivex.annotations.Nullable; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimpleQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Run all MaybeSources of an array at once and signal their values as they become available. + * + * @param the value type + */ +public final class MaybeMergeArray extends Flowable { + + final MaybeSource[] sources; + + public MaybeMergeArray(MaybeSource[] sources) { + this.sources = sources; + } + + @Override + protected void subscribeActual(Subscriber s) { + MaybeSource[] maybes = sources; + int n = maybes.length; + + SimpleQueueWithConsumerIndex queue; + + if (n <= bufferSize()) { + queue = new MpscFillOnceSimpleQueue(n); + } else { + queue = new ClqSimpleQueue(); + } + MergeMaybeObserver parent = new MergeMaybeObserver(s, n, queue); + + s.onSubscribe(parent); + + AtomicThrowable e = parent.error; + + for (MaybeSource source : maybes) { + if (parent.isCancelled() || e.get() != null) { + return; + } + + source.subscribe(parent); + } + } + + static final class MergeMaybeObserver + extends BasicIntQueueSubscription implements MaybeObserver { + + private static final long serialVersionUID = -660395290758764731L; + + final Subscriber downstream; + + final CompositeDisposable set; + + final AtomicLong requested; + + final SimpleQueueWithConsumerIndex queue; + + final AtomicThrowable error; + + final int sourceCount; + + volatile boolean cancelled; + + boolean outputFused; + + long consumed; + + MergeMaybeObserver(Subscriber actual, int sourceCount, SimpleQueueWithConsumerIndex queue) { + this.downstream = actual; + this.sourceCount = sourceCount; + this.set = new CompositeDisposable(); + this.requested = new AtomicLong(); + this.error = new AtomicThrowable(); + this.queue = queue; + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Nullable + @SuppressWarnings("unchecked") + @Override + public T poll() throws Exception { + for (;;) { + Object o = queue.poll(); + if (o != NotificationLite.COMPLETE) { + return (T)o; + } + } + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + set.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + } + + @Override + public void onSubscribe(Disposable d) { + set.add(d); + } + + @Override + public void onSuccess(T value) { + queue.offer(value); + drain(); + } + + @Override + public void onError(Throwable e) { + if (error.addThrowable(e)) { + set.dispose(); + queue.offer(NotificationLite.COMPLETE); + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + queue.offer(NotificationLite.COMPLETE); + drain(); + } + + boolean isCancelled() { + return cancelled; + } + + @SuppressWarnings("unchecked") + void drainNormal() { + int missed = 1; + Subscriber a = downstream; + SimpleQueueWithConsumerIndex q = queue; + long e = consumed; + + for (;;) { + + long r = requested.get(); + + while (e != r) { + if (cancelled) { + q.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + a.onError(error.terminate()); + return; + } + + if (q.consumerIndex() == sourceCount) { + a.onComplete(); + return; + } + + Object v = q.poll(); + + if (v == null) { + break; + } + + if (v != NotificationLite.COMPLETE) { + a.onNext((T)v); + + e++; + } + } + + if (e == r) { + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + a.onError(error.terminate()); + return; + } + + while (q.peek() == NotificationLite.COMPLETE) { + q.drop(); + } + + if (q.consumerIndex() == sourceCount) { + a.onComplete(); + return; + } + } + + consumed = e; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + + } + + void drainFused() { + int missed = 1; + Subscriber a = downstream; + SimpleQueueWithConsumerIndex q = queue; + + for (;;) { + if (cancelled) { + q.clear(); + return; + } + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + a.onError(ex); + return; + } + + boolean d = q.producerIndex() == sourceCount; + + if (!q.isEmpty()) { + a.onNext(null); + } + + if (d) { + a.onComplete(); + return; + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + if (outputFused) { + drainFused(); + } else { + drainNormal(); + } + } + } + + interface SimpleQueueWithConsumerIndex extends SimpleQueue { + + @Nullable + @Override + T poll(); + + T peek(); + + void drop(); + + int consumerIndex(); + + int producerIndex(); + } + + static final class MpscFillOnceSimpleQueue + extends AtomicReferenceArray + implements SimpleQueueWithConsumerIndex { + + private static final long serialVersionUID = -7969063454040569579L; + final AtomicInteger producerIndex; + + int consumerIndex; + + MpscFillOnceSimpleQueue(int length) { + super(length); + this.producerIndex = new AtomicInteger(); + } + + @Override + public boolean offer(T value) { + ObjectHelper.requireNonNull(value, "value is null"); + int idx = producerIndex.getAndIncrement(); + if (idx < length()) { + lazySet(idx, value); + return true; + } + return false; + } + + @Override + public boolean offer(T v1, T v2) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public T poll() { + int ci = consumerIndex; + if (ci == length()) { + return null; + } + AtomicInteger pi = producerIndex; + for (;;) { + T v = get(ci); + if (v != null) { + consumerIndex = ci + 1; + lazySet(ci, null); + return v; + } + if (pi.get() == ci) { + return null; + } + } + } + + @Override + public T peek() { + int ci = consumerIndex; + if (ci == length()) { + return null; + } + return get(ci); + } + + @Override + public void drop() { + int ci = consumerIndex; + lazySet(ci, null); + consumerIndex = ci + 1; + } + + @Override + public boolean isEmpty() { + return consumerIndex == producerIndex(); + } + + @Override + public void clear() { + while (poll() != null && !isEmpty()) { } + } + + @Override + public int consumerIndex() { + return consumerIndex; + } + + @Override + public int producerIndex() { + return producerIndex.get(); + } + } + + static final class ClqSimpleQueue extends ConcurrentLinkedQueue implements SimpleQueueWithConsumerIndex { + + private static final long serialVersionUID = -4025173261791142821L; + + int consumerIndex; + + final AtomicInteger producerIndex; + + ClqSimpleQueue() { + this.producerIndex = new AtomicInteger(); + } + + @Override + public boolean offer(T v1, T v2) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean offer(T e) { + producerIndex.getAndIncrement(); + return super.offer(e); + } + + @Nullable + @Override + public T poll() { + T v = super.poll(); + if (v != null) { + consumerIndex++; + } + return v; + } + + @Override + public int consumerIndex() { + return consumerIndex; + } + + @Override + public int producerIndex() { + return producerIndex.get(); + } + + @Override + public void drop() { + poll(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeNever.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeNever.java new file mode 100755 index 0000000..834dcca --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeNever.java @@ -0,0 +1,30 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.internal.disposables.EmptyDisposable; + +/** + * Doesn't signal any event other than onSubscribe. + */ +public final class MaybeNever extends Maybe { + + public static final MaybeNever INSTANCE = new MaybeNever(); + + @Override + protected void subscribeActual(MaybeObserver observer) { + observer.onSubscribe(EmptyDisposable.NEVER); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java new file mode 100755 index 0000000..cce201f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeObserveOn.java @@ -0,0 +1,110 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Signals the onSuccess, onError or onComplete events on a the specific scheduler. + * + * @param the value type delivered + */ +public final class MaybeObserveOn extends AbstractMaybeWithUpstream { + + final Scheduler scheduler; + + public MaybeObserveOn(MaybeSource source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new ObserveOnMaybeObserver(observer, scheduler)); + } + + static final class ObserveOnMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable, Runnable { + + private static final long serialVersionUID = 8571289934935992137L; + + final MaybeObserver downstream; + + final Scheduler scheduler; + + T value; + Throwable error; + + ObserveOnMaybeObserver(MaybeObserver actual, Scheduler scheduler) { + this.downstream = actual; + this.scheduler = scheduler; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + this.value = value; + DisposableHelper.replace(this, scheduler.scheduleDirect(this)); + } + + @Override + public void onError(Throwable e) { + this.error = e; + DisposableHelper.replace(this, scheduler.scheduleDirect(this)); + } + + @Override + public void onComplete() { + DisposableHelper.replace(this, scheduler.scheduleDirect(this)); + } + + @Override + public void run() { + Throwable ex = error; + if (ex != null) { + error = null; + downstream.onError(ex); + } else { + T v = value; + if (v != null) { + value = null; + downstream.onSuccess(v); + } else { + downstream.onComplete(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorComplete.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorComplete.java new file mode 100755 index 0000000..10fe061 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorComplete.java @@ -0,0 +1,104 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Emits an onComplete if the source emits an onError and the predicate returns true for + * that Throwable. + * + * @param the value type + */ +public final class MaybeOnErrorComplete extends AbstractMaybeWithUpstream { + + final Predicate predicate; + + public MaybeOnErrorComplete(MaybeSource source, + Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new OnErrorCompleteMaybeObserver(observer, predicate)); + } + + static final class OnErrorCompleteMaybeObserver implements MaybeObserver, Disposable { + + final MaybeObserver downstream; + + final Predicate predicate; + + Disposable upstream; + + OnErrorCompleteMaybeObserver(MaybeObserver actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + boolean b; + + try { + b = predicate.test(e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(new CompositeException(e, ex)); + return; + } + + if (b) { + downstream.onComplete(); + } else { + downstream.onError(e); + } + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java new file mode 100755 index 0000000..f7fff88 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorNext.java @@ -0,0 +1,148 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Subscribes to the MaybeSource returned by a function if the main source signals an onError. + * + * @param the value type + */ +public final class MaybeOnErrorNext extends AbstractMaybeWithUpstream { + + final Function> resumeFunction; + + final boolean allowFatal; + + public MaybeOnErrorNext(MaybeSource source, + Function> resumeFunction, + boolean allowFatal) { + super(source); + this.resumeFunction = resumeFunction; + this.allowFatal = allowFatal; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new OnErrorNextMaybeObserver(observer, resumeFunction, allowFatal)); + } + + static final class OnErrorNextMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = 2026620218879969836L; + + final MaybeObserver downstream; + + final Function> resumeFunction; + + final boolean allowFatal; + + OnErrorNextMaybeObserver(MaybeObserver actual, + Function> resumeFunction, + boolean allowFatal) { + this.downstream = actual; + this.resumeFunction = resumeFunction; + this.allowFatal = allowFatal; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + if (!allowFatal && !(e instanceof Exception)) { + downstream.onError(e); + return; + } + MaybeSource m; + + try { + m = ObjectHelper.requireNonNull(resumeFunction.apply(e), "The resumeFunction returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(new CompositeException(e, ex)); + return; + } + + DisposableHelper.replace(this, null); + + m.subscribe(new NextMaybeObserver(downstream, this)); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + static final class NextMaybeObserver implements MaybeObserver { + final MaybeObserver downstream; + + final AtomicReference upstream; + + NextMaybeObserver(MaybeObserver actual, AtomicReference d) { + this.downstream = actual; + this.upstream = d; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorReturn.java new file mode 100755 index 0000000..568f150 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeOnErrorReturn.java @@ -0,0 +1,100 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Returns a value generated via a function if the main source signals an onError. + * @param the value type + */ +public final class MaybeOnErrorReturn extends AbstractMaybeWithUpstream { + + final Function valueSupplier; + + public MaybeOnErrorReturn(MaybeSource source, + Function valueSupplier) { + super(source); + this.valueSupplier = valueSupplier; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new OnErrorReturnMaybeObserver(observer, valueSupplier)); + } + + static final class OnErrorReturnMaybeObserver implements MaybeObserver, Disposable { + + final MaybeObserver downstream; + + final Function valueSupplier; + + Disposable upstream; + + OnErrorReturnMaybeObserver(MaybeObserver actual, + Function valueSupplier) { + this.downstream = actual; + this.valueSupplier = valueSupplier; + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + T v; + + try { + v = ObjectHelper.requireNonNull(valueSupplier.apply(e), "The valueSupplier returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(new CompositeException(e, ex)); + return; + } + + downstream.onSuccess(v); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybePeek.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybePeek.java new file mode 100755 index 0000000..1d6179a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybePeek.java @@ -0,0 +1,181 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Peeks into the lifecycle of a Maybe and MaybeObserver. + * + * @param the value type + */ +public final class MaybePeek extends AbstractMaybeWithUpstream { + + final Consumer onSubscribeCall; + + final Consumer onSuccessCall; + + final Consumer onErrorCall; + + final Action onCompleteCall; + + final Action onAfterTerminate; + + final Action onDisposeCall; + + public MaybePeek(MaybeSource source, Consumer onSubscribeCall, + Consumer onSuccessCall, Consumer onErrorCall, Action onCompleteCall, + Action onAfterTerminate, Action onDispose) { + super(source); + this.onSubscribeCall = onSubscribeCall; + this.onSuccessCall = onSuccessCall; + this.onErrorCall = onErrorCall; + this.onCompleteCall = onCompleteCall; + this.onAfterTerminate = onAfterTerminate; + this.onDisposeCall = onDispose; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new MaybePeekObserver(observer, this)); + } + + static final class MaybePeekObserver implements MaybeObserver, Disposable { + final MaybeObserver downstream; + + final MaybePeek parent; + + Disposable upstream; + + MaybePeekObserver(MaybeObserver actual, MaybePeek parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void dispose() { + try { + parent.onDisposeCall.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + try { + parent.onSubscribeCall.accept(d); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + d.dispose(); + this.upstream = DisposableHelper.DISPOSED; + EmptyDisposable.error(ex, downstream); + return; + } + + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + if (this.upstream == DisposableHelper.DISPOSED) { + return; + } + try { + parent.onSuccessCall.accept(value); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onErrorInner(ex); + return; + } + this.upstream = DisposableHelper.DISPOSED; + + downstream.onSuccess(value); + + onAfterTerminate(); + } + + @Override + public void onError(Throwable e) { + if (this.upstream == DisposableHelper.DISPOSED) { + RxJavaPlugins.onError(e); + return; + } + + onErrorInner(e); + } + + void onErrorInner(Throwable e) { + try { + parent.onErrorCall.accept(e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + this.upstream = DisposableHelper.DISPOSED; + + downstream.onError(e); + + onAfterTerminate(); + } + + @Override + public void onComplete() { + if (this.upstream == DisposableHelper.DISPOSED) { + return; + } + + try { + parent.onCompleteCall.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onErrorInner(ex); + return; + } + this.upstream = DisposableHelper.DISPOSED; + + downstream.onComplete(); + + onAfterTerminate(); + } + + void onAfterTerminate() { + try { + parent.onAfterTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSubscribeOn.java new file mode 100755 index 0000000..da2ba08 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSubscribeOn.java @@ -0,0 +1,104 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +/** + * Subscribes to the upstream MaybeSource on the specified scheduler. + * + * @param the value type delivered + */ +public final class MaybeSubscribeOn extends AbstractMaybeWithUpstream { + + final Scheduler scheduler; + + public MaybeSubscribeOn(MaybeSource source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + SubscribeOnMaybeObserver parent = new SubscribeOnMaybeObserver(observer); + observer.onSubscribe(parent); + + parent.task.replace(scheduler.scheduleDirect(new SubscribeTask(parent, source))); + } + + static final class SubscribeTask implements Runnable { + final MaybeObserver observer; + final MaybeSource source; + + SubscribeTask(MaybeObserver observer, MaybeSource source) { + this.observer = observer; + this.source = source; + } + + @Override + public void run() { + source.subscribe(observer); + } + } + + static final class SubscribeOnMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + final SequentialDisposable task; + + private static final long serialVersionUID = 8571289934935992137L; + + final MaybeObserver downstream; + + SubscribeOnMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + this.task = new SequentialDisposable(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + task.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java new file mode 100755 index 0000000..68d3db3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmpty.java @@ -0,0 +1,125 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Subscribes to the other source if the main source is empty. + * + * @param the value type + */ +public final class MaybeSwitchIfEmpty extends AbstractMaybeWithUpstream { + + final MaybeSource other; + + public MaybeSwitchIfEmpty(MaybeSource source, MaybeSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new SwitchIfEmptyMaybeObserver(observer, other)); + } + + static final class SwitchIfEmptyMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = -2223459372976438024L; + + final MaybeObserver downstream; + + final MaybeSource other; + + SwitchIfEmptyMaybeObserver(MaybeObserver actual, MaybeSource other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + Disposable d = get(); + if (d != DisposableHelper.DISPOSED) { + if (compareAndSet(d, null)) { + other.subscribe(new OtherMaybeObserver(downstream, this)); + } + } + } + + static final class OtherMaybeObserver implements MaybeObserver { + + final MaybeObserver downstream; + + final AtomicReference parent; + OtherMaybeObserver(MaybeObserver actual, AtomicReference parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(parent, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java new file mode 100755 index 0000000..bd94beb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeSwitchIfEmptySingle.java @@ -0,0 +1,127 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Subscribes to the other source if the main source is empty. + * + * @param the value type + */ +public final class MaybeSwitchIfEmptySingle extends Single implements HasUpstreamMaybeSource { + + final MaybeSource source; + final SingleSource other; + + public MaybeSwitchIfEmptySingle(MaybeSource source, SingleSource other) { + this.source = source; + this.other = other; + } + + @Override + public MaybeSource source() { + return source; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new SwitchIfEmptyMaybeObserver(observer, other)); + } + + static final class SwitchIfEmptyMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = 4603919676453758899L; + + final SingleObserver downstream; + + final SingleSource other; + + SwitchIfEmptyMaybeObserver(SingleObserver actual, SingleSource other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + Disposable d = get(); + if (d != DisposableHelper.DISPOSED) { + if (compareAndSet(d, null)) { + other.subscribe(new OtherSingleObserver(downstream, this)); + } + } + } + + static final class OtherSingleObserver implements SingleObserver { + + final SingleObserver downstream; + + final AtomicReference parent; + OtherSingleObserver(SingleObserver actual, AtomicReference parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(parent, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } + + } +} \ No newline at end of file diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilMaybe.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilMaybe.java new file mode 100755 index 0000000..56a5cac --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilMaybe.java @@ -0,0 +1,152 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Relays the main source's event unless the other Maybe signals an item first or just completes + * at which point the resulting Maybe is completed. + * + * @param the value type + * @param the other's value type + */ +public final class MaybeTakeUntilMaybe extends AbstractMaybeWithUpstream { + + final MaybeSource other; + + public MaybeTakeUntilMaybe(MaybeSource source, MaybeSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + TakeUntilMainMaybeObserver parent = new TakeUntilMainMaybeObserver(observer); + observer.onSubscribe(parent); + + other.subscribe(parent.other); + + source.subscribe(parent); + } + + static final class TakeUntilMainMaybeObserver + extends AtomicReference implements MaybeObserver, Disposable { + + private static final long serialVersionUID = -2187421758664251153L; + + final MaybeObserver downstream; + + final TakeUntilOtherMaybeObserver other; + + TakeUntilMainMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + this.other = new TakeUntilOtherMaybeObserver(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + DisposableHelper.dispose(other); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + DisposableHelper.dispose(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + DisposableHelper.dispose(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + DisposableHelper.dispose(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onComplete(); + } + } + + void otherError(Throwable e) { + if (DisposableHelper.dispose(this)) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + void otherComplete() { + if (DisposableHelper.dispose(this)) { + downstream.onComplete(); + } + } + + static final class TakeUntilOtherMaybeObserver + extends AtomicReference implements MaybeObserver { + + private static final long serialVersionUID = -1266041316834525931L; + + final TakeUntilMainMaybeObserver parent; + + TakeUntilOtherMaybeObserver(TakeUntilMainMaybeObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(Object value) { + parent.otherComplete(); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java new file mode 100755 index 0000000..656cf47 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTakeUntilPublisher.java @@ -0,0 +1,156 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Relays the main source's event unless the other Publisher signals an item first or just completes + * at which point the resulting Maybe is completed. + * + * @param the value type + * @param the other's value type + */ +public final class MaybeTakeUntilPublisher extends AbstractMaybeWithUpstream { + + final Publisher other; + + public MaybeTakeUntilPublisher(MaybeSource source, Publisher other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + TakeUntilMainMaybeObserver parent = new TakeUntilMainMaybeObserver(observer); + observer.onSubscribe(parent); + + other.subscribe(parent.other); + + source.subscribe(parent); + } + + static final class TakeUntilMainMaybeObserver + extends AtomicReference implements MaybeObserver, Disposable { + + private static final long serialVersionUID = -2187421758664251153L; + + final MaybeObserver downstream; + + final TakeUntilOtherMaybeObserver other; + + TakeUntilMainMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + this.other = new TakeUntilOtherMaybeObserver(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + SubscriptionHelper.cancel(other); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + SubscriptionHelper.cancel(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + SubscriptionHelper.cancel(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + SubscriptionHelper.cancel(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onComplete(); + } + } + + void otherError(Throwable e) { + if (DisposableHelper.dispose(this)) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + void otherComplete() { + if (DisposableHelper.dispose(this)) { + downstream.onComplete(); + } + } + + static final class TakeUntilOtherMaybeObserver + extends AtomicReference implements FlowableSubscriber { + + private static final long serialVersionUID = -1266041316834525931L; + + final TakeUntilMainMaybeObserver parent; + + TakeUntilOtherMaybeObserver(TakeUntilMainMaybeObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object value) { + SubscriptionHelper.cancel(this); + parent.otherComplete(); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java new file mode 100755 index 0000000..83d22cf --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutMaybe.java @@ -0,0 +1,203 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Switches to the fallback Maybe if the other MaybeSource signals a success or completes, or + * signals TimeoutException if fallback is null. + * + * @param the main value type + * @param the other value type + */ +public final class MaybeTimeoutMaybe extends AbstractMaybeWithUpstream { + + final MaybeSource other; + + final MaybeSource fallback; + + public MaybeTimeoutMaybe(MaybeSource source, MaybeSource other, MaybeSource fallback) { + super(source); + this.other = other; + this.fallback = fallback; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + TimeoutMainMaybeObserver parent = new TimeoutMainMaybeObserver(observer, fallback); + observer.onSubscribe(parent); + + other.subscribe(parent.other); + + source.subscribe(parent); + } + + static final class TimeoutMainMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = -5955289211445418871L; + + final MaybeObserver downstream; + + final TimeoutOtherMaybeObserver other; + + final MaybeSource fallback; + + final TimeoutFallbackMaybeObserver otherObserver; + + TimeoutMainMaybeObserver(MaybeObserver actual, MaybeSource fallback) { + this.downstream = actual; + this.other = new TimeoutOtherMaybeObserver(this); + this.fallback = fallback; + this.otherObserver = fallback != null ? new TimeoutFallbackMaybeObserver(actual) : null; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + DisposableHelper.dispose(other); + TimeoutFallbackMaybeObserver oo = otherObserver; + if (oo != null) { + DisposableHelper.dispose(oo); + } + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + DisposableHelper.dispose(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + DisposableHelper.dispose(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + DisposableHelper.dispose(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onComplete(); + } + } + + public void otherError(Throwable e) { + if (DisposableHelper.dispose(this)) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + public void otherComplete() { + if (DisposableHelper.dispose(this)) { + if (fallback == null) { + downstream.onError(new TimeoutException()); + } else { + fallback.subscribe(otherObserver); + } + } + } + } + + static final class TimeoutOtherMaybeObserver + extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = 8663801314800248617L; + + final TimeoutMainMaybeObserver parent; + + TimeoutOtherMaybeObserver(TimeoutMainMaybeObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(Object value) { + parent.otherComplete(); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + static final class TimeoutFallbackMaybeObserver + extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = 8663801314800248617L; + + final MaybeObserver downstream; + + TimeoutFallbackMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java new file mode 100755 index 0000000..f0d690d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimeoutPublisher.java @@ -0,0 +1,208 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Switches to the fallback Maybe if the other Publisher signals a success or completes, or + * signals TimeoutException if fallback is null. + * + * @param the main value type + * @param the other value type + */ +public final class MaybeTimeoutPublisher extends AbstractMaybeWithUpstream { + + final Publisher other; + + final MaybeSource fallback; + + public MaybeTimeoutPublisher(MaybeSource source, Publisher other, MaybeSource fallback) { + super(source); + this.other = other; + this.fallback = fallback; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + TimeoutMainMaybeObserver parent = new TimeoutMainMaybeObserver(observer, fallback); + observer.onSubscribe(parent); + + other.subscribe(parent.other); + + source.subscribe(parent); + } + + static final class TimeoutMainMaybeObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = -5955289211445418871L; + + final MaybeObserver downstream; + + final TimeoutOtherMaybeObserver other; + + final MaybeSource fallback; + + final TimeoutFallbackMaybeObserver otherObserver; + + TimeoutMainMaybeObserver(MaybeObserver actual, MaybeSource fallback) { + this.downstream = actual; + this.other = new TimeoutOtherMaybeObserver(this); + this.fallback = fallback; + this.otherObserver = fallback != null ? new TimeoutFallbackMaybeObserver(actual) : null; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + SubscriptionHelper.cancel(other); + TimeoutFallbackMaybeObserver oo = otherObserver; + if (oo != null) { + DisposableHelper.dispose(oo); + } + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + SubscriptionHelper.cancel(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + SubscriptionHelper.cancel(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + SubscriptionHelper.cancel(other); + if (getAndSet(DisposableHelper.DISPOSED) != DisposableHelper.DISPOSED) { + downstream.onComplete(); + } + } + + public void otherError(Throwable e) { + if (DisposableHelper.dispose(this)) { + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + public void otherComplete() { + if (DisposableHelper.dispose(this)) { + if (fallback == null) { + downstream.onError(new TimeoutException()); + } else { + fallback.subscribe(otherObserver); + } + } + } + } + + static final class TimeoutOtherMaybeObserver + extends AtomicReference + implements FlowableSubscriber { + + private static final long serialVersionUID = 8663801314800248617L; + + final TimeoutMainMaybeObserver parent; + + TimeoutOtherMaybeObserver(TimeoutMainMaybeObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object value) { + get().cancel(); + parent.otherComplete(); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + + static final class TimeoutFallbackMaybeObserver + extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = 8663801314800248617L; + + final MaybeObserver downstream; + + TimeoutFallbackMaybeObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimer.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimer.java new file mode 100755 index 0000000..5d2c1c4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeTimer.java @@ -0,0 +1,75 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Signals a {@code 0L} after the specified delay. + */ +public final class MaybeTimer extends Maybe { + + final long delay; + + final TimeUnit unit; + + final Scheduler scheduler; + + public MaybeTimer(long delay, TimeUnit unit, Scheduler scheduler) { + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(final MaybeObserver observer) { + TimerDisposable parent = new TimerDisposable(observer); + observer.onSubscribe(parent); + parent.setFuture(scheduler.scheduleDirect(parent, delay, unit)); + } + + static final class TimerDisposable extends AtomicReference implements Disposable, Runnable { + + private static final long serialVersionUID = 2875964065294031672L; + final MaybeObserver downstream; + + TimerDisposable(final MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void run() { + downstream.onSuccess(0L); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + void setFuture(Disposable d) { + DisposableHelper.replace(this, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToFlowable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToFlowable.java new file mode 100755 index 0000000..c477a86 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToFlowable.java @@ -0,0 +1,89 @@ +/** + * 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.internal.operators.maybe; + +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; +import io.reactivex.internal.subscriptions.DeferredScalarSubscription; + +/** + * Wraps a MaybeSource and exposes it as a Flowable, relaying signals in a backpressure-aware manner + * and composes cancellation through. + * + * @param the value type + */ +public final class MaybeToFlowable extends Flowable implements HasUpstreamMaybeSource { + + final MaybeSource source; + + public MaybeToFlowable(MaybeSource source) { + this.source = source; + } + + @Override + public MaybeSource source() { + return source; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new MaybeToFlowableSubscriber(s)); + } + + static final class MaybeToFlowableSubscriber extends DeferredScalarSubscription + implements MaybeObserver { + + private static final long serialVersionUID = 7603343402964826922L; + + Disposable upstream; + + MaybeToFlowableSubscriber(Subscriber downstream) { + super(downstream); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + complete(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void cancel() { + super.cancel(); + upstream.dispose(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java new file mode 100755 index 0000000..8caa294 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java @@ -0,0 +1,99 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; +import io.reactivex.internal.observers.DeferredScalarDisposable; + +/** + * Wraps a MaybeSource and exposes it as an Observable, relaying signals in a backpressure-aware manner + * and composes cancellation through. + * + * @param the value type + */ +public final class MaybeToObservable extends Observable implements HasUpstreamMaybeSource { + + final MaybeSource source; + + public MaybeToObservable(MaybeSource source) { + this.source = source; + } + + @Override + public MaybeSource source() { + return source; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(create(observer)); + } + + /** + * Creates a {@link MaybeObserver} wrapper around a {@link Observer}. + *

History: 2.1.11 - experimental + * @param the value type + * @param downstream the downstream {@code Observer} to talk to + * @return the new MaybeObserver instance + * @since 2.2 + */ + public static MaybeObserver create(Observer downstream) { + return new MaybeToObservableObserver(downstream); + } + + static final class MaybeToObservableObserver extends DeferredScalarDisposable + implements MaybeObserver { + + private static final long serialVersionUID = 7603343402964826922L; + + Disposable upstream; + + MaybeToObservableObserver(Observer downstream) { + super(downstream); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + complete(value); + } + + @Override + public void onError(Throwable e) { + error(e); + } + + @Override + public void onComplete() { + complete(); + } + + @Override + public void dispose() { + super.dispose(); + upstream.dispose(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToPublisher.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToPublisher.java new file mode 100755 index 0000000..b530522 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToPublisher.java @@ -0,0 +1,36 @@ +/** + * 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.internal.operators.maybe; + +import org.reactivestreams.Publisher; + +import io.reactivex.MaybeSource; +import io.reactivex.functions.Function; + +/** + * Helper function to merge/concat values of each MaybeSource provided by a Publisher. + */ +public enum MaybeToPublisher implements Function, Publisher> { + INSTANCE; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Function, Publisher> instance() { + return (Function)INSTANCE; + } + + @Override + public Publisher apply(MaybeSource t) throws Exception { + return new MaybeToFlowable(t); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToSingle.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToSingle.java new file mode 100755 index 0000000..146bbb2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToSingle.java @@ -0,0 +1,102 @@ +/** + * 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.internal.operators.maybe; + +import java.util.NoSuchElementException; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; + +/** + * Wraps a MaybeSource and exposes its onSuccess and onError signals and signals + * NoSuchElementException for onComplete. + * + * @param the value type + */ +public final class MaybeToSingle extends Single implements HasUpstreamMaybeSource { + + final MaybeSource source; + final T defaultValue; + + public MaybeToSingle(MaybeSource source, T defaultValue) { + this.source = source; + this.defaultValue = defaultValue; + } + + @Override + public MaybeSource source() { + return source; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new ToSingleMaybeSubscriber(observer, defaultValue)); + } + + static final class ToSingleMaybeSubscriber implements MaybeObserver, Disposable { + final SingleObserver downstream; + final T defaultValue; + + Disposable upstream; + + ToSingleMaybeSubscriber(SingleObserver actual, T defaultValue) { + this.downstream = actual; + this.defaultValue = defaultValue; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + if (defaultValue != null) { + downstream.onSuccess(defaultValue); + } else { + downstream.onError(new NoSuchElementException("The MaybeSource is empty")); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsafeCreate.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsafeCreate.java new file mode 100755 index 0000000..c607811 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsafeCreate.java @@ -0,0 +1,34 @@ +/** + * 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.internal.operators.maybe; + +import io.reactivex.*; + +/** + * Wraps a MaybeSource without safeguard and calls its subscribe() method for each MaybeObserver. + * + * @param the value type + */ +public final class MaybeUnsafeCreate extends AbstractMaybeWithUpstream { + + public MaybeUnsafeCreate(MaybeSource source) { + super(source); + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(observer); + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOn.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOn.java new file mode 100755 index 0000000..a6afe4c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUnsubscribeOn.java @@ -0,0 +1,98 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Makes sure a dispose() call from downstream happens on the specified scheduler. + * + * @param the value type + */ +public final class MaybeUnsubscribeOn extends AbstractMaybeWithUpstream { + + final Scheduler scheduler; + + public MaybeUnsubscribeOn(MaybeSource source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new UnsubscribeOnMaybeObserver(observer, scheduler)); + } + + static final class UnsubscribeOnMaybeObserver extends AtomicReference + implements MaybeObserver, Disposable, Runnable { + + private static final long serialVersionUID = 3256698449646456986L; + + final MaybeObserver downstream; + + final Scheduler scheduler; + + Disposable ds; + + UnsubscribeOnMaybeObserver(MaybeObserver actual, Scheduler scheduler) { + this.downstream = actual; + this.scheduler = scheduler; + } + + @Override + public void dispose() { + Disposable d = getAndSet(DisposableHelper.DISPOSED); + if (d != DisposableHelper.DISPOSED) { + this.ds = d; + scheduler.scheduleDirect(this); + } + } + + @Override + public void run() { + ds.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java new file mode 100755 index 0000000..4628d12 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeUsing.java @@ -0,0 +1,230 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Creates a resource and a dependent Maybe for each incoming Observer and optionally + * disposes the resource eagerly (before the terminal event is send out). + * + * @param the value type + * @param the resource type + */ +public final class MaybeUsing extends Maybe { + + final Callable resourceSupplier; + + final Function> sourceSupplier; + + final Consumer resourceDisposer; + + final boolean eager; + + public MaybeUsing(Callable resourceSupplier, + Function> sourceSupplier, + Consumer resourceDisposer, + boolean eager) { + this.resourceSupplier = resourceSupplier; + this.sourceSupplier = sourceSupplier; + this.resourceDisposer = resourceDisposer; + this.eager = eager; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + D resource; + + try { + resource = resourceSupplier.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + MaybeSource source; + + try { + source = ObjectHelper.requireNonNull(sourceSupplier.apply(resource), "The sourceSupplier returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (eager) { + try { + resourceDisposer.accept(resource); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + EmptyDisposable.error(new CompositeException(ex, exc), observer); + return; + } + } + + EmptyDisposable.error(ex, observer); + + if (!eager) { + try { + resourceDisposer.accept(resource); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + RxJavaPlugins.onError(exc); + } + } + return; + } + + source.subscribe(new UsingObserver(observer, resource, resourceDisposer, eager)); + } + + static final class UsingObserver + extends AtomicReference + implements MaybeObserver, Disposable { + + private static final long serialVersionUID = -674404550052917487L; + + final MaybeObserver downstream; + + final Consumer disposer; + + final boolean eager; + + Disposable upstream; + + UsingObserver(MaybeObserver actual, D resource, Consumer disposer, boolean eager) { + super(resource); + this.downstream = actual; + this.disposer = disposer; + this.eager = eager; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + disposeResourceAfter(); + } + + @SuppressWarnings("unchecked") + void disposeResourceAfter() { + Object resource = getAndSet(this); + if (resource != this) { + try { + disposer.accept((D)resource); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + if (eager) { + Object resource = getAndSet(this); + if (resource != this) { + try { + disposer.accept((D)resource); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + } else { + return; + } + } + + downstream.onSuccess(value); + + if (!eager) { + disposeResourceAfter(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + if (eager) { + Object resource = getAndSet(this); + if (resource != this) { + try { + disposer.accept((D)resource); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + } else { + return; + } + } + + downstream.onError(e); + + if (!eager) { + disposeResourceAfter(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + if (eager) { + Object resource = getAndSet(this); + if (resource != this) { + try { + disposer.accept((D)resource); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + } else { + return; + } + } + + downstream.onComplete(); + + if (!eager) { + disposeResourceAfter(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java new file mode 100755 index 0000000..d9cc002 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipArray.java @@ -0,0 +1,196 @@ +/** + * 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.internal.operators.maybe; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class MaybeZipArray extends Maybe { + + final MaybeSource[] sources; + + final Function zipper; + + public MaybeZipArray(MaybeSource[] sources, Function zipper) { + this.sources = sources; + this.zipper = zipper; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + MaybeSource[] sources = this.sources; + int n = sources.length; + + if (n == 1) { + sources[0].subscribe(new MaybeMap.MapMaybeObserver(observer, new SingletonArrayFunc())); + return; + } + + ZipCoordinator parent = new ZipCoordinator(observer, n, zipper); + + observer.onSubscribe(parent); + + for (int i = 0; i < n; i++) { + if (parent.isDisposed()) { + return; + } + + MaybeSource source = sources[i]; + + if (source == null) { + parent.innerError(new NullPointerException("One of the sources is null"), i); + return; + } + source.subscribe(parent.observers[i]); + } + } + + static final class ZipCoordinator extends AtomicInteger implements Disposable { + + private static final long serialVersionUID = -5556924161382950569L; + + final MaybeObserver downstream; + + final Function zipper; + + final ZipMaybeObserver[] observers; + + final Object[] values; + + @SuppressWarnings("unchecked") + ZipCoordinator(MaybeObserver observer, int n, Function zipper) { + super(n); + this.downstream = observer; + this.zipper = zipper; + ZipMaybeObserver[] o = new ZipMaybeObserver[n]; + for (int i = 0; i < n; i++) { + o[i] = new ZipMaybeObserver(this, i); + } + this.observers = o; + this.values = new Object[n]; + } + + @Override + public boolean isDisposed() { + return get() <= 0; + } + + @Override + public void dispose() { + if (getAndSet(0) > 0) { + for (ZipMaybeObserver d : observers) { + d.dispose(); + } + } + } + + void innerSuccess(T value, int index) { + values[index] = value; + if (decrementAndGet() == 0) { + R v; + + try { + v = ObjectHelper.requireNonNull(zipper.apply(values), "The zipper returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(v); + } + } + + void disposeExcept(int index) { + ZipMaybeObserver[] observers = this.observers; + int n = observers.length; + for (int i = 0; i < index; i++) { + observers[i].dispose(); + } + for (int i = index + 1; i < n; i++) { + observers[i].dispose(); + } + } + + void innerError(Throwable ex, int index) { + if (getAndSet(0) > 0) { + disposeExcept(index); + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + } + + void innerComplete(int index) { + if (getAndSet(0) > 0) { + disposeExcept(index); + downstream.onComplete(); + } + } + } + + static final class ZipMaybeObserver + extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = 3323743579927613702L; + + final ZipCoordinator parent; + + final int index; + + ZipMaybeObserver(ZipCoordinator parent, int index) { + this.parent = parent; + this.index = index; + } + + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + parent.innerSuccess(value, index); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e, index); + } + + @Override + public void onComplete() { + parent.innerComplete(index); + } + } + + final class SingletonArrayFunc implements Function { + @Override + public R apply(T t) throws Exception { + return ObjectHelper.requireNonNull(zipper.apply(new Object[] { t }), "The zipper returned a null value"); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipIterable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipIterable.java new file mode 100755 index 0000000..7815bab --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeZipIterable.java @@ -0,0 +1,88 @@ +/** + * 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.internal.operators.maybe; + +import java.util.Arrays; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.maybe.MaybeZipArray.ZipCoordinator; + +public final class MaybeZipIterable extends Maybe { + + final Iterable> sources; + + final Function zipper; + + public MaybeZipIterable(Iterable> sources, Function zipper) { + this.sources = sources; + this.zipper = zipper; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + @SuppressWarnings("unchecked") + MaybeSource[] a = new MaybeSource[8]; + int n = 0; + + try { + for (MaybeSource source : sources) { + if (source == null) { + EmptyDisposable.error(new NullPointerException("One of the sources is null"), observer); + return; + } + if (n == a.length) { + a = Arrays.copyOf(a, n + (n >> 2)); + } + a[n++] = source; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + if (n == 0) { + EmptyDisposable.complete(observer); + return; + } + + if (n == 1) { + a[0].subscribe(new MaybeMap.MapMaybeObserver(observer, new SingletonArrayFunc())); + return; + } + + ZipCoordinator parent = new ZipCoordinator(observer, n, zipper); + + observer.onSubscribe(parent); + + for (int i = 0; i < n; i++) { + if (parent.isDisposed()) { + return; + } + + a[i].subscribe(parent.observers[i]); + } + } + + final class SingletonArrayFunc implements Function { + @Override + public R apply(T t) throws Exception { + return ObjectHelper.requireNonNull(zipper.apply(new Object[] { t }), "The zipper returned a null value"); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java new file mode 100755 index 0000000..454c45b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenObservable.java @@ -0,0 +1,100 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * After Completable completes, it relays the signals + * of the ObservableSource to the downstream observer. + * + * @param the result type of the ObservableSource and this operator + * @since 2.1.15 + */ +public final class CompletableAndThenObservable extends Observable { + + final CompletableSource source; + + final ObservableSource other; + + public CompletableAndThenObservable(CompletableSource source, + ObservableSource other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(Observer observer) { + AndThenObservableObserver parent = new AndThenObservableObserver(observer, other); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class AndThenObservableObserver + extends AtomicReference + implements Observer, CompletableObserver, Disposable { + + private static final long serialVersionUID = -8948264376121066672L; + + final Observer downstream; + + ObservableSource other; + + AndThenObservableObserver(Observer downstream, ObservableSource other) { + this.other = other; + this.downstream = downstream; + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + ObservableSource o = other; + if (o == null) { + downstream.onComplete(); + } else { + other = null; + o.subscribe(this); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenPublisher.java b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenPublisher.java new file mode 100755 index 0000000..70c3974 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/CompletableAndThenPublisher.java @@ -0,0 +1,114 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * After Completable completes, it relays the signals + * of the Publisher to the downstream subscriber. + * + * @param the result type of the Publisher and this operator + * @since 2.1.15 + */ +public final class CompletableAndThenPublisher extends Flowable { + + final CompletableSource source; + + final Publisher other; + + public CompletableAndThenPublisher(CompletableSource source, + Publisher other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new AndThenPublisherSubscriber(s, other)); + } + + static final class AndThenPublisherSubscriber + extends AtomicReference + implements FlowableSubscriber, CompletableObserver, Subscription { + + private static final long serialVersionUID = -8948264376121066672L; + + final Subscriber downstream; + + Publisher other; + + Disposable upstream; + + final AtomicLong requested; + + AndThenPublisherSubscriber(Subscriber downstream, Publisher other) { + this.downstream = downstream; + this.other = other; + this.requested = new AtomicLong(); + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + Publisher p = other; + if (p == null) { + downstream.onComplete(); + } else { + other = null; + p.subscribe(this); + } + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(this, requested, n); + } + + @Override + public void cancel() { + upstream.dispose(); + SubscriptionHelper.cancel(this); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(this, requested, s); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapCompletable.java new file mode 100755 index 0000000..249d01e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapCompletable.java @@ -0,0 +1,290 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other completes or terminates (in error-delaying mode). + *

History: 2.1.11 - experimental + * @param the upstream value type + * @since 2.2 + */ +public final class FlowableConcatMapCompletable extends Completable { + + final Flowable source; + + final Function mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public FlowableConcatMapCompletable(Flowable source, + Function mapper, + ErrorMode errorMode, + int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new ConcatMapCompletableObserver(observer, mapper, errorMode, prefetch)); + } + + static final class ConcatMapCompletableObserver + extends AtomicInteger + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = 3610901111000061034L; + + final CompletableObserver downstream; + + final Function mapper; + + final ErrorMode errorMode; + + final AtomicThrowable errors; + + final ConcatMapInnerObserver inner; + + final int prefetch; + + final SimplePlainQueue queue; + + Subscription upstream; + + volatile boolean active; + + volatile boolean done; + + volatile boolean disposed; + + int consumed; + + ConcatMapCompletableObserver(CompletableObserver downstream, + Function mapper, + ErrorMode errorMode, int prefetch) { + this.downstream = downstream; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapInnerObserver(this); + this.queue = new SpscArrayQueue(prefetch); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (queue.offer(t)) { + drain(); + } else { + upstream.cancel(); + onError(new MissingBackpressureException("Queue full?!")); + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + t = errors.terminate(); + if (t != ExceptionHelper.TERMINATED) { + downstream.onError(t); + } + if (getAndIncrement() == 0) { + queue.clear(); + } + } else { + done = true; + drain(); + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + disposed = true; + upstream.cancel(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode == ErrorMode.IMMEDIATE) { + upstream.cancel(); + ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + if (getAndIncrement() == 0) { + queue.clear(); + } + } else { + active = false; + drain(); + } + } else { + RxJavaPlugins.onError(ex); + } + } + + void innerComplete() { + active = false; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + do { + if (disposed) { + queue.clear(); + return; + } + + if (!active) { + + if (errorMode == ErrorMode.BOUNDARY) { + if (errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (!empty) { + + int limit = prefetch - (prefetch >> 1); + int c = consumed + 1; + if (c == limit) { + consumed = 0; + upstream.request(limit); + } else { + consumed = c; + } + + CompletableSource cs; + + try { + cs = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + queue.clear(); + upstream.cancel(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + active = true; + cs.subscribe(inner); + } + } + } while (decrementAndGet() != 0); + } + + static final class ConcatMapInnerObserver extends AtomicReference + implements CompletableObserver { + + private static final long serialVersionUID = 5638352172918776687L; + + final ConcatMapCompletableObserver parent; + + ConcatMapInnerObserver(ConcatMapCompletableObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapMaybe.java new file mode 100755 index 0000000..82a3009 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapMaybe.java @@ -0,0 +1,340 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each upstream item into a {@link MaybeSource}, subscribes to them one after the other terminates + * and relays their success values, optionally delaying any errors till the main and inner sources + * terminate. + *

History: 2.1.11 - experimental + * @param the upstream element type + * @param the output element type + * @since 2.2 + */ +public final class FlowableConcatMapMaybe extends Flowable { + + final Flowable source; + + final Function> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public FlowableConcatMapMaybe(Flowable source, + Function> mapper, + ErrorMode errorMode, int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ConcatMapMaybeSubscriber(s, mapper, prefetch, errorMode)); + } + + static final class ConcatMapMaybeSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -9140123220065488293L; + + final Subscriber downstream; + + final Function> mapper; + + final int prefetch; + + final AtomicLong requested; + + final AtomicThrowable errors; + + final ConcatMapMaybeObserver inner; + + final SimplePlainQueue queue; + + final ErrorMode errorMode; + + Subscription upstream; + + volatile boolean done; + + volatile boolean cancelled; + + long emitted; + + int consumed; + + R item; + + volatile int state; + + /** No inner MaybeSource is running. */ + static final int STATE_INACTIVE = 0; + /** An inner MaybeSource is running but there are no results yet. */ + static final int STATE_ACTIVE = 1; + /** The inner MaybeSource succeeded with a value in {@link #item}. */ + static final int STATE_RESULT_VALUE = 2; + + ConcatMapMaybeSubscriber(Subscriber downstream, + Function> mapper, + int prefetch, ErrorMode errorMode) { + this.downstream = downstream; + this.mapper = mapper; + this.prefetch = prefetch; + this.errorMode = errorMode; + this.requested = new AtomicLong(); + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapMaybeObserver(this); + this.queue = new SpscArrayQueue(prefetch); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (!queue.offer(t)) { + upstream.cancel(); + onError(new MissingBackpressureException("queue full?!")); + return; + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + item = null; + } + } + + void innerSuccess(R item) { + this.item = item; + this.state = STATE_RESULT_VALUE; + drain(); + } + + void innerComplete() { + this.state = STATE_INACTIVE; + drain(); + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode != ErrorMode.END) { + upstream.cancel(); + } + this.state = STATE_INACTIVE; + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber downstream = this.downstream; + ErrorMode errorMode = this.errorMode; + SimplePlainQueue queue = this.queue; + AtomicThrowable errors = this.errors; + AtomicLong requested = this.requested; + int limit = prefetch - (prefetch >> 1); + + for (;;) { + + for (;;) { + if (cancelled) { + queue.clear(); + item = null; + break; + } + + int s = state; + + if (errors.get() != null) { + if (errorMode == ErrorMode.IMMEDIATE + || (errorMode == ErrorMode.BOUNDARY && s == STATE_INACTIVE)) { + queue.clear(); + item = null; + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + if (s == STATE_INACTIVE) { + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + upstream.request(limit); + } else { + consumed = c; + } + + MaybeSource ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + queue.clear(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + state = STATE_ACTIVE; + ms.subscribe(inner); + break; + } else if (s == STATE_RESULT_VALUE) { + long e = emitted; + if (e != requested.get()) { + R w = item; + item = null; + + downstream.onNext(w); + + emitted = e + 1; + state = STATE_INACTIVE; + } else { + break; + } + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConcatMapMaybeObserver + extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = -3051469169682093892L; + + final ConcatMapMaybeSubscriber parent; + + ConcatMapMaybeObserver(ConcatMapMaybeSubscriber parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(R t) { + parent.innerSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapSingle.java new file mode 100755 index 0000000..9be42fc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableConcatMapSingle.java @@ -0,0 +1,330 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each upstream item into a {@link SingleSource}, subscribes to them one after the other terminates + * and relays their success values, optionally delaying any errors till the main and inner sources + * terminate. + *

History: 2.1.11 - experimental + * @param the upstream element type + * @param the output element type + * @since 2.2 + */ +public final class FlowableConcatMapSingle extends Flowable { + + final Flowable source; + + final Function> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public FlowableConcatMapSingle(Flowable source, + Function> mapper, + ErrorMode errorMode, int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new ConcatMapSingleSubscriber(s, mapper, prefetch, errorMode)); + } + + static final class ConcatMapSingleSubscriber + extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -9140123220065488293L; + + final Subscriber downstream; + + final Function> mapper; + + final int prefetch; + + final AtomicLong requested; + + final AtomicThrowable errors; + + final ConcatMapSingleObserver inner; + + final SimplePlainQueue queue; + + final ErrorMode errorMode; + + Subscription upstream; + + volatile boolean done; + + volatile boolean cancelled; + + long emitted; + + int consumed; + + R item; + + volatile int state; + + /** No inner SingleSource is running. */ + static final int STATE_INACTIVE = 0; + /** An inner SingleSource is running but there are no results yet. */ + static final int STATE_ACTIVE = 1; + /** The inner SingleSource succeeded with a value in {@link #item}. */ + static final int STATE_RESULT_VALUE = 2; + + ConcatMapSingleSubscriber(Subscriber downstream, + Function> mapper, + int prefetch, ErrorMode errorMode) { + this.downstream = downstream; + this.mapper = mapper; + this.prefetch = prefetch; + this.errorMode = errorMode; + this.requested = new AtomicLong(); + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapSingleObserver(this); + this.queue = new SpscArrayQueue(prefetch); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(prefetch); + } + } + + @Override + public void onNext(T t) { + if (!queue.offer(t)) { + upstream.cancel(); + onError(new MissingBackpressureException("queue full?!")); + return; + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + item = null; + } + } + + void innerSuccess(R item) { + this.item = item; + this.state = STATE_RESULT_VALUE; + drain(); + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode != ErrorMode.END) { + upstream.cancel(); + } + this.state = STATE_INACTIVE; + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber downstream = this.downstream; + ErrorMode errorMode = this.errorMode; + SimplePlainQueue queue = this.queue; + AtomicThrowable errors = this.errors; + AtomicLong requested = this.requested; + int limit = prefetch - (prefetch >> 1); + + for (;;) { + + for (;;) { + if (cancelled) { + queue.clear(); + item = null; + break; + } + + int s = state; + + if (errors.get() != null) { + if (errorMode == ErrorMode.IMMEDIATE + || (errorMode == ErrorMode.BOUNDARY && s == STATE_INACTIVE)) { + queue.clear(); + item = null; + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + if (s == STATE_INACTIVE) { + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + upstream.request(limit); + } else { + consumed = c; + } + + SingleSource ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + queue.clear(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + state = STATE_ACTIVE; + ss.subscribe(inner); + break; + } else if (s == STATE_RESULT_VALUE) { + long e = emitted; + if (e != requested.get()) { + R w = item; + item = null; + + downstream.onNext(w); + + emitted = e + 1; + state = STATE_INACTIVE; + } else { + break; + } + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConcatMapSingleObserver + extends AtomicReference + implements SingleObserver { + + private static final long serialVersionUID = -3051469169682093892L; + + final ConcatMapSingleSubscriber parent; + + ConcatMapSingleObserver(ConcatMapSingleSubscriber parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(R t) { + parent.innerSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapCompletable.java new file mode 100755 index 0000000..70294ff --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapCompletable.java @@ -0,0 +1,237 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Subscription; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running. + *

History: 2.1.11 - experimental + * @param the upstream value type + * @since 2.2 + */ +public final class FlowableSwitchMapCompletable extends Completable { + + final Flowable source; + + final Function mapper; + + final boolean delayErrors; + + public FlowableSwitchMapCompletable(Flowable source, + Function mapper, boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new SwitchMapCompletableObserver(observer, mapper, delayErrors)); + } + + static final class SwitchMapCompletableObserver implements FlowableSubscriber, Disposable { + + final CompletableObserver downstream; + + final Function mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicReference inner; + + static final SwitchMapInnerObserver INNER_DISPOSED = new SwitchMapInnerObserver(null); + + volatile boolean done; + + Subscription upstream; + + SwitchMapCompletableObserver(CompletableObserver downstream, + Function mapper, boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.inner = new AtomicReference(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + CompletableSource c; + + try { + c = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + onError(ex); + return; + } + + SwitchMapInnerObserver o = new SwitchMapInnerObserver(this); + + for (;;) { + SwitchMapInnerObserver current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, o)) { + if (current != null) { + current.dispose(); + } + c.subscribe(o); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (delayErrors) { + onComplete(); + } else { + disposeInner(); + Throwable ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + if (inner.get() == null) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + } + } + + void disposeInner() { + SwitchMapInnerObserver o = inner.getAndSet(INNER_DISPOSED); + if (o != null && o != INNER_DISPOSED) { + o.dispose(); + } + } + + @Override + public void dispose() { + upstream.cancel(); + disposeInner(); + } + + @Override + public boolean isDisposed() { + return inner.get() == INNER_DISPOSED; + } + + void innerError(SwitchMapInnerObserver sender, Throwable error) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(error)) { + if (delayErrors) { + if (done) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } else { + dispose(); + Throwable ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + } + return; + } + } + RxJavaPlugins.onError(error); + } + + void innerComplete(SwitchMapInnerObserver sender) { + if (inner.compareAndSet(sender, null)) { + if (done) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + } + } + } + + static final class SwitchMapInnerObserver extends AtomicReference + implements CompletableObserver { + + private static final long serialVersionUID = -8003404460084760287L; + + final SwitchMapCompletableObserver parent; + + SwitchMapInnerObserver(SwitchMapCompletableObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybe.java new file mode 100755 index 0000000..7bb3941 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapMaybe.java @@ -0,0 +1,301 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones and emits the latest success value if available, optionally delaying + * errors from the main source or the inner sources. + *

History: 2.1.11 - experimental + * @param the upstream value type + * @param the downstream value type + * @since 2.2 + */ +public final class FlowableSwitchMapMaybe extends Flowable { + + final Flowable source; + + final Function> mapper; + + final boolean delayErrors; + + public FlowableSwitchMapMaybe(Flowable source, + Function> mapper, + boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new SwitchMapMaybeSubscriber(s, mapper, delayErrors)); + } + + static final class SwitchMapMaybeSubscriber extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -5402190102429853762L; + + final Subscriber downstream; + + final Function> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicLong requested; + + final AtomicReference> inner; + + static final SwitchMapMaybeObserver INNER_DISPOSED = + new SwitchMapMaybeObserver(null); + + Subscription upstream; + + volatile boolean done; + + volatile boolean cancelled; + + long emitted; + + SwitchMapMaybeSubscriber(Subscriber downstream, + Function> mapper, + boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.requested = new AtomicLong(); + this.inner = new AtomicReference>(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void onNext(T t) { + SwitchMapMaybeObserver current = inner.get(); + if (current != null) { + current.dispose(); + } + + MaybeSource ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + inner.getAndSet((SwitchMapMaybeObserver)INNER_DISPOSED); + onError(ex); + return; + } + + SwitchMapMaybeObserver observer = new SwitchMapMaybeObserver(this); + + for (;;) { + current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, observer)) { + ms.subscribe(observer); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + void disposeInner() { + SwitchMapMaybeObserver current = inner.getAndSet((SwitchMapMaybeObserver)INNER_DISPOSED); + if (current != null && current != INNER_DISPOSED) { + current.dispose(); + } + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + disposeInner(); + } + + void innerError(SwitchMapMaybeObserver sender, Throwable ex) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(ex)) { + if (!delayErrors) { + upstream.cancel(); + disposeInner(); + } + drain(); + return; + } + } + RxJavaPlugins.onError(ex); + } + + void innerComplete(SwitchMapMaybeObserver sender) { + if (inner.compareAndSet(sender, null)) { + drain(); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber downstream = this.downstream; + AtomicThrowable errors = this.errors; + AtomicReference> inner = this.inner; + AtomicLong requested = this.requested; + long emitted = this.emitted; + + for (;;) { + + for (;;) { + if (cancelled) { + return; + } + + if (errors.get() != null) { + if (!delayErrors) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + SwitchMapMaybeObserver current = inner.get(); + boolean empty = current == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (empty || current.item == null || emitted == requested.get()) { + break; + } + + inner.compareAndSet(current, null); + + downstream.onNext(current.item); + + emitted++; + } + + this.emitted = emitted; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class SwitchMapMaybeObserver + extends AtomicReference implements MaybeObserver { + + private static final long serialVersionUID = 8042919737683345351L; + + final SwitchMapMaybeSubscriber parent; + + volatile R item; + + SwitchMapMaybeObserver(SwitchMapMaybeSubscriber parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R t) { + item = t; + parent.drain(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingle.java new file mode 100755 index 0000000..752ee85 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/FlowableSwitchMapSingle.java @@ -0,0 +1,290 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link SingleSource}s and switches (subscribes) to the newer ones + * while disposing the older ones and emits the latest success value, optionally delaying + * errors from the main source or the inner sources. + *

History: 2.1.11 - experimental + * @param the upstream value type + * @param the downstream value type + * @since 2.2 + */ +public final class FlowableSwitchMapSingle extends Flowable { + + final Flowable source; + + final Function> mapper; + + final boolean delayErrors; + + public FlowableSwitchMapSingle(Flowable source, + Function> mapper, + boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new SwitchMapSingleSubscriber(s, mapper, delayErrors)); + } + + static final class SwitchMapSingleSubscriber extends AtomicInteger + implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -5402190102429853762L; + + final Subscriber downstream; + + final Function> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicLong requested; + + final AtomicReference> inner; + + static final SwitchMapSingleObserver INNER_DISPOSED = + new SwitchMapSingleObserver(null); + + Subscription upstream; + + volatile boolean done; + + volatile boolean cancelled; + + long emitted; + + SwitchMapSingleSubscriber(Subscriber downstream, + Function> mapper, + boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.requested = new AtomicLong(); + this.inner = new AtomicReference>(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(upstream, s)) { + upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void onNext(T t) { + SwitchMapSingleObserver current = inner.get(); + if (current != null) { + current.dispose(); + } + + SingleSource ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + inner.getAndSet((SwitchMapSingleObserver)INNER_DISPOSED); + onError(ex); + return; + } + + SwitchMapSingleObserver observer = new SwitchMapSingleObserver(this); + + for (;;) { + current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, observer)) { + ss.subscribe(observer); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + void disposeInner() { + SwitchMapSingleObserver current = inner.getAndSet((SwitchMapSingleObserver)INNER_DISPOSED); + if (current != null && current != INNER_DISPOSED) { + current.dispose(); + } + } + + @Override + public void request(long n) { + BackpressureHelper.add(requested, n); + drain(); + } + + @Override + public void cancel() { + cancelled = true; + upstream.cancel(); + disposeInner(); + } + + void innerError(SwitchMapSingleObserver sender, Throwable ex) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(ex)) { + if (!delayErrors) { + upstream.cancel(); + disposeInner(); + } + drain(); + return; + } + } + RxJavaPlugins.onError(ex); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber downstream = this.downstream; + AtomicThrowable errors = this.errors; + AtomicReference> inner = this.inner; + AtomicLong requested = this.requested; + long emitted = this.emitted; + + for (;;) { + + for (;;) { + if (cancelled) { + return; + } + + if (errors.get() != null) { + if (!delayErrors) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + SwitchMapSingleObserver current = inner.get(); + boolean empty = current == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (empty || current.item == null || emitted == requested.get()) { + break; + } + + inner.compareAndSet(current, null); + + downstream.onNext(current.item); + + emitted++; + } + + this.emitted = emitted; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class SwitchMapSingleObserver + extends AtomicReference implements SingleObserver { + + private static final long serialVersionUID = 8042919737683345351L; + + final SwitchMapSingleSubscriber parent; + + volatile R item; + + SwitchMapSingleObserver(SwitchMapSingleSubscriber parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R t) { + item = t; + parent.drain(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/MaterializeSingleObserver.java b/src/main/java/io/reactivex/internal/operators/mixed/MaterializeSingleObserver.java new file mode 100755 index 0000000..ef8a870 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/MaterializeSingleObserver.java @@ -0,0 +1,71 @@ +/** + * 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.internal.operators.mixed; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * A consumer that implements the consumer types of Maybe, Single and Completable + * and turns their signals into Notifications for a SingleObserver. + * @param the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class MaterializeSingleObserver +implements SingleObserver, MaybeObserver, CompletableObserver, Disposable { + + final SingleObserver> downstream; + + Disposable upstream; + + public MaterializeSingleObserver(SingleObserver> downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onComplete() { + downstream.onSuccess(Notification.createOnComplete()); + } + + @Override + public void onSuccess(T t) { + downstream.onSuccess(Notification.createOnNext(t)); + } + + @Override + public void onError(Throwable e) { + downstream.onSuccess(Notification.createOnError(e)); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void dispose() { + upstream.dispose(); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapObservable.java b/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapObservable.java new file mode 100755 index 0000000..533e00a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapObservable.java @@ -0,0 +1,113 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of a Maybe onto an ObservableSource and + * relays its signals to the downstream observer. + * + * @param the success value type of the Maybe source + * @param the result type of the ObservableSource and this operator + * @since 2.1.15 + */ +public final class MaybeFlatMapObservable extends Observable { + + final MaybeSource source; + + final Function> mapper; + + public MaybeFlatMapObservable(MaybeSource source, + Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Observer observer) { + FlatMapObserver parent = new FlatMapObserver(observer, mapper); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class FlatMapObserver + extends AtomicReference + implements Observer, MaybeObserver, Disposable { + + private static final long serialVersionUID = -8948264376121066672L; + + final Observer downstream; + + final Function> mapper; + + FlatMapObserver(Observer downstream, Function> mapper) { + this.downstream = downstream; + this.mapper = mapper; + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(T t) { + ObservableSource o; + + try { + o = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + o.subscribe(this); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapPublisher.java b/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapPublisher.java new file mode 100755 index 0000000..2bd7b18 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/MaybeFlatMapPublisher.java @@ -0,0 +1,127 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * Maps the success value of a Maybe onto a Publisher and + * relays its signals to the downstream subscriber. + * + * @param the success value type of the Maybe source + * @param the result type of the Publisher and this operator + * @since 2.1.15 + */ +public final class MaybeFlatMapPublisher extends Flowable { + + final MaybeSource source; + + final Function> mapper; + + public MaybeFlatMapPublisher(MaybeSource source, + Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new FlatMapPublisherSubscriber(s, mapper)); + } + + static final class FlatMapPublisherSubscriber + extends AtomicReference + implements FlowableSubscriber, MaybeObserver, Subscription { + + private static final long serialVersionUID = -8948264376121066672L; + + final Subscriber downstream; + + final Function> mapper; + + Disposable upstream; + + final AtomicLong requested; + + FlatMapPublisherSubscriber(Subscriber downstream, Function> mapper) { + this.downstream = downstream; + this.mapper = mapper; + this.requested = new AtomicLong(); + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(this, requested, n); + } + + @Override + public void cancel() { + upstream.dispose(); + SubscriptionHelper.cancel(this); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + Publisher p; + + try { + p = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + p.subscribe(this); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(this, requested, s); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java new file mode 100755 index 0000000..41fa298 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java @@ -0,0 +1,302 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link CompletableSource}s and subscribes to them one after the + * other completes or terminates (in error-delaying mode). + *

History: 2.1.11 - experimental + * @param the upstream value type + * @since 2.2 + */ +public final class ObservableConcatMapCompletable extends Completable { + + final Observable source; + + final Function mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public ObservableConcatMapCompletable(Observable source, + Function mapper, + ErrorMode errorMode, + int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + if (!ScalarXMapZHelper.tryAsCompletable(source, mapper, observer)) { + source.subscribe(new ConcatMapCompletableObserver(observer, mapper, errorMode, prefetch)); + } + } + + static final class ConcatMapCompletableObserver + extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = 3610901111000061034L; + + final CompletableObserver downstream; + + final Function mapper; + + final ErrorMode errorMode; + + final AtomicThrowable errors; + + final ConcatMapInnerObserver inner; + + final int prefetch; + + SimpleQueue queue; + + Disposable upstream; + + volatile boolean active; + + volatile boolean done; + + volatile boolean disposed; + + ConcatMapCompletableObserver(CompletableObserver downstream, + Function mapper, + ErrorMode errorMode, int prefetch) { + this.downstream = downstream; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapInnerObserver(this); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + if (d instanceof QueueDisposable) { + @SuppressWarnings("unchecked") + QueueDisposable qd = (QueueDisposable) d; + + int m = qd.requestFusion(QueueDisposable.ANY); + if (m == QueueDisposable.SYNC) { + queue = qd; + done = true; + downstream.onSubscribe(this); + drain(); + return; + } + if (m == QueueDisposable.ASYNC) { + queue = qd; + downstream.onSubscribe(this); + return; + } + } + queue = new SpscLinkedArrayQueue(prefetch); + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (t != null) { + queue.offer(t); + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + disposed = true; + inner.dispose(); + t = errors.terminate(); + if (t != ExceptionHelper.TERMINATED) { + downstream.onError(t); + } + if (getAndIncrement() == 0) { + queue.clear(); + } + } else { + done = true; + drain(); + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + disposed = true; + upstream.dispose(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode == ErrorMode.IMMEDIATE) { + disposed = true; + upstream.dispose(); + ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + if (getAndIncrement() == 0) { + queue.clear(); + } + } else { + active = false; + drain(); + } + } else { + RxJavaPlugins.onError(ex); + } + } + + void innerComplete() { + active = false; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + AtomicThrowable errors = this.errors; + ErrorMode errorMode = this.errorMode; + + do { + if (disposed) { + queue.clear(); + return; + } + + if (!active) { + + if (errorMode == ErrorMode.BOUNDARY) { + if (errors.get() != null) { + disposed = true; + queue.clear(); + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + boolean empty = true; + CompletableSource cs = null; + try { + T v = queue.poll(); + if (v != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null CompletableSource"); + empty = false; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + disposed = true; + queue.clear(); + upstream.dispose(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + if (d && empty) { + disposed = true; + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (!empty) { + active = true; + cs.subscribe(inner); + } + } + } while (decrementAndGet() != 0); + } + + static final class ConcatMapInnerObserver extends AtomicReference + implements CompletableObserver { + + private static final long serialVersionUID = 5638352172918776687L; + + final ConcatMapCompletableObserver parent; + + ConcatMapInnerObserver(ConcatMapCompletableObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java new file mode 100755 index 0000000..32b174a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java @@ -0,0 +1,306 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each upstream item into a {@link MaybeSource}, subscribes to them one after the other terminates + * and relays their success values, optionally delaying any errors till the main and inner sources + * terminate. + *

History: 2.1.11 - experimental + * @param the upstream element type + * @param the output element type + * @since 2.2 + */ +public final class ObservableConcatMapMaybe extends Observable { + + final Observable source; + + final Function> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public ObservableConcatMapMaybe(Observable source, + Function> mapper, + ErrorMode errorMode, int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(Observer observer) { + if (!ScalarXMapZHelper.tryAsMaybe(source, mapper, observer)) { + source.subscribe(new ConcatMapMaybeMainObserver(observer, mapper, prefetch, errorMode)); + } + } + + static final class ConcatMapMaybeMainObserver + extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = -9140123220065488293L; + + final Observer downstream; + + final Function> mapper; + + final AtomicThrowable errors; + + final ConcatMapMaybeObserver inner; + + final SimplePlainQueue queue; + + final ErrorMode errorMode; + + Disposable upstream; + + volatile boolean done; + + volatile boolean cancelled; + + R item; + + volatile int state; + + /** No inner MaybeSource is running. */ + static final int STATE_INACTIVE = 0; + /** An inner MaybeSource is running but there are no results yet. */ + static final int STATE_ACTIVE = 1; + /** The inner MaybeSource succeeded with a value in {@link #item}. */ + static final int STATE_RESULT_VALUE = 2; + + ConcatMapMaybeMainObserver(Observer downstream, + Function> mapper, + int prefetch, ErrorMode errorMode) { + this.downstream = downstream; + this.mapper = mapper; + this.errorMode = errorMode; + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapMaybeObserver(this); + this.queue = new SpscLinkedArrayQueue(prefetch); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + item = null; + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerSuccess(R item) { + this.item = item; + this.state = STATE_RESULT_VALUE; + drain(); + } + + void innerComplete() { + this.state = STATE_INACTIVE; + drain(); + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode != ErrorMode.END) { + upstream.dispose(); + } + this.state = STATE_INACTIVE; + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer downstream = this.downstream; + ErrorMode errorMode = this.errorMode; + SimplePlainQueue queue = this.queue; + AtomicThrowable errors = this.errors; + + for (;;) { + + for (;;) { + if (cancelled) { + queue.clear(); + item = null; + break; + } + + int s = state; + + if (errors.get() != null) { + if (errorMode == ErrorMode.IMMEDIATE + || (errorMode == ErrorMode.BOUNDARY && s == STATE_INACTIVE)) { + queue.clear(); + item = null; + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + if (s == STATE_INACTIVE) { + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + MaybeSource ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + queue.clear(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + state = STATE_ACTIVE; + ms.subscribe(inner); + break; + } else if (s == STATE_RESULT_VALUE) { + R w = item; + item = null; + downstream.onNext(w); + + state = STATE_INACTIVE; + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConcatMapMaybeObserver + extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = -3051469169682093892L; + + final ConcatMapMaybeMainObserver parent; + + ConcatMapMaybeObserver(ConcatMapMaybeMainObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(R t) { + parent.innerSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java new file mode 100755 index 0000000..1358e1e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java @@ -0,0 +1,296 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each upstream item into a {@link SingleSource}, subscribes to them one after the other terminates + * and relays their success values, optionally delaying any errors till the main and inner sources + * terminate. + *

History: 2.1.11 - experimental + * @param the upstream element type + * @param the output element type + * @since 2.2 + */ +public final class ObservableConcatMapSingle extends Observable { + + final Observable source; + + final Function> mapper; + + final ErrorMode errorMode; + + final int prefetch; + + public ObservableConcatMapSingle(Observable source, + Function> mapper, + ErrorMode errorMode, int prefetch) { + this.source = source; + this.mapper = mapper; + this.errorMode = errorMode; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(Observer observer) { + if (!ScalarXMapZHelper.tryAsSingle(source, mapper, observer)) { + source.subscribe(new ConcatMapSingleMainObserver(observer, mapper, prefetch, errorMode)); + } + } + + static final class ConcatMapSingleMainObserver + extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = -9140123220065488293L; + + final Observer downstream; + + final Function> mapper; + + final AtomicThrowable errors; + + final ConcatMapSingleObserver inner; + + final SimplePlainQueue queue; + + final ErrorMode errorMode; + + Disposable upstream; + + volatile boolean done; + + volatile boolean cancelled; + + R item; + + volatile int state; + + /** No inner SingleSource is running. */ + static final int STATE_INACTIVE = 0; + /** An inner SingleSource is running but there are no results yet. */ + static final int STATE_ACTIVE = 1; + /** The inner SingleSource succeeded with a value in {@link #item}. */ + static final int STATE_RESULT_VALUE = 2; + + ConcatMapSingleMainObserver(Observer downstream, + Function> mapper, + int prefetch, ErrorMode errorMode) { + this.downstream = downstream; + this.mapper = mapper; + this.errorMode = errorMode; + this.errors = new AtomicThrowable(); + this.inner = new ConcatMapSingleObserver(this); + this.queue = new SpscLinkedArrayQueue(prefetch); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (errorMode == ErrorMode.IMMEDIATE) { + inner.dispose(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + inner.dispose(); + if (getAndIncrement() == 0) { + queue.clear(); + item = null; + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerSuccess(R item) { + this.item = item; + this.state = STATE_RESULT_VALUE; + drain(); + } + + void innerError(Throwable ex) { + if (errors.addThrowable(ex)) { + if (errorMode != ErrorMode.END) { + upstream.dispose(); + } + this.state = STATE_INACTIVE; + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer downstream = this.downstream; + ErrorMode errorMode = this.errorMode; + SimplePlainQueue queue = this.queue; + AtomicThrowable errors = this.errors; + + for (;;) { + + for (;;) { + if (cancelled) { + queue.clear(); + item = null; + break; + } + + int s = state; + + if (errors.get() != null) { + if (errorMode == ErrorMode.IMMEDIATE + || (errorMode == ErrorMode.BOUNDARY && s == STATE_INACTIVE)) { + queue.clear(); + item = null; + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + if (s == STATE_INACTIVE) { + boolean d = done; + T v = queue.poll(); + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + SingleSource ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + queue.clear(); + errors.addThrowable(ex); + ex = errors.terminate(); + downstream.onError(ex); + return; + } + + state = STATE_ACTIVE; + ss.subscribe(inner); + break; + } else if (s == STATE_RESULT_VALUE) { + R w = item; + item = null; + downstream.onNext(w); + + state = STATE_INACTIVE; + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConcatMapSingleObserver + extends AtomicReference + implements SingleObserver { + + private static final long serialVersionUID = -3051469169682093892L; + + final ConcatMapSingleMainObserver parent; + + ConcatMapSingleObserver(ConcatMapSingleMainObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(R t) { + parent.innerSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java new file mode 100755 index 0000000..1d4e8d2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java @@ -0,0 +1,235 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream values into {@link CompletableSource}s, subscribes to the newer one while + * disposing the subscription to the previous {@code CompletableSource}, thus keeping at most one + * active {@code CompletableSource} running. + *

History: 2.1.11 - experimental + * @param the upstream value type + * @since 2.2 + */ +public final class ObservableSwitchMapCompletable extends Completable { + + final Observable source; + + final Function mapper; + + final boolean delayErrors; + + public ObservableSwitchMapCompletable(Observable source, + Function mapper, boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + if (!ScalarXMapZHelper.tryAsCompletable(source, mapper, observer)) { + source.subscribe(new SwitchMapCompletableObserver(observer, mapper, delayErrors)); + } + } + + static final class SwitchMapCompletableObserver implements Observer, Disposable { + + final CompletableObserver downstream; + + final Function mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicReference inner; + + static final SwitchMapInnerObserver INNER_DISPOSED = new SwitchMapInnerObserver(null); + + volatile boolean done; + + Disposable upstream; + + SwitchMapCompletableObserver(CompletableObserver downstream, + Function mapper, boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.inner = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + CompletableSource c; + + try { + c = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + + SwitchMapInnerObserver o = new SwitchMapInnerObserver(this); + + for (;;) { + SwitchMapInnerObserver current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, o)) { + if (current != null) { + current.dispose(); + } + c.subscribe(o); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (delayErrors) { + onComplete(); + } else { + disposeInner(); + Throwable ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + if (inner.get() == null) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + } + } + + void disposeInner() { + SwitchMapInnerObserver o = inner.getAndSet(INNER_DISPOSED); + if (o != null && o != INNER_DISPOSED) { + o.dispose(); + } + } + + @Override + public void dispose() { + upstream.dispose(); + disposeInner(); + } + + @Override + public boolean isDisposed() { + return inner.get() == INNER_DISPOSED; + } + + void innerError(SwitchMapInnerObserver sender, Throwable error) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(error)) { + if (delayErrors) { + if (done) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } else { + dispose(); + Throwable ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + downstream.onError(ex); + } + } + return; + } + } + RxJavaPlugins.onError(error); + } + + void innerComplete(SwitchMapInnerObserver sender) { + if (inner.compareAndSet(sender, null)) { + if (done) { + Throwable ex = errors.terminate(); + if (ex == null) { + downstream.onComplete(); + } else { + downstream.onError(ex); + } + } + } + } + + static final class SwitchMapInnerObserver extends AtomicReference + implements CompletableObserver { + + private static final long serialVersionUID = -8003404460084760287L; + + final SwitchMapCompletableObserver parent; + + SwitchMapInnerObserver(SwitchMapCompletableObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java new file mode 100755 index 0000000..8908625 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java @@ -0,0 +1,288 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link MaybeSource}s and switches (subscribes) to the newer ones + * while disposing the older ones and emits the latest success value if available, optionally delaying + * errors from the main source or the inner sources. + *

History: 2.1.11 - experimental + * @param the upstream value type + * @param the downstream value type + * @since 2.2 + */ +public final class ObservableSwitchMapMaybe extends Observable { + + final Observable source; + + final Function> mapper; + + final boolean delayErrors; + + public ObservableSwitchMapMaybe(Observable source, + Function> mapper, + boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Observer observer) { + if (!ScalarXMapZHelper.tryAsMaybe(source, mapper, observer)) { + source.subscribe(new SwitchMapMaybeMainObserver(observer, mapper, delayErrors)); + } + } + + static final class SwitchMapMaybeMainObserver extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = -5402190102429853762L; + + final Observer downstream; + + final Function> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicReference> inner; + + static final SwitchMapMaybeObserver INNER_DISPOSED = + new SwitchMapMaybeObserver(null); + + Disposable upstream; + + volatile boolean done; + + volatile boolean cancelled; + + SwitchMapMaybeMainObserver(Observer downstream, + Function> mapper, + boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.inner = new AtomicReference>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void onNext(T t) { + SwitchMapMaybeObserver current = inner.get(); + if (current != null) { + current.dispose(); + } + + MaybeSource ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + inner.getAndSet((SwitchMapMaybeObserver)INNER_DISPOSED); + onError(ex); + return; + } + + SwitchMapMaybeObserver observer = new SwitchMapMaybeObserver(this); + + for (;;) { + current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, observer)) { + ms.subscribe(observer); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + void disposeInner() { + SwitchMapMaybeObserver current = inner.getAndSet((SwitchMapMaybeObserver)INNER_DISPOSED); + if (current != null && current != INNER_DISPOSED) { + current.dispose(); + } + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + disposeInner(); + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerError(SwitchMapMaybeObserver sender, Throwable ex) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(ex)) { + if (!delayErrors) { + upstream.dispose(); + disposeInner(); + } + drain(); + return; + } + } + RxJavaPlugins.onError(ex); + } + + void innerComplete(SwitchMapMaybeObserver sender) { + if (inner.compareAndSet(sender, null)) { + drain(); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer downstream = this.downstream; + AtomicThrowable errors = this.errors; + AtomicReference> inner = this.inner; + + for (;;) { + + for (;;) { + if (cancelled) { + return; + } + + if (errors.get() != null) { + if (!delayErrors) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + SwitchMapMaybeObserver current = inner.get(); + boolean empty = current == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (empty || current.item == null) { + break; + } + + inner.compareAndSet(current, null); + + downstream.onNext(current.item); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class SwitchMapMaybeObserver + extends AtomicReference implements MaybeObserver { + + private static final long serialVersionUID = 8042919737683345351L; + + final SwitchMapMaybeMainObserver parent; + + volatile R item; + + SwitchMapMaybeObserver(SwitchMapMaybeMainObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R t) { + item = t; + parent.drain(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java new file mode 100755 index 0000000..f9871aa --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java @@ -0,0 +1,277 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps the upstream items into {@link SingleSource}s and switches (subscribes) to the newer ones + * while disposing the older ones and emits the latest success value if available, optionally delaying + * errors from the main source or the inner sources. + *

History: 2.1.11 - experimental + * @param the upstream value type + * @param the downstream value type + * @since 2.2 + */ +public final class ObservableSwitchMapSingle extends Observable { + + final Observable source; + + final Function> mapper; + + final boolean delayErrors; + + public ObservableSwitchMapSingle(Observable source, + Function> mapper, + boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Observer observer) { + if (!ScalarXMapZHelper.tryAsSingle(source, mapper, observer)) { + source.subscribe(new SwitchMapSingleMainObserver(observer, mapper, delayErrors)); + } + } + + static final class SwitchMapSingleMainObserver extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = -5402190102429853762L; + + final Observer downstream; + + final Function> mapper; + + final boolean delayErrors; + + final AtomicThrowable errors; + + final AtomicReference> inner; + + static final SwitchMapSingleObserver INNER_DISPOSED = + new SwitchMapSingleObserver(null); + + Disposable upstream; + + volatile boolean done; + + volatile boolean cancelled; + + SwitchMapSingleMainObserver(Observer downstream, + Function> mapper, + boolean delayErrors) { + this.downstream = downstream; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.inner = new AtomicReference>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void onNext(T t) { + SwitchMapSingleObserver current = inner.get(); + if (current != null) { + current.dispose(); + } + + SingleSource ss; + + try { + ss = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + inner.getAndSet((SwitchMapSingleObserver)INNER_DISPOSED); + onError(ex); + return; + } + + SwitchMapSingleObserver observer = new SwitchMapSingleObserver(this); + + for (;;) { + current = inner.get(); + if (current == INNER_DISPOSED) { + break; + } + if (inner.compareAndSet(current, observer)) { + ss.subscribe(observer); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + void disposeInner() { + SwitchMapSingleObserver current = inner.getAndSet((SwitchMapSingleObserver)INNER_DISPOSED); + if (current != null && current != INNER_DISPOSED) { + current.dispose(); + } + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + disposeInner(); + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerError(SwitchMapSingleObserver sender, Throwable ex) { + if (inner.compareAndSet(sender, null)) { + if (errors.addThrowable(ex)) { + if (!delayErrors) { + upstream.dispose(); + disposeInner(); + } + drain(); + return; + } + } + RxJavaPlugins.onError(ex); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer downstream = this.downstream; + AtomicThrowable errors = this.errors; + AtomicReference> inner = this.inner; + + for (;;) { + + for (;;) { + if (cancelled) { + return; + } + + if (errors.get() != null) { + if (!delayErrors) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + return; + } + } + + boolean d = done; + SwitchMapSingleObserver current = inner.get(); + boolean empty = current == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + if (empty || current.item == null) { + break; + } + + inner.compareAndSet(current, null); + + downstream.onNext(current.item); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class SwitchMapSingleObserver + extends AtomicReference implements SingleObserver { + + private static final long serialVersionUID = 8042919737683345351L; + + final SwitchMapSingleMainObserver parent; + + volatile R item; + + SwitchMapSingleObserver(SwitchMapSingleMainObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R t) { + item = t; + parent.drain(); + } + + @Override + public void onError(Throwable e) { + parent.innerError(this, e); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java b/src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java new file mode 100755 index 0000000..2755a42 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java @@ -0,0 +1,155 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.maybe.MaybeToObservable; +import io.reactivex.internal.operators.single.SingleToObservable; + +/** + * Utility class to extract a value from a scalar source reactive type, + * map it to a 0-1 type then subscribe the output type's consumer to it, + * saving on the overhead of the regular subscription channel. + *

History: 2.1.11 - experimental + * @since 2.2 + */ +final class ScalarXMapZHelper { + + private ScalarXMapZHelper() { + throw new IllegalStateException("No instances!"); + } + + /** + * Try subscribing to a {@link CompletableSource} mapped from + * a scalar source (which implements {@link Callable}). + * @param the upstream value type + * @param source the source reactive type ({@code Flowable} or {@code Observable}) + * possibly implementing {@link Callable}. + * @param mapper the function that turns the scalar upstream value into a + * {@link CompletableSource} + * @param observer the consumer to subscribe to the mapped {@link CompletableSource} + * @return true if a subscription did happen and the regular path should be skipped + */ + static boolean tryAsCompletable(Object source, + Function mapper, + CompletableObserver observer) { + if (source instanceof Callable) { + @SuppressWarnings("unchecked") + Callable call = (Callable) source; + CompletableSource cs = null; + try { + T item = call.call(); + if (item != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null CompletableSource"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (cs == null) { + EmptyDisposable.complete(observer); + } else { + cs.subscribe(observer); + } + return true; + } + return false; + } + + /** + * Try subscribing to a {@link MaybeSource} mapped from + * a scalar source (which implements {@link Callable}). + * @param the upstream value type + * @param source the source reactive type ({@code Flowable} or {@code Observable}) + * possibly implementing {@link Callable}. + * @param mapper the function that turns the scalar upstream value into a + * {@link MaybeSource} + * @param observer the consumer to subscribe to the mapped {@link MaybeSource} + * @return true if a subscription did happen and the regular path should be skipped + */ + static boolean tryAsMaybe(Object source, + Function> mapper, + Observer observer) { + if (source instanceof Callable) { + @SuppressWarnings("unchecked") + Callable call = (Callable) source; + MaybeSource cs = null; + try { + T item = call.call(); + if (item != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null MaybeSource"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (cs == null) { + EmptyDisposable.complete(observer); + } else { + cs.subscribe(MaybeToObservable.create(observer)); + } + return true; + } + return false; + } + + /** + * Try subscribing to a {@link SingleSource} mapped from + * a scalar source (which implements {@link Callable}). + * @param the upstream value type + * @param source the source reactive type ({@code Flowable} or {@code Observable}) + * possibly implementing {@link Callable}. + * @param mapper the function that turns the scalar upstream value into a + * {@link SingleSource} + * @param observer the consumer to subscribe to the mapped {@link SingleSource} + * @return true if a subscription did happen and the regular path should be skipped + */ + static boolean tryAsSingle(Object source, + Function> mapper, + Observer observer) { + if (source instanceof Callable) { + @SuppressWarnings("unchecked") + Callable call = (Callable) source; + SingleSource cs = null; + try { + T item = call.call(); + if (item != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null SingleSource"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (cs == null) { + EmptyDisposable.complete(observer); + } else { + cs.subscribe(SingleToObservable.create(observer)); + } + return true; + } + return false; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/mixed/SingleFlatMapObservable.java b/src/main/java/io/reactivex/internal/operators/mixed/SingleFlatMapObservable.java new file mode 100755 index 0000000..48d5793 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/SingleFlatMapObservable.java @@ -0,0 +1,113 @@ +/** + * 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.internal.operators.mixed; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of a Single onto an ObservableSource and + * relays its signals to the downstream observer. + * + * @param the success value type of the Single source + * @param the result type of the ObservableSource and this operator + * @since 2.1.15 + */ +public final class SingleFlatMapObservable extends Observable { + + final SingleSource source; + + final Function> mapper; + + public SingleFlatMapObservable(SingleSource source, + Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Observer observer) { + FlatMapObserver parent = new FlatMapObserver(observer, mapper); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class FlatMapObserver + extends AtomicReference + implements Observer, SingleObserver, Disposable { + + private static final long serialVersionUID = -8948264376121066672L; + + final Observer downstream; + + final Function> mapper; + + FlatMapObserver(Observer downstream, Function> mapper) { + this.downstream = downstream; + this.mapper = mapper; + } + + @Override + public void onNext(R t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(T t) { + ObservableSource o; + + try { + o = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + o.subscribe(this); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/AbstractObservableWithUpstream.java b/src/main/java/io/reactivex/internal/operators/observable/AbstractObservableWithUpstream.java new file mode 100755 index 0000000..c2da840 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/AbstractObservableWithUpstream.java @@ -0,0 +1,43 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.internal.fuseable.HasUpstreamObservableSource; + +/** + * Base class for operators with a source consumable. + * + * @param the input source type + * @param the output type + */ +abstract class AbstractObservableWithUpstream extends Observable implements HasUpstreamObservableSource { + + /** The source consumable Observable. */ + protected final ObservableSource source; + + /** + * Constructs the ObservableSource with the given consumable. + * @param source the consumable Observable + */ + AbstractObservableWithUpstream(ObservableSource source) { + this.source = source; + } + + @Override + public final ObservableSource source() { + return source; + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java new file mode 100755 index 0000000..24a7cb7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java @@ -0,0 +1,164 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.*; + +import io.reactivex.ObservableSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.*; + +public final class BlockingObservableIterable implements Iterable { + final ObservableSource source; + + final int bufferSize; + + public BlockingObservableIterable(ObservableSource source, int bufferSize) { + this.source = source; + this.bufferSize = bufferSize; + } + + @Override + public Iterator iterator() { + BlockingObservableIterator it = new BlockingObservableIterator(bufferSize); + source.subscribe(it); + return it; + } + + static final class BlockingObservableIterator + extends AtomicReference + implements io.reactivex.Observer, Iterator, Disposable { + + private static final long serialVersionUID = 6695226475494099826L; + + final SpscLinkedArrayQueue queue; + + final Lock lock; + + final Condition condition; + + volatile boolean done; + volatile Throwable error; + + BlockingObservableIterator(int batchSize) { + this.queue = new SpscLinkedArrayQueue(batchSize); + this.lock = new ReentrantLock(); + this.condition = lock.newCondition(); + } + + @Override + public boolean hasNext() { + for (;;) { + if (isDisposed()) { + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + return false; + } + boolean d = done; + boolean empty = queue.isEmpty(); + if (d) { + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } else + if (empty) { + return false; + } + } + if (empty) { + try { + BlockingHelper.verifyNonBlocking(); + lock.lock(); + try { + while (!done && queue.isEmpty() && !isDisposed()) { + condition.await(); + } + } finally { + lock.unlock(); + } + } catch (InterruptedException ex) { + DisposableHelper.dispose(this); + signalConsumer(); + throw ExceptionHelper.wrapOrThrow(ex); + } + } else { + return true; + } + } + } + + @Override + public T next() { + if (hasNext()) { + return queue.poll(); + } + throw new NoSuchElementException(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(T t) { + queue.offer(t); + signalConsumer(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + signalConsumer(); + } + + @Override + public void onComplete() { + done = true; + signalConsumer(); + } + + void signalConsumer() { + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); + } + } + + @Override // otherwise default method which isn't available in Java 7 + public void remove() { + throw new UnsupportedOperationException("remove"); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + signalConsumer(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableLatest.java b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableLatest.java new file mode 100755 index 0000000..471e695 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableLatest.java @@ -0,0 +1,114 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.internal.util.*; +import io.reactivex.observers.DisposableObserver; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Wait for and iterate over the latest values of the source observable. If the source works faster than the + * iterator, values may be skipped, but not the {@code onError} or {@code onComplete} events. + * @param the value type + */ +public final class BlockingObservableLatest implements Iterable { + + final ObservableSource source; + + public BlockingObservableLatest(ObservableSource source) { + this.source = source; + } + + @Override + public Iterator iterator() { + BlockingObservableLatestIterator lio = new BlockingObservableLatestIterator(); + + Observable> materialized = Observable.wrap(source).materialize(); + + materialized.subscribe(lio); + return lio; + } + + static final class BlockingObservableLatestIterator extends DisposableObserver> implements Iterator { + // iterator's notification + Notification iteratorNotification; + + final Semaphore notify = new Semaphore(0); + // observer's notification + final AtomicReference> value = new AtomicReference>(); + + @Override + public void onNext(Notification args) { + boolean wasNotAvailable = value.getAndSet(args) == null; + if (wasNotAvailable) { + notify.release(); + } + } + + @Override + public void onError(Throwable e) { + RxJavaPlugins.onError(e); + } + + @Override + public void onComplete() { + // not expected + } + + @Override + public boolean hasNext() { + if (iteratorNotification != null && iteratorNotification.isOnError()) { + throw ExceptionHelper.wrapOrThrow(iteratorNotification.getError()); + } + if (iteratorNotification == null) { + try { + BlockingHelper.verifyNonBlocking(); + notify.acquire(); + } catch (InterruptedException ex) { + dispose(); + iteratorNotification = Notification.createOnError(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + + Notification n = value.getAndSet(null); + iteratorNotification = n; + if (n.isOnError()) { + throw ExceptionHelper.wrapOrThrow(n.getError()); + } + } + return iteratorNotification.isOnNext(); + } + + @Override + public T next() { + if (hasNext()) { + T v = iteratorNotification.getValue(); + iteratorNotification = null; + return v; + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Read-only iterator."); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java new file mode 100755 index 0000000..04940be --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java @@ -0,0 +1,119 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; + +import io.reactivex.ObservableSource; +import io.reactivex.internal.util.*; +import io.reactivex.observers.DefaultObserver; + +/** + * Returns an Iterable that always returns the item most recently emitted by an Observable, or a + * seed value if no item has yet been emitted. + *

+ * + * + * @param the value type + */ +public final class BlockingObservableMostRecent implements Iterable { + + final ObservableSource source; + + final T initialValue; + + public BlockingObservableMostRecent(ObservableSource source, T initialValue) { + this.source = source; + this.initialValue = initialValue; + } + + @Override + public Iterator iterator() { + MostRecentObserver mostRecentObserver = new MostRecentObserver(initialValue); + + source.subscribe(mostRecentObserver); + + return mostRecentObserver.getIterable(); + } + + static final class MostRecentObserver extends DefaultObserver { + volatile Object value; + + MostRecentObserver(T value) { + this.value = NotificationLite.next(value); + } + + @Override + public void onComplete() { + value = NotificationLite.complete(); + } + + @Override + public void onError(Throwable e) { + value = NotificationLite.error(e); + } + + @Override + public void onNext(T args) { + value = NotificationLite.next(args); + } + + /** + * The {@link Iterator} return is not thread safe. In other words don't call {@link Iterator#hasNext()} in one + * thread expect {@link Iterator#next()} called from a different thread to work. + * @return the Iterator + */ + public Iterator getIterable() { + return new Iterator(); + } + + final class Iterator implements java.util.Iterator { + /** + * buffer to make sure that the state of the iterator doesn't change between calling hasNext() and next(). + */ + private Object buf; + + @Override + public boolean hasNext() { + buf = value; + return !NotificationLite.isComplete(buf); + } + + @Override + public T next() { + try { + // if hasNext wasn't called before calling next. + if (buf == null) { + buf = value; + } + if (NotificationLite.isComplete(buf)) { + throw new NoSuchElementException(); + } + if (NotificationLite.isError(buf)) { + throw ExceptionHelper.wrapOrThrow(NotificationLite.getError(buf)); + } + return NotificationLite.getValue(buf); + } + finally { + buf = null; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Read only iterator"); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableNext.java b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableNext.java new file mode 100755 index 0000000..33d7b65 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableNext.java @@ -0,0 +1,172 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.internal.util.*; +import io.reactivex.observers.DisposableObserver; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Returns an Iterable that blocks until the Observable emits another item, then returns that item. + *

+ * + * + * @param the value type + */ +public final class BlockingObservableNext implements Iterable { + + final ObservableSource source; + + public BlockingObservableNext(ObservableSource source) { + this.source = source; + } + + @Override + public Iterator iterator() { + NextObserver nextObserver = new NextObserver(); + return new NextIterator(source, nextObserver); + } + + // test needs to access the observer.waiting flag + static final class NextIterator implements Iterator { + + private final NextObserver observer; + private final ObservableSource items; + private T next; + private boolean hasNext = true; + private boolean isNextConsumed = true; + private Throwable error; + private boolean started; + + NextIterator(ObservableSource items, NextObserver observer) { + this.items = items; + this.observer = observer; + } + + @Override + public boolean hasNext() { + if (error != null) { + // If any error has already been thrown, throw it again. + throw ExceptionHelper.wrapOrThrow(error); + } + // Since an iterator should not be used in different thread, + // so we do not need any synchronization. + if (!hasNext) { + // the iterator has reached the end. + return false; + } + // next has not been used yet. + return !isNextConsumed || moveToNext(); + } + + private boolean moveToNext() { + if (!started) { + started = true; + // if not started, start now + observer.setWaiting(); + new ObservableMaterialize(items).subscribe(observer); + } + + Notification nextNotification; + + try { + nextNotification = observer.takeNext(); + } catch (InterruptedException e) { + observer.dispose(); + error = e; + throw ExceptionHelper.wrapOrThrow(e); + } + + if (nextNotification.isOnNext()) { + isNextConsumed = false; + next = nextNotification.getValue(); + return true; + } + // If an observable is completed or fails, + // hasNext() always return false. + hasNext = false; + if (nextNotification.isOnComplete()) { + return false; + } + error = nextNotification.getError(); + throw ExceptionHelper.wrapOrThrow(error); + } + + @Override + public T next() { + if (error != null) { + // If any error has already been thrown, throw it again. + throw ExceptionHelper.wrapOrThrow(error); + } + if (hasNext()) { + isNextConsumed = true; + return next; + } + else { + throw new NoSuchElementException("No more elements"); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Read only iterator"); + } + } + + static final class NextObserver extends DisposableObserver> { + private final BlockingQueue> buf = new ArrayBlockingQueue>(1); + final AtomicInteger waiting = new AtomicInteger(); + + @Override + public void onComplete() { + // ignore + } + + @Override + public void onError(Throwable e) { + RxJavaPlugins.onError(e); + } + + @Override + public void onNext(Notification args) { + + if (waiting.getAndSet(0) == 1 || !args.isOnNext()) { + Notification toOffer = args; + while (!buf.offer(toOffer)) { + Notification concurrentItem = buf.poll(); + + // in case if we won race condition with onComplete/onError method + if (concurrentItem != null && !concurrentItem.isOnNext()) { + toOffer = concurrentItem; + } + } + } + + } + + public Notification takeNext() throws InterruptedException { + setWaiting(); + BlockingHelper.verifyNonBlocking(); + return buf.take(); + } + void setWaiting() { + waiting.set(1); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java new file mode 100755 index 0000000..2349ce4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAll.java @@ -0,0 +1,107 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableAll extends AbstractObservableWithUpstream { + final Predicate predicate; + public ObservableAll(ObservableSource source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(Observer t) { + source.subscribe(new AllObserver(t, predicate)); + } + + static final class AllObserver implements Observer, Disposable { + final Observer downstream; + final Predicate predicate; + + Disposable upstream; + + boolean done; + + AllObserver(Observer actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + if (!b) { + done = true; + upstream.dispose(); + downstream.onNext(false); + downstream.onComplete(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onNext(true); + downstream.onComplete(); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java new file mode 100755 index 0000000..1fb7562 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAllSingle.java @@ -0,0 +1,113 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.FuseToObservable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableAllSingle extends Single implements FuseToObservable { + final ObservableSource source; + + final Predicate predicate; + public ObservableAllSingle(ObservableSource source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + protected void subscribeActual(SingleObserver t) { + source.subscribe(new AllObserver(t, predicate)); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableAll(source, predicate)); + } + + static final class AllObserver implements Observer, Disposable { + final SingleObserver downstream; + final Predicate predicate; + + Disposable upstream; + + boolean done; + + AllObserver(SingleObserver actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + if (!b) { + done = true; + upstream.dispose(); + downstream.onSuccess(false); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onSuccess(true); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java new file mode 100755 index 0000000..53068e7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java @@ -0,0 +1,204 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableAmb extends Observable { + final ObservableSource[] sources; + final Iterable> sourcesIterable; + + public ObservableAmb(ObservableSource[] sources, Iterable> sourcesIterable) { + this.sources = sources; + this.sourcesIterable = sourcesIterable; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribeActual(Observer observer) { + ObservableSource[] sources = this.sources; + int count = 0; + if (sources == null) { + sources = new ObservableSource[8]; + try { + for (ObservableSource p : sourcesIterable) { + if (p == null) { + EmptyDisposable.error(new NullPointerException("One of the sources is null"), observer); + return; + } + if (count == sources.length) { + ObservableSource[] b = new ObservableSource[count + (count >> 2)]; + System.arraycopy(sources, 0, b, 0, count); + sources = b; + } + sources[count++] = p; + } + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + } else { + count = sources.length; + } + + if (count == 0) { + EmptyDisposable.complete(observer); + return; + } else + if (count == 1) { + sources[0].subscribe(observer); + return; + } + + AmbCoordinator ac = new AmbCoordinator(observer, count); + ac.subscribe(sources); + } + + static final class AmbCoordinator implements Disposable { + final Observer downstream; + final AmbInnerObserver[] observers; + + final AtomicInteger winner = new AtomicInteger(); + + @SuppressWarnings("unchecked") + AmbCoordinator(Observer actual, int count) { + this.downstream = actual; + this.observers = new AmbInnerObserver[count]; + } + + public void subscribe(ObservableSource[] sources) { + AmbInnerObserver[] as = observers; + int len = as.length; + for (int i = 0; i < len; i++) { + as[i] = new AmbInnerObserver(this, i + 1, downstream); + } + winner.lazySet(0); // release the contents of 'as' + downstream.onSubscribe(this); + + for (int i = 0; i < len; i++) { + if (winner.get() != 0) { + return; + } + + sources[i].subscribe(as[i]); + } + } + + public boolean win(int index) { + int w = winner.get(); + if (w == 0) { + if (winner.compareAndSet(0, index)) { + AmbInnerObserver[] a = observers; + int n = a.length; + for (int i = 0; i < n; i++) { + if (i + 1 != index) { + a[i].dispose(); + } + } + return true; + } + return false; + } + return w == index; + } + + @Override + public void dispose() { + if (winner.get() != -1) { + winner.lazySet(-1); + + for (AmbInnerObserver a : observers) { + a.dispose(); + } + } + } + + @Override + public boolean isDisposed() { + return winner.get() == -1; + } + } + + static final class AmbInnerObserver extends AtomicReference implements Observer { + + private static final long serialVersionUID = -1185974347409665484L; + final AmbCoordinator parent; + final int index; + final Observer downstream; + + boolean won; + + AmbInnerObserver(AmbCoordinator parent, int index, Observer downstream) { + this.parent = parent; + this.index = index; + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(T t) { + if (won) { + downstream.onNext(t); + } else { + if (parent.win(index)) { + won = true; + downstream.onNext(t); + } else { + get().dispose(); + } + } + } + + @Override + public void onError(Throwable t) { + if (won) { + downstream.onError(t); + } else { + if (parent.win(index)) { + won = true; + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + } + + @Override + public void onComplete() { + if (won) { + downstream.onComplete(); + } else { + if (parent.win(index)) { + won = true; + downstream.onComplete(); + } + } + } + + public void dispose() { + DisposableHelper.dispose(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java new file mode 100755 index 0000000..c1500c3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAny.java @@ -0,0 +1,108 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableAny extends AbstractObservableWithUpstream { + final Predicate predicate; + public ObservableAny(ObservableSource source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + protected void subscribeActual(Observer t) { + source.subscribe(new AnyObserver(t, predicate)); + } + + static final class AnyObserver implements Observer, Disposable { + + final Observer downstream; + final Predicate predicate; + + Disposable upstream; + + boolean done; + + AnyObserver(Observer actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + if (b) { + done = true; + upstream.dispose(); + downstream.onNext(true); + downstream.onComplete(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onNext(false); + downstream.onComplete(); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java new file mode 100755 index 0000000..b8c7001 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAnySingle.java @@ -0,0 +1,115 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.FuseToObservable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableAnySingle extends Single implements FuseToObservable { + final ObservableSource source; + + final Predicate predicate; + + public ObservableAnySingle(ObservableSource source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + protected void subscribeActual(SingleObserver t) { + source.subscribe(new AnyObserver(t, predicate)); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableAny(source, predicate)); + } + + static final class AnyObserver implements Observer, Disposable { + + final SingleObserver downstream; + final Predicate predicate; + + Disposable upstream; + + boolean done; + + AnyObserver(SingleObserver actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + if (b) { + done = true; + upstream.dispose(); + downstream.onSuccess(true); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onSuccess(false); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAutoConnect.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAutoConnect.java new file mode 100755 index 0000000..ebb6a49 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAutoConnect.java @@ -0,0 +1,51 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; +import io.reactivex.observables.ConnectableObservable; + +/** + * Wraps a ConnectableObservable and calls its connect() method once + * the specified number of Observers have subscribed. + * + * @param the value type of the chain + */ +public final class ObservableAutoConnect extends Observable { + final ConnectableObservable source; + final int numberOfObservers; + final Consumer connection; + final AtomicInteger clients; + + public ObservableAutoConnect(ConnectableObservable source, + int numberOfObservers, + Consumer connection) { + this.source = source; + this.numberOfObservers = numberOfObservers; + this.connection = connection; + this.clients = new AtomicInteger(); + } + + @Override + public void subscribeActual(Observer child) { + source.subscribe(child); + if (clients.incrementAndGet() == numberOfObservers) { + source.connect(connection); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java new file mode 100755 index 0000000..589b71c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java @@ -0,0 +1,105 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.*; + +import io.reactivex.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.observers.*; +import io.reactivex.internal.util.*; + +/** + * Utility methods to consume an Observable in a blocking manner with callbacks or Observer. + */ +public final class ObservableBlockingSubscribe { + + /** Utility class. */ + private ObservableBlockingSubscribe() { + throw new IllegalStateException("No instances!"); + } + + /** + * Subscribes to the source and calls the Observer methods on the current thread. + *

+ * @param o the source ObservableSource + * The call to dispose() is composed through. + * @param observer the subscriber to forward events and calls to in the current thread + * @param the value type + */ + public static void subscribe(ObservableSource o, Observer observer) { + final BlockingQueue queue = new LinkedBlockingQueue(); + + BlockingObserver bs = new BlockingObserver(queue); + observer.onSubscribe(bs); + + o.subscribe(bs); + for (;;) { + if (bs.isDisposed()) { + break; + } + Object v = queue.poll(); + if (v == null) { + try { + v = queue.take(); + } catch (InterruptedException ex) { + bs.dispose(); + observer.onError(ex); + return; + } + } + if (bs.isDisposed() + || v == BlockingObserver.TERMINATED + || NotificationLite.acceptFull(v, observer)) { + break; + } + } + } + + /** + * Runs the source observable to a terminal event, ignoring any values and rethrowing any exception. + * @param o the source ObservableSource + * @param the value type + */ + public static void subscribe(ObservableSource o) { + BlockingIgnoringReceiver callback = new BlockingIgnoringReceiver(); + LambdaObserver ls = new LambdaObserver(Functions.emptyConsumer(), + callback, callback, Functions.emptyConsumer()); + + o.subscribe(ls); + + BlockingHelper.awaitForComplete(callback, ls); + Throwable e = callback.error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + } + + /** + * Subscribes to the source and calls the given actions on the current thread. + * @param o the source ObservableSource + * @param onNext the callback action for each source value + * @param onError the callback action for an error event + * @param onComplete the callback action for the completion event. + * @param the value type + */ + public static void subscribe(ObservableSource o, final Consumer onNext, + final Consumer onError, final Action onComplete) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + subscribe(o, new LambdaObserver(onNext, onError, onComplete, Functions.emptyConsumer())); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBuffer.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBuffer.java new file mode 100755 index 0000000..369eb42 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBuffer.java @@ -0,0 +1,224 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.*; + +public final class ObservableBuffer> extends AbstractObservableWithUpstream { + final int count; + final int skip; + final Callable bufferSupplier; + + public ObservableBuffer(ObservableSource source, int count, int skip, Callable bufferSupplier) { + super(source); + this.count = count; + this.skip = skip; + this.bufferSupplier = bufferSupplier; + } + + @Override + protected void subscribeActual(Observer t) { + if (skip == count) { + BufferExactObserver bes = new BufferExactObserver(t, count, bufferSupplier); + if (bes.createBuffer()) { + source.subscribe(bes); + } + } else { + source.subscribe(new BufferSkipObserver(t, count, skip, bufferSupplier)); + } + } + + static final class BufferExactObserver> implements Observer, Disposable { + final Observer downstream; + final int count; + final Callable bufferSupplier; + U buffer; + + int size; + + Disposable upstream; + + BufferExactObserver(Observer actual, int count, Callable bufferSupplier) { + this.downstream = actual; + this.count = count; + this.bufferSupplier = bufferSupplier; + } + + boolean createBuffer() { + U b; + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "Empty buffer supplied"); + } catch (Throwable t) { + Exceptions.throwIfFatal(t); + buffer = null; + if (upstream == null) { + EmptyDisposable.error(t, downstream); + } else { + upstream.dispose(); + downstream.onError(t); + } + return false; + } + + buffer = b; + + return true; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + U b = buffer; + if (b != null) { + b.add(t); + + if (++size >= count) { + downstream.onNext(b); + + size = 0; + createBuffer(); + } + } + } + + @Override + public void onError(Throwable t) { + buffer = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + U b = buffer; + if (b != null) { + buffer = null; + if (!b.isEmpty()) { + downstream.onNext(b); + } + downstream.onComplete(); + } + } + } + + static final class BufferSkipObserver> + extends AtomicBoolean implements Observer, Disposable { + + private static final long serialVersionUID = -8223395059921494546L; + final Observer downstream; + final int count; + final int skip; + final Callable bufferSupplier; + + Disposable upstream; + + final ArrayDeque buffers; + + long index; + + BufferSkipObserver(Observer actual, int count, int skip, Callable bufferSupplier) { + this.downstream = actual; + this.count = count; + this.skip = skip; + this.bufferSupplier = bufferSupplier; + this.buffers = new ArrayDeque(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (index++ % skip == 0) { + U b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable e) { + buffers.clear(); + upstream.dispose(); + downstream.onError(e); + return; + } + + buffers.offer(b); + } + + Iterator it = buffers.iterator(); + while (it.hasNext()) { + U b = it.next(); + b.add(t); + if (count <= b.size()) { + it.remove(); + + downstream.onNext(b); + } + } + } + + @Override + public void onError(Throwable t) { + buffers.clear(); + downstream.onError(t); + } + + @Override + public void onComplete() { + while (!buffers.isEmpty()) { + downstream.onNext(buffers.poll()); + } + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundary.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundary.java new file mode 100755 index 0000000..09f5b2d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundary.java @@ -0,0 +1,387 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableBufferBoundary, Open, Close> +extends AbstractObservableWithUpstream { + final Callable bufferSupplier; + final ObservableSource bufferOpen; + final Function> bufferClose; + + public ObservableBufferBoundary(ObservableSource source, ObservableSource bufferOpen, + Function> bufferClose, Callable bufferSupplier) { + super(source); + this.bufferOpen = bufferOpen; + this.bufferClose = bufferClose; + this.bufferSupplier = bufferSupplier; + } + + @Override + protected void subscribeActual(Observer t) { + BufferBoundaryObserver parent = + new BufferBoundaryObserver( + t, bufferOpen, bufferClose, bufferSupplier + ); + t.onSubscribe(parent); + source.subscribe(parent); + } + + static final class BufferBoundaryObserver, Open, Close> + extends AtomicInteger implements Observer, Disposable { + + private static final long serialVersionUID = -8466418554264089604L; + + final Observer downstream; + + final Callable bufferSupplier; + + final ObservableSource bufferOpen; + + final Function> bufferClose; + + final CompositeDisposable observers; + + final AtomicReference upstream; + + final AtomicThrowable errors; + + volatile boolean done; + + final SpscLinkedArrayQueue queue; + + volatile boolean cancelled; + + long index; + + Map buffers; + + BufferBoundaryObserver(Observer actual, + ObservableSource bufferOpen, + Function> bufferClose, + Callable bufferSupplier + ) { + this.downstream = actual; + this.bufferSupplier = bufferSupplier; + this.bufferOpen = bufferOpen; + this.bufferClose = bufferClose; + this.queue = new SpscLinkedArrayQueue(bufferSize()); + this.observers = new CompositeDisposable(); + this.upstream = new AtomicReference(); + this.buffers = new LinkedHashMap(); + this.errors = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this.upstream, d)) { + + BufferOpenObserver open = new BufferOpenObserver(this); + observers.add(open); + + bufferOpen.subscribe(open); + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + for (C b : bufs.values()) { + b.add(t); + } + } + } + + @Override + public void onError(Throwable t) { + if (errors.addThrowable(t)) { + observers.dispose(); + synchronized (this) { + buffers = null; + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + observers.dispose(); + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + for (C b : bufs.values()) { + queue.offer(b); + } + buffers = null; + } + done = true; + drain(); + } + + @Override + public void dispose() { + if (DisposableHelper.dispose(upstream)) { + cancelled = true; + observers.dispose(); + synchronized (this) { + buffers = null; + } + if (getAndIncrement() != 0) { + queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } + + void open(Open token) { + ObservableSource p; + C buf; + try { + buf = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null Collection"); + p = ObjectHelper.requireNonNull(bufferClose.apply(token), "The bufferClose returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + DisposableHelper.dispose(upstream); + onError(ex); + return; + } + + long idx = index; + index = idx + 1; + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + bufs.put(idx, buf); + } + + BufferCloseObserver bc = new BufferCloseObserver(this, idx); + observers.add(bc); + p.subscribe(bc); + } + + void openComplete(BufferOpenObserver os) { + observers.delete(os); + if (observers.size() == 0) { + DisposableHelper.dispose(upstream); + done = true; + drain(); + } + } + + void close(BufferCloseObserver closer, long idx) { + observers.delete(closer); + boolean makeDone = false; + if (observers.size() == 0) { + makeDone = true; + DisposableHelper.dispose(upstream); + } + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + queue.offer(buffers.remove(idx)); + } + if (makeDone) { + done = true; + } + drain(); + } + + void boundaryError(Disposable observer, Throwable ex) { + DisposableHelper.dispose(upstream); + observers.delete(observer); + onError(ex); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer a = downstream; + SpscLinkedArrayQueue q = queue; + + for (;;) { + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + if (d && errors.get() != null) { + q.clear(); + Throwable ex = errors.terminate(); + a.onError(ex); + return; + } + + C v = q.poll(); + boolean empty = v == null; + + if (d && empty) { + a.onComplete(); + return; + } + + if (empty) { + break; + } + + a.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class BufferOpenObserver + extends AtomicReference + implements Observer, Disposable { + + private static final long serialVersionUID = -8498650778633225126L; + + final BufferBoundaryObserver parent; + + BufferOpenObserver(BufferBoundaryObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Open t) { + parent.open(t); + } + + @Override + public void onError(Throwable t) { + lazySet(DisposableHelper.DISPOSED); + parent.boundaryError(this, t); + } + + @Override + public void onComplete() { + lazySet(DisposableHelper.DISPOSED); + parent.openComplete(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + } + } + + static final class BufferCloseObserver> + extends AtomicReference + implements Observer, Disposable { + + private static final long serialVersionUID = -8498650778633225126L; + + final BufferBoundaryObserver parent; + + final long index; + + BufferCloseObserver(BufferBoundaryObserver parent, long index) { + this.parent = parent; + this.index = index; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Object t) { + Disposable upstream = get(); + if (upstream != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + upstream.dispose(); + parent.close(this, index); + } + } + + @Override + public void onError(Throwable t) { + if (get() != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + parent.boundaryError(this, t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (get() != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + parent.close(this, index); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundarySupplier.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundarySupplier.java new file mode 100755 index 0000000..b642bf1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferBoundarySupplier.java @@ -0,0 +1,255 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.Collection; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.observers.QueueDrainObserver; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.util.QueueDrainHelper; +import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableBufferBoundarySupplier, B> +extends AbstractObservableWithUpstream { + final Callable> boundarySupplier; + final Callable bufferSupplier; + + public ObservableBufferBoundarySupplier(ObservableSource source, Callable> boundarySupplier, Callable bufferSupplier) { + super(source); + this.boundarySupplier = boundarySupplier; + this.bufferSupplier = bufferSupplier; + } + + @Override + protected void subscribeActual(Observer t) { + source.subscribe(new BufferBoundarySupplierObserver(new SerializedObserver(t), bufferSupplier, boundarySupplier)); + } + + static final class BufferBoundarySupplierObserver, B> + extends QueueDrainObserver implements Observer, Disposable { + + final Callable bufferSupplier; + final Callable> boundarySupplier; + + Disposable upstream; + + final AtomicReference other = new AtomicReference(); + + U buffer; + + BufferBoundarySupplierObserver(Observer actual, Callable bufferSupplier, + Callable> boundarySupplier) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.boundarySupplier = boundarySupplier; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + Observer actual = this.downstream; + + U b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancelled = true; + d.dispose(); + EmptyDisposable.error(e, actual); + return; + } + + buffer = b; + + ObservableSource boundary; + + try { + boundary = ObjectHelper.requireNonNull(boundarySupplier.call(), "The boundary ObservableSource supplied is null"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelled = true; + d.dispose(); + EmptyDisposable.error(ex, actual); + return; + } + + BufferBoundaryObserver bs = new BufferBoundaryObserver(this); + other.set(bs); + + actual.onSubscribe(this); + + if (!cancelled) { + boundary.subscribe(bs); + } + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + U b = buffer; + if (b == null) { + return; + } + b.add(t); + } + } + + @Override + public void onError(Throwable t) { + dispose(); + downstream.onError(t); + } + + @Override + public void onComplete() { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = null; + } + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainLoop(queue, downstream, false, this, this); + } + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + upstream.dispose(); + disposeOther(); + + if (enter()) { + queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void disposeOther() { + DisposableHelper.dispose(other); + } + + void next() { + + U next; + + try { + next = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + dispose(); + downstream.onError(e); + return; + } + + ObservableSource boundary; + + try { + boundary = ObjectHelper.requireNonNull(boundarySupplier.call(), "The boundary ObservableSource supplied is null"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelled = true; + upstream.dispose(); + downstream.onError(ex); + return; + } + + BufferBoundaryObserver bs = new BufferBoundaryObserver(this); + + if (DisposableHelper.replace(other, bs)) { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = next; + } + + boundary.subscribe(bs); + + fastPathEmit(b, false, this); + } + } + + @Override + public void accept(Observer a, U v) { + downstream.onNext(v); + } + + } + + static final class BufferBoundaryObserver, B> + extends DisposableObserver { + final BufferBoundarySupplierObserver parent; + + boolean once; + + BufferBoundaryObserver(BufferBoundarySupplierObserver parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + if (once) { + return; + } + once = true; + dispose(); + parent.next(); + } + + @Override + public void onError(Throwable t) { + if (once) { + RxJavaPlugins.onError(t); + return; + } + once = true; + parent.onError(t); + } + + @Override + public void onComplete() { + if (once) { + return; + } + once = true; + parent.next(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferExactBoundary.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferExactBoundary.java new file mode 100755 index 0000000..b80d303 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferExactBoundary.java @@ -0,0 +1,201 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.Collection; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.observers.QueueDrainObserver; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.util.QueueDrainHelper; +import io.reactivex.observers.*; + +public final class ObservableBufferExactBoundary, B> +extends AbstractObservableWithUpstream { + final ObservableSource boundary; + final Callable bufferSupplier; + + public ObservableBufferExactBoundary(ObservableSource source, ObservableSource boundary, Callable bufferSupplier) { + super(source); + this.boundary = boundary; + this.bufferSupplier = bufferSupplier; + } + + @Override + protected void subscribeActual(Observer t) { + source.subscribe(new BufferExactBoundaryObserver(new SerializedObserver(t), bufferSupplier, boundary)); + } + + static final class BufferExactBoundaryObserver, B> + extends QueueDrainObserver implements Observer, Disposable { + + final Callable bufferSupplier; + final ObservableSource boundary; + + Disposable upstream; + + Disposable other; + + U buffer; + + BufferExactBoundaryObserver(Observer actual, Callable bufferSupplier, + ObservableSource boundary) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.boundary = boundary; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + U b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + cancelled = true; + d.dispose(); + EmptyDisposable.error(e, downstream); + return; + } + + buffer = b; + + BufferBoundaryObserver bs = new BufferBoundaryObserver(this); + other = bs; + + downstream.onSubscribe(this); + + if (!cancelled) { + boundary.subscribe(bs); + } + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + U b = buffer; + if (b == null) { + return; + } + b.add(t); + } + } + + @Override + public void onError(Throwable t) { + dispose(); + downstream.onError(t); + } + + @Override + public void onComplete() { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = null; + } + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainLoop(queue, downstream, false, this, this); + } + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + other.dispose(); + upstream.dispose(); + + if (enter()) { + queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void next() { + + U next; + + try { + next = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + dispose(); + downstream.onError(e); + return; + } + + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + buffer = next; + } + + fastPathEmit(b, false, this); + } + + @Override + public void accept(Observer a, U v) { + downstream.onNext(v); + } + + } + + static final class BufferBoundaryObserver, B> + extends DisposableObserver { + final BufferExactBoundaryObserver parent; + + BufferBoundaryObserver(BufferExactBoundaryObserver parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + parent.next(); + } + + @Override + public void onError(Throwable t) { + parent.onError(t); + } + + @Override + public void onComplete() { + parent.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferTimed.java new file mode 100755 index 0000000..9a50e9c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBufferTimed.java @@ -0,0 +1,564 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.Observer; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.QueueDrainObserver; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.util.QueueDrainHelper; +import io.reactivex.observers.SerializedObserver; + +public final class ObservableBufferTimed> +extends AbstractObservableWithUpstream { + + final long timespan; + final long timeskip; + final TimeUnit unit; + final Scheduler scheduler; + final Callable bufferSupplier; + final int maxSize; + final boolean restartTimerOnMaxSize; + + public ObservableBufferTimed(ObservableSource source, long timespan, long timeskip, TimeUnit unit, Scheduler scheduler, Callable bufferSupplier, int maxSize, + boolean restartTimerOnMaxSize) { + super(source); + this.timespan = timespan; + this.timeskip = timeskip; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSupplier = bufferSupplier; + this.maxSize = maxSize; + this.restartTimerOnMaxSize = restartTimerOnMaxSize; + } + + @Override + protected void subscribeActual(Observer t) { + if (timespan == timeskip && maxSize == Integer.MAX_VALUE) { + source.subscribe(new BufferExactUnboundedObserver( + new SerializedObserver(t), + bufferSupplier, timespan, unit, scheduler)); + return; + } + Worker w = scheduler.createWorker(); + + if (timespan == timeskip) { + source.subscribe(new BufferExactBoundedObserver( + new SerializedObserver(t), + bufferSupplier, + timespan, unit, maxSize, restartTimerOnMaxSize, w + )); + return; + } + // Can't use maxSize because what to do if a buffer is full but its + // timespan hasn't been elapsed? + source.subscribe(new BufferSkipBoundedObserver( + new SerializedObserver(t), + bufferSupplier, timespan, timeskip, unit, w)); + + } + + static final class BufferExactUnboundedObserver> + extends QueueDrainObserver implements Runnable, Disposable { + final Callable bufferSupplier; + final long timespan; + final TimeUnit unit; + final Scheduler scheduler; + + Disposable upstream; + + U buffer; + + final AtomicReference timer = new AtomicReference(); + + BufferExactUnboundedObserver( + Observer actual, Callable bufferSupplier, + long timespan, TimeUnit unit, Scheduler scheduler) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.timespan = timespan; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + U b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + dispose(); + EmptyDisposable.error(e, downstream); + return; + } + + buffer = b; + + downstream.onSubscribe(this); + + if (!cancelled) { + Disposable task = scheduler.schedulePeriodicallyDirect(this, timespan, timespan, unit); + if (!timer.compareAndSet(null, task)) { + task.dispose(); + } + } + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + U b = buffer; + if (b == null) { + return; + } + b.add(t); + } + } + + @Override + public void onError(Throwable t) { + synchronized (this) { + buffer = null; + } + downstream.onError(t); + DisposableHelper.dispose(timer); + } + + @Override + public void onComplete() { + U b; + synchronized (this) { + b = buffer; + buffer = null; + } + if (b != null) { + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainLoop(queue, downstream, false, null, this); + } + } + DisposableHelper.dispose(timer); + } + + @Override + public void dispose() { + DisposableHelper.dispose(timer); + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return timer.get() == DisposableHelper.DISPOSED; + } + + @Override + public void run() { + U next; + + try { + next = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null buffer"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + dispose(); + return; + } + + U current; + + synchronized (this) { + current = buffer; + if (current != null) { + buffer = next; + } + } + + if (current == null) { + DisposableHelper.dispose(timer); + return; + } + + fastPathEmit(current, false, this); + } + + @Override + public void accept(Observer a, U v) { + downstream.onNext(v); + } + } + + static final class BufferSkipBoundedObserver> + extends QueueDrainObserver implements Runnable, Disposable { + final Callable bufferSupplier; + final long timespan; + final long timeskip; + final TimeUnit unit; + final Worker w; + final List buffers; + + Disposable upstream; + + BufferSkipBoundedObserver(Observer actual, + Callable bufferSupplier, long timespan, + long timeskip, TimeUnit unit, Worker w) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.timespan = timespan; + this.timeskip = timeskip; + this.unit = unit; + this.w = w; + this.buffers = new LinkedList(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + final U b; // NOPMD + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + d.dispose(); + EmptyDisposable.error(e, downstream); + w.dispose(); + return; + } + + buffers.add(b); + + downstream.onSubscribe(this); + + w.schedulePeriodically(this, timeskip, timeskip, unit); + + w.schedule(new RemoveFromBufferEmit(b), timespan, unit); + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + for (U b : buffers) { + b.add(t); + } + } + } + + @Override + public void onError(Throwable t) { + done = true; + clear(); + downstream.onError(t); + w.dispose(); + } + + @Override + public void onComplete() { + List bs; + synchronized (this) { + bs = new ArrayList(buffers); + buffers.clear(); + } + + for (U b : bs) { + queue.offer(b); + } + done = true; + if (enter()) { + QueueDrainHelper.drainLoop(queue, downstream, false, w, this); + } + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + clear(); + upstream.dispose(); + w.dispose(); + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void clear() { + synchronized (this) { + buffers.clear(); + } + } + + @Override + public void run() { + if (cancelled) { + return; + } + final U b; // NOPMD + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null buffer"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + dispose(); + return; + } + + synchronized (this) { + if (cancelled) { + return; + } + buffers.add(b); + } + + w.schedule(new RemoveFromBuffer(b), timespan, unit); + } + + @Override + public void accept(Observer a, U v) { + a.onNext(v); + } + + final class RemoveFromBuffer implements Runnable { + private final U b; + + RemoveFromBuffer(U b) { + this.b = b; + } + + @Override + public void run() { + synchronized (BufferSkipBoundedObserver.this) { + buffers.remove(b); + } + + fastPathOrderedEmit(b, false, w); + } + } + + final class RemoveFromBufferEmit implements Runnable { + private final U buffer; + + RemoveFromBufferEmit(U buffer) { + this.buffer = buffer; + } + + @Override + public void run() { + synchronized (BufferSkipBoundedObserver.this) { + buffers.remove(buffer); + } + + fastPathOrderedEmit(buffer, false, w); + } + } + } + + static final class BufferExactBoundedObserver> + extends QueueDrainObserver implements Runnable, Disposable { + final Callable bufferSupplier; + final long timespan; + final TimeUnit unit; + final int maxSize; + final boolean restartTimerOnMaxSize; + final Worker w; + + U buffer; + + Disposable timer; + + Disposable upstream; + + long producerIndex; + + long consumerIndex; + + BufferExactBoundedObserver( + Observer actual, + Callable bufferSupplier, + long timespan, TimeUnit unit, int maxSize, + boolean restartOnMaxSize, Worker w) { + super(actual, new MpscLinkedQueue()); + this.bufferSupplier = bufferSupplier; + this.timespan = timespan; + this.unit = unit; + this.maxSize = maxSize; + this.restartTimerOnMaxSize = restartOnMaxSize; + this.w = w; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + U b; + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + d.dispose(); + EmptyDisposable.error(e, downstream); + w.dispose(); + return; + } + + buffer = b; + + downstream.onSubscribe(this); + + timer = w.schedulePeriodically(this, timespan, timespan, unit); + } + } + + @Override + public void onNext(T t) { + U b; + synchronized (this) { + b = buffer; + if (b == null) { + return; + } + + b.add(t); + + if (b.size() < maxSize) { + return; + } + buffer = null; + producerIndex++; + } + + if (restartTimerOnMaxSize) { + timer.dispose(); + } + + fastPathOrderedEmit(b, false, this); + + try { + b = ObjectHelper.requireNonNull(bufferSupplier.call(), "The buffer supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + dispose(); + return; + } + + synchronized (this) { + buffer = b; + consumerIndex++; + } + if (restartTimerOnMaxSize) { + timer = w.schedulePeriodically(this, timespan, timespan, unit); + } + } + + @Override + public void onError(Throwable t) { + synchronized (this) { + buffer = null; + } + downstream.onError(t); + w.dispose(); + } + + @Override + public void onComplete() { + w.dispose(); + + U b; + synchronized (this) { + b = buffer; + buffer = null; + } + + if (b != null) { + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainLoop(queue, downstream, false, this, this); + } + } + } + + @Override + public void accept(Observer a, U v) { + a.onNext(v); + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + upstream.dispose(); + w.dispose(); + synchronized (this) { + buffer = null; + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public void run() { + U next; + + try { + next = ObjectHelper.requireNonNull(bufferSupplier.call(), "The bufferSupplier returned a null buffer"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + dispose(); + downstream.onError(e); + return; + } + + U current; + + synchronized (this) { + current = buffer; + if (current == null || producerIndex != consumerIndex) { + return; + } + buffer = next; + } + + fastPathOrderedEmit(current, false, this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java new file mode 100755 index 0000000..fdc3477 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCache.java @@ -0,0 +1,399 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; + +/** + * An observable which auto-connects to another observable, caches the elements + * from that observable but allows terminating the connection and completing the cache. + * + * @param the source element type + */ +public final class ObservableCache extends AbstractObservableWithUpstream +implements Observer { + + /** + * The subscription to the source should happen at most once. + */ + final AtomicBoolean once; + + /** + * The number of items per cached nodes. + */ + final int capacityHint; + + /** + * The current known array of observer state to notify. + */ + final AtomicReference[]> observers; + + /** + * A shared instance of an empty array of observers to avoid creating + * a new empty array when all observers dispose. + */ + @SuppressWarnings("rawtypes") + static final CacheDisposable[] EMPTY = new CacheDisposable[0]; + /** + * A shared instance indicating the source has no more events and there + * is no need to remember observers anymore. + */ + @SuppressWarnings("rawtypes") + static final CacheDisposable[] TERMINATED = new CacheDisposable[0]; + + /** + * The total number of elements in the list available for reads. + */ + volatile long size; + + /** + * The starting point of the cached items. + */ + final Node head; + + /** + * The current tail of the linked structure holding the items. + */ + Node tail; + + /** + * How many items have been put into the tail node so far. + */ + int tailOffset; + + /** + * If {@link #observers} is {@link #TERMINATED}, this holds the terminal error if not null. + */ + Throwable error; + + /** + * True if the source has terminated. + */ + volatile boolean done; + + /** + * Constructs an empty, non-connected cache. + * @param source the source to subscribe to for the first incoming observer + * @param capacityHint the number of items expected (reduce allocation frequency) + */ + @SuppressWarnings("unchecked") + public ObservableCache(Observable source, int capacityHint) { + super(source); + this.capacityHint = capacityHint; + this.once = new AtomicBoolean(); + Node n = new Node(capacityHint); + this.head = n; + this.tail = n; + this.observers = new AtomicReference[]>(EMPTY); + } + + @Override + protected void subscribeActual(Observer t) { + CacheDisposable consumer = new CacheDisposable(t, this); + t.onSubscribe(consumer); + add(consumer); + + if (!once.get() && once.compareAndSet(false, true)) { + source.subscribe(this); + } else { + replay(consumer); + } + } + + /** + * Check if this cached observable is connected to its source. + * @return true if already connected + */ + /* public */boolean isConnected() { + return once.get(); + } + + /** + * Returns true if there are observers subscribed to this observable. + * @return true if the cache has observers + */ + /* public */ boolean hasObservers() { + return observers.get().length != 0; + } + + /** + * Returns the number of events currently cached. + * @return the number of currently cached event count + */ + /* public */ long cachedEventCount() { + return size; + } + + /** + * Atomically adds the consumer to the {@link #observers} copy-on-write array + * if the source has not yet terminated. + * @param consumer the consumer to add + */ + void add(CacheDisposable consumer) { + for (;;) { + CacheDisposable[] current = observers.get(); + if (current == TERMINATED) { + return; + } + int n = current.length; + + @SuppressWarnings("unchecked") + CacheDisposable[] next = new CacheDisposable[n + 1]; + System.arraycopy(current, 0, next, 0, n); + next[n] = consumer; + + if (observers.compareAndSet(current, next)) { + return; + } + } + } + + /** + * Atomically removes the consumer from the {@link #observers} copy-on-write array. + * @param consumer the consumer to remove + */ + @SuppressWarnings("unchecked") + void remove(CacheDisposable consumer) { + for (;;) { + CacheDisposable[] current = observers.get(); + int n = current.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (current[i] == consumer) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + CacheDisposable[] next; + + if (n == 1) { + next = EMPTY; + } else { + next = new CacheDisposable[n - 1]; + System.arraycopy(current, 0, next, 0, j); + System.arraycopy(current, j + 1, next, j, n - j - 1); + } + + if (observers.compareAndSet(current, next)) { + return; + } + } + } + + /** + * Replays the contents of this cache to the given consumer based on its + * current state and number of items requested by it. + * @param consumer the consumer to continue replaying items to + */ + void replay(CacheDisposable consumer) { + // make sure there is only one replay going on at a time + if (consumer.getAndIncrement() != 0) { + return; + } + + // see if there were more replay request in the meantime + int missed = 1; + // read out state into locals upfront to avoid being re-read due to volatile reads + long index = consumer.index; + int offset = consumer.offset; + Node node = consumer.node; + Observer downstream = consumer.downstream; + int capacity = capacityHint; + + for (;;) { + // if the consumer got disposed, clear the node and quit + if (consumer.disposed) { + consumer.node = null; + return; + } + + // first see if the source has terminated, read order matters! + boolean sourceDone = done; + // and if the number of items is the same as this consumer has received + boolean empty = size == index; + + // if the source is done and we have all items so far, terminate the consumer + if (sourceDone && empty) { + // release the node object to avoid leaks through retained consumers + consumer.node = null; + // if error is not null then the source failed + Throwable ex = error; + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + + // there are still items not sent to the consumer + if (!empty) { + // if the offset in the current node has reached the node capacity + if (offset == capacity) { + // switch to the subsequent node + node = node.next; + // reset the in-node offset + offset = 0; + } + + // emit the cached item + downstream.onNext(node.values[offset]); + + // move the node offset forward + offset++; + // move the total consumed item count forward + index++; + + // retry for the next item/terminal event if any + continue; + } + + // commit the changed references back + consumer.index = index; + consumer.offset = offset; + consumer.node = node; + // release the changes and see if there were more replay request in the meantime + missed = consumer.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void onSubscribe(Disposable d) { + // we can't do much with the upstream disposable + } + + @Override + public void onNext(T t) { + int tailOffset = this.tailOffset; + // if the current tail node is full, create a fresh node + if (tailOffset == capacityHint) { + Node n = new Node(tailOffset); + n.values[0] = t; + this.tailOffset = 1; + tail.next = n; + tail = n; + } else { + tail.values[tailOffset] = t; + this.tailOffset = tailOffset + 1; + } + size++; + for (CacheDisposable consumer : observers.get()) { + replay(consumer); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable t) { + error = t; + done = true; + for (CacheDisposable consumer : observers.getAndSet(TERMINATED)) { + replay(consumer); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + done = true; + for (CacheDisposable consumer : observers.getAndSet(TERMINATED)) { + replay(consumer); + } + } + + /** + * Hosts the downstream consumer and its current requested and replay states. + * {@code this} holds the work-in-progress counter for the serialized replay. + * @param the value type + */ + static final class CacheDisposable extends AtomicInteger + implements Disposable { + + private static final long serialVersionUID = 6770240836423125754L; + + final Observer downstream; + + final ObservableCache parent; + + Node node; + + int offset; + + long index; + + volatile boolean disposed; + + /** + * Constructs a new instance with the actual downstream consumer and + * the parent cache object. + * @param downstream the actual consumer + * @param parent the parent that holds onto the cached items + */ + CacheDisposable(Observer downstream, ObservableCache parent) { + this.downstream = downstream; + this.parent = parent; + this.node = parent.head; + } + + @Override + public void dispose() { + if (!disposed) { + disposed = true; + parent.remove(this); + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + } + + /** + * Represents a segment of the cached item list as + * part of a linked-node-list structure. + * @param the element type + */ + static final class Node { + + /** + * The array of values held by this node. + */ + final T[] values; + + /** + * The next node if not null. + */ + volatile Node next; + + @SuppressWarnings("unchecked") + Node(int capacityHint) { + this.values = (T[])new Object[capacityHint]; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java new file mode 100755 index 0000000..761fabd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollect.java @@ -0,0 +1,115 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.BiConsumer; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableCollect extends AbstractObservableWithUpstream { + final Callable initialSupplier; + final BiConsumer collector; + + public ObservableCollect(ObservableSource source, + Callable initialSupplier, BiConsumer collector) { + super(source); + this.initialSupplier = initialSupplier; + this.collector = collector; + } + + @Override + protected void subscribeActual(Observer t) { + U u; + try { + u = ObjectHelper.requireNonNull(initialSupplier.call(), "The initialSupplier returned a null value"); + } catch (Throwable e) { + EmptyDisposable.error(e, t); + return; + } + + source.subscribe(new CollectObserver(t, u, collector)); + + } + + static final class CollectObserver implements Observer, Disposable { + final Observer downstream; + final BiConsumer collector; + final U u; + + Disposable upstream; + + boolean done; + + CollectObserver(Observer actual, U u, BiConsumer collector) { + this.downstream = actual; + this.collector = collector; + this.u = u; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + collector.accept(u, t); + } catch (Throwable e) { + upstream.dispose(); + onError(e); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onNext(u); + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java new file mode 100755 index 0000000..59c34a0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCollectSingle.java @@ -0,0 +1,122 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.BiConsumer; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.FuseToObservable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableCollectSingle extends Single implements FuseToObservable { + + final ObservableSource source; + + final Callable initialSupplier; + final BiConsumer collector; + + public ObservableCollectSingle(ObservableSource source, + Callable initialSupplier, BiConsumer collector) { + this.source = source; + this.initialSupplier = initialSupplier; + this.collector = collector; + } + + @Override + protected void subscribeActual(SingleObserver t) { + U u; + try { + u = ObjectHelper.requireNonNull(initialSupplier.call(), "The initialSupplier returned a null value"); + } catch (Throwable e) { + EmptyDisposable.error(e, t); + return; + } + + source.subscribe(new CollectObserver(t, u, collector)); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableCollect(source, initialSupplier, collector)); + } + + static final class CollectObserver implements Observer, Disposable { + final SingleObserver downstream; + final BiConsumer collector; + final U u; + + Disposable upstream; + + boolean done; + + CollectObserver(SingleObserver actual, U u, BiConsumer collector) { + this.downstream = actual; + this.collector = collector; + this.u = u; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + collector.accept(u, t); + } catch (Throwable e) { + upstream.dispose(); + onError(e); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onSuccess(u); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java new file mode 100755 index 0000000..56b62cd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java @@ -0,0 +1,323 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableCombineLatest extends Observable { + final ObservableSource[] sources; + final Iterable> sourcesIterable; + final Function combiner; + final int bufferSize; + final boolean delayError; + + public ObservableCombineLatest(ObservableSource[] sources, + Iterable> sourcesIterable, + Function combiner, int bufferSize, + boolean delayError) { + this.sources = sources; + this.sourcesIterable = sourcesIterable; + this.combiner = combiner; + this.bufferSize = bufferSize; + this.delayError = delayError; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribeActual(Observer observer) { + ObservableSource[] sources = this.sources; + int count = 0; + if (sources == null) { + sources = new ObservableSource[8]; + for (ObservableSource p : sourcesIterable) { + if (count == sources.length) { + ObservableSource[] b = new ObservableSource[count + (count >> 2)]; + System.arraycopy(sources, 0, b, 0, count); + sources = b; + } + sources[count++] = p; + } + } else { + count = sources.length; + } + + if (count == 0) { + EmptyDisposable.complete(observer); + return; + } + + LatestCoordinator lc = new LatestCoordinator(observer, combiner, count, bufferSize, delayError); + lc.subscribe(sources); + } + + static final class LatestCoordinator extends AtomicInteger implements Disposable { + + private static final long serialVersionUID = 8567835998786448817L; + final Observer downstream; + final Function combiner; + final CombinerObserver[] observers; + Object[] latest; + final SpscLinkedArrayQueue queue; + final boolean delayError; + + volatile boolean cancelled; + + volatile boolean done; + + final AtomicThrowable errors = new AtomicThrowable(); + + int active; + int complete; + + @SuppressWarnings("unchecked") + LatestCoordinator(Observer actual, + Function combiner, + int count, int bufferSize, boolean delayError) { + this.downstream = actual; + this.combiner = combiner; + this.delayError = delayError; + this.latest = new Object[count]; + CombinerObserver[] as = new CombinerObserver[count]; + for (int i = 0; i < count; i++) { + as[i] = new CombinerObserver(this, i); + } + this.observers = as; + this.queue = new SpscLinkedArrayQueue(bufferSize); + } + + public void subscribe(ObservableSource[] sources) { + Observer[] as = observers; + int len = as.length; + downstream.onSubscribe(this); + for (int i = 0; i < len; i++) { + if (done || cancelled) { + return; + } + sources[i].subscribe(as[i]); + } + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + cancelSources(); + if (getAndIncrement() == 0) { + clear(queue); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void cancelSources() { + for (CombinerObserver observer : observers) { + observer.dispose(); + } + } + + void clear(SpscLinkedArrayQueue q) { + synchronized (this) { + latest = null; + } + q.clear(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + final SpscLinkedArrayQueue q = queue; + final Observer a = downstream; + final boolean delayError = this.delayError; + + int missed = 1; + for (;;) { + + for (;;) { + if (cancelled) { + clear(q); + return; + } + + if (!delayError && errors.get() != null) { + cancelSources(); + clear(q); + a.onError(errors.terminate()); + return; + } + + boolean d = done; + Object[] s = q.poll(); + boolean empty = s == null; + + if (d && empty) { + clear(q); + Throwable ex = errors.terminate(); + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; + } + + if (empty) { + break; + } + + R v; + + try { + v = ObjectHelper.requireNonNull(combiner.apply(s), "The combiner returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errors.addThrowable(ex); + cancelSources(); + clear(q); + ex = errors.terminate(); + a.onError(ex); + return; + } + + a.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void innerNext(int index, T item) { + boolean shouldDrain = false; + synchronized (this) { + Object[] latest = this.latest; + if (latest == null) { + return; + } + Object o = latest[index]; + int a = active; + if (o == null) { + active = ++a; + } + latest[index] = item; + if (a == latest.length) { + queue.offer(latest.clone()); + shouldDrain = true; + } + } + if (shouldDrain) { + drain(); + } + } + + void innerError(int index, Throwable ex) { + if (errors.addThrowable(ex)) { + boolean cancelOthers = true; + if (delayError) { + synchronized (this) { + Object[] latest = this.latest; + if (latest == null) { + return; + } + + cancelOthers = latest[index] == null; + if (cancelOthers || ++complete == latest.length) { + done = true; + } + } + } + if (cancelOthers) { + cancelSources(); + } + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void innerComplete(int index) { + boolean cancelOthers = false; + synchronized (this) { + Object[] latest = this.latest; + if (latest == null) { + return; + } + + cancelOthers = latest[index] == null; + if (cancelOthers || ++complete == latest.length) { + done = true; + } + } + if (cancelOthers) { + cancelSources(); + } + drain(); + } + + } + + static final class CombinerObserver extends AtomicReference implements Observer { + private static final long serialVersionUID = -4823716997131257941L; + + final LatestCoordinator parent; + + final int index; + + CombinerObserver(LatestCoordinator parent, int index) { + this.parent = parent; + this.index = index; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(T t) { + parent.innerNext(index, t); + } + + @Override + public void onError(Throwable t) { + parent.innerError(index, t); + } + + @Override + public void onComplete() { + parent.innerComplete(index); + } + + public void dispose() { + DisposableHelper.dispose(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java new file mode 100755 index 0000000..a59841d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMap.java @@ -0,0 +1,535 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.*; +import io.reactivex.observers.SerializedObserver; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableConcatMap extends AbstractObservableWithUpstream { + final Function> mapper; + final int bufferSize; + + final ErrorMode delayErrors; + + public ObservableConcatMap(ObservableSource source, Function> mapper, + int bufferSize, ErrorMode delayErrors) { + super(source); + this.mapper = mapper; + this.delayErrors = delayErrors; + this.bufferSize = Math.max(8, bufferSize); + } + + @Override + public void subscribeActual(Observer observer) { + + if (ObservableScalarXMap.tryScalarXMapSubscribe(source, observer, mapper)) { + return; + } + + if (delayErrors == ErrorMode.IMMEDIATE) { + SerializedObserver serial = new SerializedObserver(observer); + source.subscribe(new SourceObserver(serial, mapper, bufferSize)); + } else { + source.subscribe(new ConcatMapDelayErrorObserver(observer, mapper, bufferSize, delayErrors == ErrorMode.END)); + } + } + + static final class SourceObserver extends AtomicInteger implements Observer, Disposable { + + private static final long serialVersionUID = 8828587559905699186L; + final Observer downstream; + final Function> mapper; + final InnerObserver inner; + final int bufferSize; + + SimpleQueue queue; + + Disposable upstream; + + volatile boolean active; + + volatile boolean disposed; + + volatile boolean done; + + int fusionMode; + + SourceObserver(Observer actual, + Function> mapper, int bufferSize) { + this.downstream = actual; + this.mapper = mapper; + this.bufferSize = bufferSize; + this.inner = new InnerObserver(actual, this); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + if (d instanceof QueueDisposable) { + @SuppressWarnings("unchecked") + QueueDisposable qd = (QueueDisposable) d; + + int m = qd.requestFusion(QueueDisposable.ANY); + if (m == QueueDisposable.SYNC) { + fusionMode = m; + queue = qd; + done = true; + + downstream.onSubscribe(this); + + drain(); + return; + } + + if (m == QueueDisposable.ASYNC) { + fusionMode = m; + queue = qd; + + downstream.onSubscribe(this); + + return; + } + } + + queue = new SpscLinkedArrayQueue(bufferSize); + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (fusionMode == QueueDisposable.NONE) { + queue.offer(t); + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + dispose(); + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + drain(); + } + + void innerComplete() { + active = false; + drain(); + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void dispose() { + disposed = true; + inner.dispose(); + upstream.dispose(); + + if (getAndIncrement() == 0) { + queue.clear(); + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + for (;;) { + if (disposed) { + queue.clear(); + return; + } + if (!active) { + + boolean d = done; + + T t; + + try { + t = queue.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + dispose(); + queue.clear(); + downstream.onError(ex); + return; + } + + boolean empty = t == null; + + if (d && empty) { + disposed = true; + downstream.onComplete(); + return; + } + + if (!empty) { + ObservableSource o; + + try { + o = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + dispose(); + queue.clear(); + downstream.onError(ex); + return; + } + + active = true; + o.subscribe(inner); + } + } + + if (decrementAndGet() == 0) { + break; + } + } + } + + static final class InnerObserver extends AtomicReference implements Observer { + + private static final long serialVersionUID = -7449079488798789337L; + + final Observer downstream; + final SourceObserver parent; + + InnerObserver(Observer actual, SourceObserver parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onNext(U t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + parent.dispose(); + downstream.onError(t); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } + + static final class ConcatMapDelayErrorObserver + extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = -6951100001833242599L; + + final Observer downstream; + + final Function> mapper; + + final int bufferSize; + + final AtomicThrowable error; + + final DelayErrorInnerObserver observer; + + final boolean tillTheEnd; + + SimpleQueue queue; + + Disposable upstream; + + volatile boolean active; + + volatile boolean done; + + volatile boolean cancelled; + + int sourceMode; + + ConcatMapDelayErrorObserver(Observer actual, + Function> mapper, int bufferSize, + boolean tillTheEnd) { + this.downstream = actual; + this.mapper = mapper; + this.bufferSize = bufferSize; + this.tillTheEnd = tillTheEnd; + this.error = new AtomicThrowable(); + this.observer = new DelayErrorInnerObserver(actual, this); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + if (d instanceof QueueDisposable) { + @SuppressWarnings("unchecked") + QueueDisposable qd = (QueueDisposable) d; + + int m = qd.requestFusion(QueueDisposable.ANY); + if (m == QueueDisposable.SYNC) { + sourceMode = m; + queue = qd; + done = true; + + downstream.onSubscribe(this); + + drain(); + return; + } + if (m == QueueDisposable.ASYNC) { + sourceMode = m; + queue = qd; + + downstream.onSubscribe(this); + + return; + } + } + + queue = new SpscLinkedArrayQueue(bufferSize); + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T value) { + if (sourceMode == QueueDisposable.NONE) { + queue.offer(value); + } + drain(); + } + + @Override + public void onError(Throwable e) { + if (error.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + observer.dispose(); + } + + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + + Observer actual = this.downstream; + SimpleQueue queue = this.queue; + AtomicThrowable error = this.error; + + for (;;) { + + if (!active) { + + if (cancelled) { + queue.clear(); + return; + } + + if (!tillTheEnd) { + Throwable ex = error.get(); + if (ex != null) { + queue.clear(); + cancelled = true; + actual.onError(error.terminate()); + return; + } + } + + boolean d = done; + + T v; + + try { + v = queue.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelled = true; + this.upstream.dispose(); + error.addThrowable(ex); + actual.onError(error.terminate()); + return; + } + + boolean empty = v == null; + + if (d && empty) { + cancelled = true; + Throwable ex = error.terminate(); + if (ex != null) { + actual.onError(ex); + } else { + actual.onComplete(); + } + return; + } + + if (!empty) { + + ObservableSource o; + + try { + o = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancelled = true; + this.upstream.dispose(); + queue.clear(); + error.addThrowable(ex); + actual.onError(error.terminate()); + return; + } + + if (o instanceof Callable) { + R w; + + try { + w = ((Callable)o).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + error.addThrowable(ex); + continue; + } + + if (w != null && !cancelled) { + actual.onNext(w); + } + continue; + } else { + active = true; + o.subscribe(observer); + } + } + } + + if (decrementAndGet() == 0) { + break; + } + } + } + + static final class DelayErrorInnerObserver extends AtomicReference implements Observer { + + private static final long serialVersionUID = 2620149119579502636L; + + final Observer downstream; + + final ConcatMapDelayErrorObserver parent; + + DelayErrorInnerObserver(Observer actual, ConcatMapDelayErrorObserver parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onNext(R value) { + downstream.onNext(value); + } + + @Override + public void onError(Throwable e) { + ConcatMapDelayErrorObserver p = parent; + if (p.error.addThrowable(e)) { + if (!p.tillTheEnd) { + p.upstream.dispose(); + } + p.active = false; + p.drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + ConcatMapDelayErrorObserver p = parent; + p.active = false; + p.drain(); + } + + void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java new file mode 100755 index 0000000..7028fdc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatMapEager.java @@ -0,0 +1,415 @@ +/** + * 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.internal.operators.observable; + +import java.util.ArrayDeque; +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.observers.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableConcatMapEager extends AbstractObservableWithUpstream { + + final Function> mapper; + + final ErrorMode errorMode; + + final int maxConcurrency; + + final int prefetch; + + public ObservableConcatMapEager(ObservableSource source, + Function> mapper, + ErrorMode errorMode, + int maxConcurrency, int prefetch) { + super(source); + this.mapper = mapper; + this.errorMode = errorMode; + this.maxConcurrency = maxConcurrency; + this.prefetch = prefetch; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new ConcatMapEagerMainObserver(observer, mapper, maxConcurrency, prefetch, errorMode)); + } + + static final class ConcatMapEagerMainObserver + extends AtomicInteger + implements Observer, Disposable, InnerQueuedObserverSupport { + + private static final long serialVersionUID = 8080567949447303262L; + + final Observer downstream; + + final Function> mapper; + + final int maxConcurrency; + + final int prefetch; + + final ErrorMode errorMode; + + final AtomicThrowable error; + + final ArrayDeque> observers; + + SimpleQueue queue; + + Disposable upstream; + + volatile boolean done; + + int sourceMode; + + volatile boolean cancelled; + + InnerQueuedObserver current; + + int activeCount; + + ConcatMapEagerMainObserver(Observer actual, + Function> mapper, + int maxConcurrency, int prefetch, ErrorMode errorMode) { + this.downstream = actual; + this.mapper = mapper; + this.maxConcurrency = maxConcurrency; + this.prefetch = prefetch; + this.errorMode = errorMode; + this.error = new AtomicThrowable(); + this.observers = new ArrayDeque>(); + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + if (d instanceof QueueDisposable) { + QueueDisposable qd = (QueueDisposable) d; + + int m = qd.requestFusion(QueueDisposable.ANY); + if (m == QueueDisposable.SYNC) { + sourceMode = m; + queue = qd; + done = true; + + downstream.onSubscribe(this); + + drain(); + return; + } + if (m == QueueDisposable.ASYNC) { + sourceMode = m; + queue = qd; + + downstream.onSubscribe(this); + + return; + } + } + + queue = new SpscLinkedArrayQueue(prefetch); + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T value) { + if (sourceMode == QueueDisposable.NONE) { + queue.offer(value); + } + drain(); + } + + @Override + public void onError(Throwable e) { + if (error.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + if (cancelled) { + return; + } + cancelled = true; + upstream.dispose(); + + drainAndDispose(); + } + + void drainAndDispose() { + if (getAndIncrement() == 0) { + do { + queue.clear(); + disposeAll(); + } while (decrementAndGet() != 0); + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void disposeAll() { + InnerQueuedObserver inner = current; + + if (inner != null) { + inner.dispose(); + } + + for (;;) { + + inner = observers.poll(); + + if (inner == null) { + return; + } + + inner.dispose(); + } + } + + @Override + public void innerNext(InnerQueuedObserver inner, R value) { + inner.queue().offer(value); + drain(); + } + + @Override + public void innerError(InnerQueuedObserver inner, Throwable e) { + if (error.addThrowable(e)) { + if (errorMode == ErrorMode.IMMEDIATE) { + upstream.dispose(); + } + inner.setDone(); + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void innerComplete(InnerQueuedObserver inner) { + inner.setDone(); + drain(); + } + + @Override + public void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + SimpleQueue q = queue; + ArrayDeque> observers = this.observers; + Observer a = this.downstream; + ErrorMode errorMode = this.errorMode; + + outer: + for (;;) { + + int ac = activeCount; + + while (ac != maxConcurrency) { + if (cancelled) { + q.clear(); + disposeAll(); + return; + } + + if (errorMode == ErrorMode.IMMEDIATE) { + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + disposeAll(); + + a.onError(error.terminate()); + return; + } + } + + T v; + ObservableSource source; + + try { + v = q.poll(); + + if (v == null) { + break; + } + + source = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + q.clear(); + disposeAll(); + error.addThrowable(ex); + a.onError(error.terminate()); + return; + } + + InnerQueuedObserver inner = new InnerQueuedObserver(this, prefetch); + + observers.offer(inner); + + source.subscribe(inner); + + ac++; + } + + activeCount = ac; + + if (cancelled) { + q.clear(); + disposeAll(); + return; + } + + if (errorMode == ErrorMode.IMMEDIATE) { + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + disposeAll(); + + a.onError(error.terminate()); + return; + } + } + + InnerQueuedObserver active = current; + + if (active == null) { + if (errorMode == ErrorMode.BOUNDARY) { + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + disposeAll(); + + a.onError(error.terminate()); + return; + } + } + boolean d = done; + + active = observers.poll(); + + boolean empty = active == null; + + if (d && empty) { + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + disposeAll(); + + a.onError(error.terminate()); + } else { + a.onComplete(); + } + return; + } + + if (!empty) { + current = active; + } + + } + + if (active != null) { + SimpleQueue aq = active.queue(); + + for (;;) { + if (cancelled) { + q.clear(); + disposeAll(); + return; + } + + boolean d = active.isDone(); + + if (errorMode == ErrorMode.IMMEDIATE) { + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + disposeAll(); + + a.onError(error.terminate()); + return; + } + } + + R w; + + try { + w = aq.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + error.addThrowable(ex); + + current = null; + activeCount--; + continue outer; + } + + boolean empty = w == null; + + if (d && empty) { + current = null; + activeCount--; + continue outer; + } + + if (empty) { + break; + } + + a.onNext(w); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithCompletable.java new file mode 100755 index 0000000..5609455 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithCompletable.java @@ -0,0 +1,100 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Subscribe to a main Observable first, then when it completes normally, subscribe to a Single, + * signal its success value followed by a completion or signal its error as is. + *

History: 2.1.10 - experimental + * @param the element type of the main source and output type + * @since 2.2 + */ +public final class ObservableConcatWithCompletable extends AbstractObservableWithUpstream { + + final CompletableSource other; + + public ObservableConcatWithCompletable(Observable source, CompletableSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new ConcatWithObserver(observer, other)); + } + + static final class ConcatWithObserver + extends AtomicReference + implements Observer, CompletableObserver, Disposable { + + private static final long serialVersionUID = -1953724749712440952L; + + final Observer downstream; + + CompletableSource other; + + boolean inCompletable; + + ConcatWithObserver(Observer actual, CompletableSource other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d) && !inCompletable) { + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + if (inCompletable) { + downstream.onComplete(); + } else { + inCompletable = true; + DisposableHelper.replace(this, null); + CompletableSource cs = other; + other = null; + cs.subscribe(this); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybe.java new file mode 100755 index 0000000..ee0f5b9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithMaybe.java @@ -0,0 +1,106 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Subscribe to a main Observable first, then when it completes normally, subscribe to a Maybe, + * signal its success value followed by a completion or signal its error or completion signal as is. + *

History: 2.1.10 - experimental + * @param the element type of the main source and output type + * @since 2.2 + */ +public final class ObservableConcatWithMaybe extends AbstractObservableWithUpstream { + + final MaybeSource other; + + public ObservableConcatWithMaybe(Observable source, MaybeSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new ConcatWithObserver(observer, other)); + } + + static final class ConcatWithObserver + extends AtomicReference + implements Observer, MaybeObserver, Disposable { + + private static final long serialVersionUID = -1953724749712440952L; + + final Observer downstream; + + MaybeSource other; + + boolean inMaybe; + + ConcatWithObserver(Observer actual, MaybeSource other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d) && !inMaybe) { + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onSuccess(T t) { + downstream.onNext(t); + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + if (inMaybe) { + downstream.onComplete(); + } else { + inMaybe = true; + DisposableHelper.replace(this, null); + MaybeSource ms = other; + other = null; + ms.subscribe(this); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithSingle.java new file mode 100755 index 0000000..f3548e6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableConcatWithSingle.java @@ -0,0 +1,102 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Subscribe to a main Observable first, then when it completes normally, subscribe to a Single, + * signal its success value followed by a completion or signal its error as is. + *

History: 2.1.10 - experimental + * @param the element type of the main source and output type + * @since 2.2 + */ +public final class ObservableConcatWithSingle extends AbstractObservableWithUpstream { + + final SingleSource other; + + public ObservableConcatWithSingle(Observable source, SingleSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new ConcatWithObserver(observer, other)); + } + + static final class ConcatWithObserver + extends AtomicReference + implements Observer, SingleObserver, Disposable { + + private static final long serialVersionUID = -1953724749712440952L; + + final Observer downstream; + + SingleSource other; + + boolean inSingle; + + ConcatWithObserver(Observer actual, SingleSource other) { + this.downstream = actual; + this.other = other; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d) && !inSingle) { + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onSuccess(T t) { + downstream.onNext(t); + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + inSingle = true; + DisposableHelper.replace(this, null); + SingleSource ss = other; + other = null; + ss.subscribe(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java new file mode 100755 index 0000000..bae08e0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCount.java @@ -0,0 +1,75 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class ObservableCount extends AbstractObservableWithUpstream { + public ObservableCount(ObservableSource source) { + super(source); + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new CountObserver(t)); + } + + static final class CountObserver implements Observer, Disposable { + final Observer downstream; + + Disposable upstream; + + long count; + + CountObserver(Observer downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(Object t) { + count++; + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onNext(count); + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java new file mode 100755 index 0000000..6e534b0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCountSingle.java @@ -0,0 +1,85 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.FuseToObservable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableCountSingle extends Single implements FuseToObservable { + final ObservableSource source; + public ObservableCountSingle(ObservableSource source) { + this.source = source; + } + + @Override + public void subscribeActual(SingleObserver t) { + source.subscribe(new CountObserver(t)); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableCount(source)); + } + + static final class CountObserver implements Observer, Disposable { + final SingleObserver downstream; + + Disposable upstream; + + long count; + + CountObserver(SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(Object t) { + count++; + } + + @Override + public void onError(Throwable t) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + downstream.onSuccess(count); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java new file mode 100755 index 0000000..03ba3f1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableCreate.java @@ -0,0 +1,293 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Cancellable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.fuseable.SimpleQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableCreate extends Observable { + final ObservableOnSubscribe source; + + public ObservableCreate(ObservableOnSubscribe source) { + this.source = source; + } + + @Override + protected void subscribeActual(Observer observer) { + CreateEmitter parent = new CreateEmitter(observer); + observer.onSubscribe(parent); + + try { + source.subscribe(parent); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + parent.onError(ex); + } + } + + static final class CreateEmitter + extends AtomicReference + implements ObservableEmitter, Disposable { + + private static final long serialVersionUID = -3434801548987643227L; + + final Observer observer; + + CreateEmitter(Observer observer) { + this.observer = observer; + } + + @Override + public void onNext(T t) { + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + if (!isDisposed()) { + observer.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + if (!isDisposed()) { + try { + observer.onError(t); + } finally { + dispose(); + } + return true; + } + return false; + } + + @Override + public void onComplete() { + if (!isDisposed()) { + try { + observer.onComplete(); + } finally { + dispose(); + } + } + } + + @Override + public void setDisposable(Disposable d) { + DisposableHelper.set(this, d); + } + + @Override + public void setCancellable(Cancellable c) { + setDisposable(new CancellableDisposable(c)); + } + + @Override + public ObservableEmitter serialize() { + return new SerializedEmitter(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } + } + + /** + * Serializes calls to onNext, onError and onComplete. + * + * @param the value type + */ + static final class SerializedEmitter + extends AtomicInteger + implements ObservableEmitter { + + private static final long serialVersionUID = 4883307006032401862L; + + final ObservableEmitter emitter; + + final AtomicThrowable error; + + final SpscLinkedArrayQueue queue; + + volatile boolean done; + + SerializedEmitter(ObservableEmitter emitter) { + this.emitter = emitter; + this.error = new AtomicThrowable(); + this.queue = new SpscLinkedArrayQueue(16); + } + + @Override + public void onNext(T t) { + if (emitter.isDisposed() || done) { + return; + } + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + if (get() == 0 && compareAndSet(0, 1)) { + emitter.onNext(t); + if (decrementAndGet() == 0) { + return; + } + } else { + SimpleQueue q = queue; + synchronized (q) { + q.offer(t); + } + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { + if (emitter.isDisposed() || done) { + return false; + } + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + if (error.addThrowable(t)) { + done = true; + drain(); + return true; + } + return false; + } + + @Override + public void onComplete() { + if (emitter.isDisposed() || done) { + return; + } + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + ObservableEmitter e = emitter; + SpscLinkedArrayQueue q = queue; + AtomicThrowable error = this.error; + int missed = 1; + for (;;) { + + for (;;) { + if (e.isDisposed()) { + q.clear(); + return; + } + + if (error.get() != null) { + q.clear(); + e.onError(error.terminate()); + return; + } + + boolean d = done; + T v = q.poll(); + + boolean empty = v == null; + + if (d && empty) { + e.onComplete(); + return; + } + + if (empty) { + break; + } + + e.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void setDisposable(Disposable d) { + emitter.setDisposable(d); + } + + @Override + public void setCancellable(Cancellable c) { + emitter.setCancellable(c); + } + + @Override + public boolean isDisposed() { + return emitter.isDisposed(); + } + + @Override + public ObservableEmitter serialize() { + return this; + } + + @Override + public String toString() { + return emitter.toString(); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java new file mode 100755 index 0000000..db8b9d4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java @@ -0,0 +1,191 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableDebounce extends AbstractObservableWithUpstream { + final Function> debounceSelector; + + public ObservableDebounce(ObservableSource source, Function> debounceSelector) { + super(source); + this.debounceSelector = debounceSelector; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new DebounceObserver(new SerializedObserver(t), debounceSelector)); + } + + static final class DebounceObserver + implements Observer, Disposable { + final Observer downstream; + final Function> debounceSelector; + + Disposable upstream; + + final AtomicReference debouncer = new AtomicReference(); + + volatile long index; + + boolean done; + + DebounceObserver(Observer actual, + Function> debounceSelector) { + this.downstream = actual; + this.debounceSelector = debounceSelector; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + long idx = index + 1; + index = idx; + + Disposable d = debouncer.get(); + if (d != null) { + d.dispose(); + } + + ObservableSource p; + + try { + p = ObjectHelper.requireNonNull(debounceSelector.apply(t), "The ObservableSource supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + dispose(); + downstream.onError(e); + return; + } + + DebounceInnerObserver dis = new DebounceInnerObserver(this, idx, t); + + if (debouncer.compareAndSet(d, dis)) { + p.subscribe(dis); + } + } + + @Override + public void onError(Throwable t) { + DisposableHelper.dispose(debouncer); + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + Disposable d = debouncer.get(); + if (d != DisposableHelper.DISPOSED) { + @SuppressWarnings("unchecked") + DebounceInnerObserver dis = (DebounceInnerObserver)d; + if (dis != null) { + dis.emit(); + } + DisposableHelper.dispose(debouncer); + downstream.onComplete(); + } + } + + @Override + public void dispose() { + upstream.dispose(); + DisposableHelper.dispose(debouncer); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + void emit(long idx, T value) { + if (idx == index) { + downstream.onNext(value); + } + } + + static final class DebounceInnerObserver extends DisposableObserver { + final DebounceObserver parent; + final long index; + final T value; + + boolean done; + + final AtomicBoolean once = new AtomicBoolean(); + + DebounceInnerObserver(DebounceObserver parent, long index, T value) { + this.parent = parent; + this.index = index; + this.value = value; + } + + @Override + public void onNext(U t) { + if (done) { + return; + } + done = true; + dispose(); + emit(); + } + + void emit() { + if (once.compareAndSet(false, true)) { + parent.emit(index, value); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + parent.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + emit(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java new file mode 100755 index 0000000..a3fad4f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounceTimed.java @@ -0,0 +1,186 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.observers.SerializedObserver; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableDebounceTimed extends AbstractObservableWithUpstream { + final long timeout; + final TimeUnit unit; + final Scheduler scheduler; + + public ObservableDebounceTimed(ObservableSource source, long timeout, TimeUnit unit, Scheduler scheduler) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new DebounceTimedObserver( + new SerializedObserver(t), + timeout, unit, scheduler.createWorker())); + } + + static final class DebounceTimedObserver + implements Observer, Disposable { + final Observer downstream; + final long timeout; + final TimeUnit unit; + final Worker worker; + + Disposable upstream; + + Disposable timer; + + volatile long index; + + boolean done; + + DebounceTimedObserver(Observer actual, long timeout, TimeUnit unit, Worker worker) { + this.downstream = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long idx = index + 1; + index = idx; + + Disposable d = timer; + if (d != null) { + d.dispose(); + } + + DebounceEmitter de = new DebounceEmitter(t, idx, this); + timer = de; + d = worker.schedule(de, timeout, unit); + de.setResource(d); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + Disposable d = timer; + if (d != null) { + d.dispose(); + } + done = true; + downstream.onError(t); + worker.dispose(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + Disposable d = timer; + if (d != null) { + d.dispose(); + } + + @SuppressWarnings("unchecked") + DebounceEmitter de = (DebounceEmitter)d; + if (de != null) { + de.run(); + } + downstream.onComplete(); + worker.dispose(); + } + + @Override + public void dispose() { + upstream.dispose(); + worker.dispose(); + } + + @Override + public boolean isDisposed() { + return worker.isDisposed(); + } + + void emit(long idx, T t, DebounceEmitter emitter) { + if (idx == index) { + downstream.onNext(t); + emitter.dispose(); + } + } + } + + static final class DebounceEmitter extends AtomicReference implements Runnable, Disposable { + + private static final long serialVersionUID = 6812032969491025141L; + + final T value; + final long idx; + final DebounceTimedObserver parent; + + final AtomicBoolean once = new AtomicBoolean(); + + DebounceEmitter(T value, long idx, DebounceTimedObserver parent) { + this.value = value; + this.idx = idx; + this.parent = parent; + } + + @Override + public void run() { + if (once.compareAndSet(false, true)) { + parent.emit(idx, value, this); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + + public void setResource(Disposable d) { + DisposableHelper.replace(this, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java new file mode 100755 index 0000000..adc2c29 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDefer.java @@ -0,0 +1,42 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; + +public final class ObservableDefer extends Observable { + final Callable> supplier; + public ObservableDefer(Callable> supplier) { + this.supplier = supplier; + } + + @Override + public void subscribeActual(Observer observer) { + ObservableSource pub; + try { + pub = ObjectHelper.requireNonNull(supplier.call(), "null ObservableSource supplied"); + } catch (Throwable t) { + Exceptions.throwIfFatal(t); + EmptyDisposable.error(t, observer); + return; + } + + pub.subscribe(observer); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDelay.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDelay.java new file mode 100755 index 0000000..53a75c1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDelay.java @@ -0,0 +1,146 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.observers.SerializedObserver; + +public final class ObservableDelay extends AbstractObservableWithUpstream { + final long delay; + final TimeUnit unit; + final Scheduler scheduler; + final boolean delayError; + + public ObservableDelay(ObservableSource source, long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) { + super(source); + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + this.delayError = delayError; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribeActual(Observer t) { + Observer observer; + if (delayError) { + observer = (Observer)t; + } else { + observer = new SerializedObserver(t); + } + + Worker w = scheduler.createWorker(); + + source.subscribe(new DelayObserver(observer, delay, unit, w, delayError)); + } + + static final class DelayObserver implements Observer, Disposable { + final Observer downstream; + final long delay; + final TimeUnit unit; + final Worker w; + final boolean delayError; + + Disposable upstream; + + DelayObserver(Observer actual, long delay, TimeUnit unit, Worker w, boolean delayError) { + super(); + this.downstream = actual; + this.delay = delay; + this.unit = unit; + this.w = w; + this.delayError = delayError; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(final T t) { + w.schedule(new OnNext(t), delay, unit); + } + + @Override + public void onError(final Throwable t) { + w.schedule(new OnError(t), delayError ? delay : 0, unit); + } + + @Override + public void onComplete() { + w.schedule(new OnComplete(), delay, unit); + } + + @Override + public void dispose() { + upstream.dispose(); + w.dispose(); + } + + @Override + public boolean isDisposed() { + return w.isDisposed(); + } + + final class OnNext implements Runnable { + private final T t; + + OnNext(T t) { + this.t = t; + } + + @Override + public void run() { + downstream.onNext(t); + } + } + + final class OnError implements Runnable { + private final Throwable throwable; + + OnError(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public void run() { + try { + downstream.onError(throwable); + } finally { + w.dispose(); + } + } + } + + final class OnComplete implements Runnable { + @Override + public void run() { + try { + downstream.onComplete(); + } finally { + w.dispose(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOther.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOther.java new file mode 100755 index 0000000..c6aff6e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDelaySubscriptionOther.java @@ -0,0 +1,108 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.SequentialDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Delays the subscription to the main source until the other + * observable fires an event or completes. + * @param the main type + * @param the other value type, ignored + */ +public final class ObservableDelaySubscriptionOther extends Observable { + final ObservableSource main; + final ObservableSource other; + + public ObservableDelaySubscriptionOther(ObservableSource main, ObservableSource other) { + this.main = main; + this.other = other; + } + + @Override + public void subscribeActual(final Observer child) { + final SequentialDisposable serial = new SequentialDisposable(); + child.onSubscribe(serial); + + Observer otherObserver = new DelayObserver(serial, child); + + other.subscribe(otherObserver); + } + + final class DelayObserver implements Observer { + final SequentialDisposable serial; + final Observer child; + boolean done; + + DelayObserver(SequentialDisposable serial, Observer child) { + this.serial = serial; + this.child = child; + } + + @Override + public void onSubscribe(Disposable d) { + serial.update(d); + } + + @Override + public void onNext(U t) { + onComplete(); + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaPlugins.onError(e); + return; + } + done = true; + child.onError(e); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + main.subscribe(new OnComplete()); + } + + final class OnComplete implements Observer { + @Override + public void onSubscribe(Disposable d) { + serial.update(d); + } + + @Override + public void onNext(T value) { + child.onNext(value); + } + + @Override + public void onError(Throwable e) { + child.onError(e); + } + + @Override + public void onComplete() { + child.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java new file mode 100755 index 0000000..2f86415 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDematerialize.java @@ -0,0 +1,126 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableDematerialize extends AbstractObservableWithUpstream { + + final Function> selector; + + public ObservableDematerialize(ObservableSource source, Function> selector) { + super(source); + this.selector = selector; + } + + @Override + public void subscribeActual(Observer observer) { + source.subscribe(new DematerializeObserver(observer, selector)); + } + + static final class DematerializeObserver implements Observer, Disposable { + final Observer downstream; + + final Function> selector; + + boolean done; + + Disposable upstream; + + DematerializeObserver(Observer downstream, Function> selector) { + this.downstream = downstream; + this.selector = selector; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T item) { + if (done) { + if (item instanceof Notification) { + Notification notification = (Notification)item; + if (notification.isOnError()) { + RxJavaPlugins.onError(notification.getError()); + } + } + return; + } + + Notification notification; + + try { + notification = ObjectHelper.requireNonNull(selector.apply(item), "The selector returned a null Notification"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + if (notification.isOnError()) { + upstream.dispose(); + onError(notification.getError()); + } + else if (notification.isOnComplete()) { + upstream.dispose(); + onComplete(); + } else { + downstream.onNext(notification.getValue()); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDetach.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDetach.java new file mode 100755 index 0000000..897ac02 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDetach.java @@ -0,0 +1,92 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.EmptyComponent; + +/** + * Breaks the links between the upstream and the downstream (the Disposable and + * the Observer references) when the sequence terminates or gets disposed. + * + * @param the value type + */ +public final class ObservableDetach extends AbstractObservableWithUpstream { + + public ObservableDetach(ObservableSource source) { + super(source); + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new DetachObserver(observer)); + } + + static final class DetachObserver implements Observer, Disposable { + + Observer downstream; + + Disposable upstream; + + DetachObserver(Observer downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + Disposable d = this.upstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asObserver(); + d.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + Observer a = downstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asObserver(); + a.onError(t); + } + + @Override + public void onComplete() { + Observer a = downstream; + this.upstream = EmptyComponent.INSTANCE; + this.downstream = EmptyComponent.asObserver(); + a.onComplete(); + } + } +} + diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinct.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinct.java new file mode 100755 index 0000000..6ebe127 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinct.java @@ -0,0 +1,135 @@ +/** + * 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.internal.operators.observable; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.BasicFuseableObserver; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableDistinct extends AbstractObservableWithUpstream { + + final Function keySelector; + + final Callable> collectionSupplier; + + public ObservableDistinct(ObservableSource source, Function keySelector, Callable> collectionSupplier) { + super(source); + this.keySelector = keySelector; + this.collectionSupplier = collectionSupplier; + } + + @Override + protected void subscribeActual(Observer observer) { + Collection collection; + + try { + collection = ObjectHelper.requireNonNull(collectionSupplier.call(), "The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + source.subscribe(new DistinctObserver(observer, keySelector, collection)); + } + + static final class DistinctObserver extends BasicFuseableObserver { + + final Collection collection; + + final Function keySelector; + + DistinctObserver(Observer actual, Function keySelector, Collection collection) { + super(actual); + this.keySelector = keySelector; + this.collection = collection; + } + + @Override + public void onNext(T value) { + if (done) { + return; + } + if (sourceMode == NONE) { + K key; + boolean b; + + try { + key = ObjectHelper.requireNonNull(keySelector.apply(value), "The keySelector returned a null key"); + b = collection.add(key); + } catch (Throwable ex) { + fail(ex); + return; + } + + if (b) { + downstream.onNext(value); + } + } else { + downstream.onNext(null); + } + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaPlugins.onError(e); + } else { + done = true; + collection.clear(); + downstream.onError(e); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + collection.clear(); + downstream.onComplete(); + } + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + for (;;) { + T v = qd.poll(); + + if (v == null || collection.add(ObjectHelper.requireNonNull(keySelector.apply(v), "The keySelector returned a null key"))) { + return v; + } + } + } + + @Override + public void clear() { + collection.clear(); + super.clear(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChanged.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChanged.java new file mode 100755 index 0000000..866efd3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChanged.java @@ -0,0 +1,117 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.functions.*; +import io.reactivex.internal.observers.BasicFuseableObserver; + +public final class ObservableDistinctUntilChanged extends AbstractObservableWithUpstream { + + final Function keySelector; + + final BiPredicate comparer; + + public ObservableDistinctUntilChanged(ObservableSource source, Function keySelector, BiPredicate comparer) { + super(source); + this.keySelector = keySelector; + this.comparer = comparer; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new DistinctUntilChangedObserver(observer, keySelector, comparer)); + } + + static final class DistinctUntilChangedObserver extends BasicFuseableObserver { + + final Function keySelector; + + final BiPredicate comparer; + + K last; + + boolean hasValue; + + DistinctUntilChangedObserver(Observer actual, + Function keySelector, + BiPredicate comparer) { + super(actual); + this.keySelector = keySelector; + this.comparer = comparer; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (sourceMode != NONE) { + downstream.onNext(t); + return; + } + + K key; + + try { + key = keySelector.apply(t); + if (hasValue) { + boolean equal = comparer.test(last, key); + last = key; + if (equal) { + return; + } + } else { + hasValue = true; + last = key; + } + } catch (Throwable ex) { + fail(ex); + return; + } + + downstream.onNext(t); + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + for (;;) { + T v = qd.poll(); + if (v == null) { + return null; + } + K key = keySelector.apply(v); + if (!hasValue) { + hasValue = true; + last = key; + return v; + } + + if (!comparer.test(last, key)) { + last = key; + return v; + } + last = key; + } + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoAfterNext.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoAfterNext.java new file mode 100755 index 0000000..c84f357 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoAfterNext.java @@ -0,0 +1,78 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.observers.BasicFuseableObserver; + +/** + * Calls a consumer after pushing the current item to the downstream. + *

History: 2.0.1 - experimental + * @param the value type + * @since 2.1 + */ +public final class ObservableDoAfterNext extends AbstractObservableWithUpstream { + + final Consumer onAfterNext; + + public ObservableDoAfterNext(ObservableSource source, Consumer onAfterNext) { + super(source); + this.onAfterNext = onAfterNext; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new DoAfterObserver(observer, onAfterNext)); + } + + static final class DoAfterObserver extends BasicFuseableObserver { + + final Consumer onAfterNext; + + DoAfterObserver(Observer actual, Consumer onAfterNext) { + super(actual); + this.onAfterNext = onAfterNext; + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + + if (sourceMode == NONE) { + try { + onAfterNext.accept(t); + } catch (Throwable ex) { + fail(ex); + } + } + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + T v = qd.poll(); + if (v != null) { + onAfterNext.accept(v); + } + return v; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoFinally.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoFinally.java new file mode 100755 index 0000000..bc305af --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoFinally.java @@ -0,0 +1,150 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.observers.BasicIntQueueDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Execute an action after an onError, onComplete or a dispose event. + *

History: 2.0.1 - experimental + * @param the value type + * @since 2.1 + */ +public final class ObservableDoFinally extends AbstractObservableWithUpstream { + + final Action onFinally; + + public ObservableDoFinally(ObservableSource source, Action onFinally) { + super(source); + this.onFinally = onFinally; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new DoFinallyObserver(observer, onFinally)); + } + + static final class DoFinallyObserver extends BasicIntQueueDisposable implements Observer { + + private static final long serialVersionUID = 4109457741734051389L; + + final Observer downstream; + + final Action onFinally; + + Disposable upstream; + + QueueDisposable qd; + + boolean syncFused; + + DoFinallyObserver(Observer actual, Action onFinally) { + this.downstream = actual; + this.onFinally = onFinally; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + if (d instanceof QueueDisposable) { + this.qd = (QueueDisposable)d; + } + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + runFinally(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + runFinally(); + } + + @Override + public void dispose() { + upstream.dispose(); + runFinally(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public int requestFusion(int mode) { + QueueDisposable qd = this.qd; + if (qd != null && (mode & BOUNDARY) == 0) { + int m = qd.requestFusion(mode); + if (m != NONE) { + syncFused = m == SYNC; + } + return m; + } + return NONE; + } + + @Override + public void clear() { + qd.clear(); + } + + @Override + public boolean isEmpty() { + return qd.isEmpty(); + } + + @Nullable + @Override + public T poll() throws Exception { + T v = qd.poll(); + if (v == null && syncFused) { + runFinally(); + } + return v; + } + + void runFinally() { + if (compareAndSet(0, 1)) { + try { + onFinally.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java new file mode 100755 index 0000000..a88218c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnEach.java @@ -0,0 +1,151 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableDoOnEach extends AbstractObservableWithUpstream { + final Consumer onNext; + final Consumer onError; + final Action onComplete; + final Action onAfterTerminate; + + public ObservableDoOnEach(ObservableSource source, Consumer onNext, + Consumer onError, + Action onComplete, + Action onAfterTerminate) { + super(source); + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + this.onAfterTerminate = onAfterTerminate; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new DoOnEachObserver(t, onNext, onError, onComplete, onAfterTerminate)); + } + + static final class DoOnEachObserver implements Observer, Disposable { + final Observer downstream; + final Consumer onNext; + final Consumer onError; + final Action onComplete; + final Action onAfterTerminate; + + Disposable upstream; + + boolean done; + + DoOnEachObserver( + Observer actual, + Consumer onNext, + Consumer onError, + Action onComplete, + Action onAfterTerminate) { + this.downstream = actual; + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + this.onAfterTerminate = onAfterTerminate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + onNext.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + try { + onError.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + t = new CompositeException(t, e); + } + downstream.onError(t); + + try { + onAfterTerminate.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + try { + onComplete.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + onError(e); + return; + } + + done = true; + downstream.onComplete(); + + try { + onAfterTerminate.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnLifecycle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnLifecycle.java new file mode 100755 index 0000000..cc4a068 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDoOnLifecycle.java @@ -0,0 +1,35 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.*; +import io.reactivex.internal.observers.DisposableLambdaObserver; + +public final class ObservableDoOnLifecycle extends AbstractObservableWithUpstream { + private final Consumer onSubscribe; + private final Action onDispose; + + public ObservableDoOnLifecycle(Observable upstream, Consumer onSubscribe, + Action onDispose) { + super(upstream); + this.onSubscribe = onSubscribe; + this.onDispose = onDispose; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new DisposableLambdaObserver(observer, onSubscribe, onDispose)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java new file mode 100755 index 0000000..5897fdd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAt.java @@ -0,0 +1,119 @@ +/** + * 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.internal.operators.observable; + +import java.util.NoSuchElementException; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableElementAt extends AbstractObservableWithUpstream { + final long index; + final T defaultValue; + final boolean errorOnFewer; + + public ObservableElementAt(ObservableSource source, long index, T defaultValue, boolean errorOnFewer) { + super(source); + this.index = index; + this.defaultValue = defaultValue; + this.errorOnFewer = errorOnFewer; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new ElementAtObserver(t, index, defaultValue, errorOnFewer)); + } + + static final class ElementAtObserver implements Observer, Disposable { + final Observer downstream; + final long index; + final T defaultValue; + final boolean errorOnFewer; + + Disposable upstream; + + long count; + + boolean done; + + ElementAtObserver(Observer actual, long index, T defaultValue, boolean errorOnFewer) { + this.downstream = actual; + this.index = index; + this.defaultValue = defaultValue; + this.errorOnFewer = errorOnFewer; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long c = count; + if (c == index) { + done = true; + upstream.dispose(); + downstream.onNext(t); + downstream.onComplete(); + return; + } + count = c + 1; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + T v = defaultValue; + if (v == null && errorOnFewer) { + downstream.onError(new NoSuchElementException()); + } else { + if (v != null) { + downstream.onNext(v); + } + downstream.onComplete(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java new file mode 100755 index 0000000..921edd6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtMaybe.java @@ -0,0 +1,106 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.FuseToObservable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableElementAtMaybe extends Maybe implements FuseToObservable { + final ObservableSource source; + final long index; + public ObservableElementAtMaybe(ObservableSource source, long index) { + this.source = source; + this.index = index; + } + + @Override + public void subscribeActual(MaybeObserver t) { + source.subscribe(new ElementAtObserver(t, index)); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableElementAt(source, index, null, false)); + } + + static final class ElementAtObserver implements Observer, Disposable { + final MaybeObserver downstream; + final long index; + + Disposable upstream; + + long count; + + boolean done; + + ElementAtObserver(MaybeObserver actual, long index) { + this.downstream = actual; + this.index = index; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long c = count; + if (c == index) { + done = true; + upstream.dispose(); + downstream.onSuccess(t); + return; + } + count = c + 1; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java new file mode 100755 index 0000000..7de1fa7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableElementAtSingle.java @@ -0,0 +1,120 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.FuseToObservable; + +import java.util.NoSuchElementException; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableElementAtSingle extends Single implements FuseToObservable { + final ObservableSource source; + final long index; + final T defaultValue; + + public ObservableElementAtSingle(ObservableSource source, long index, T defaultValue) { + this.source = source; + this.index = index; + this.defaultValue = defaultValue; + } + + @Override + public void subscribeActual(SingleObserver t) { + source.subscribe(new ElementAtObserver(t, index, defaultValue)); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableElementAt(source, index, defaultValue, true)); + } + + static final class ElementAtObserver implements Observer, Disposable { + final SingleObserver downstream; + final long index; + final T defaultValue; + + Disposable upstream; + + long count; + + boolean done; + + ElementAtObserver(SingleObserver actual, long index, T defaultValue) { + this.downstream = actual; + this.index = index; + this.defaultValue = defaultValue; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + long c = count; + if (c == index) { + done = true; + upstream.dispose(); + downstream.onSuccess(t); + return; + } + count = c + 1; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + + T v = defaultValue; + + if (v != null) { + downstream.onSuccess(v); + } else { + downstream.onError(new NoSuchElementException()); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableEmpty.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableEmpty.java new file mode 100755 index 0000000..0343f13 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableEmpty.java @@ -0,0 +1,35 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.fuseable.ScalarCallable; + +public final class ObservableEmpty extends Observable implements ScalarCallable { + public static final Observable INSTANCE = new ObservableEmpty(); + + private ObservableEmpty() { + } + + @Override + protected void subscribeActual(Observer o) { + EmptyDisposable.complete(o); + } + + @Override + public Object call() { + return null; // null scalar is interpreted as being empty + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java new file mode 100755 index 0000000..b0eeb95 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableError.java @@ -0,0 +1,40 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; + +public final class ObservableError extends Observable { + final Callable errorSupplier; + public ObservableError(Callable errorSupplier) { + this.errorSupplier = errorSupplier; + } + + @Override + public void subscribeActual(Observer observer) { + Throwable error; + try { + error = ObjectHelper.requireNonNull(errorSupplier.call(), "Callable returned null throwable. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable t) { + Exceptions.throwIfFatal(t); + error = t; + } + EmptyDisposable.error(error, observer); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFilter.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFilter.java new file mode 100755 index 0000000..c9ec142 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFilter.java @@ -0,0 +1,75 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.observers.BasicFuseableObserver; + +public final class ObservableFilter extends AbstractObservableWithUpstream { + final Predicate predicate; + public ObservableFilter(ObservableSource source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + public void subscribeActual(Observer observer) { + source.subscribe(new FilterObserver(observer, predicate)); + } + + static final class FilterObserver extends BasicFuseableObserver { + final Predicate filter; + + FilterObserver(Observer actual, Predicate filter) { + super(actual); + this.filter = filter; + } + + @Override + public void onNext(T t) { + if (sourceMode == NONE) { + boolean b; + try { + b = filter.test(t); + } catch (Throwable e) { + fail(e); + return; + } + if (b) { + downstream.onNext(t); + } + } else { + downstream.onNext(null); + } + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public T poll() throws Exception { + for (;;) { + T v = qd.poll(); + if (v == null || filter.test(v)) { + return v; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java new file mode 100755 index 0000000..73a5306 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java @@ -0,0 +1,606 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.*; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableFlatMap extends AbstractObservableWithUpstream { + final Function> mapper; + final boolean delayErrors; + final int maxConcurrency; + final int bufferSize; + + public ObservableFlatMap(ObservableSource source, + Function> mapper, + boolean delayErrors, int maxConcurrency, int bufferSize) { + super(source); + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.bufferSize = bufferSize; + } + + @Override + public void subscribeActual(Observer t) { + + if (ObservableScalarXMap.tryScalarXMapSubscribe(source, t, mapper)) { + return; + } + + source.subscribe(new MergeObserver(t, mapper, delayErrors, maxConcurrency, bufferSize)); + } + + static final class MergeObserver extends AtomicInteger implements Disposable, Observer { + + private static final long serialVersionUID = -2117620485640801370L; + + final Observer downstream; + final Function> mapper; + final boolean delayErrors; + final int maxConcurrency; + final int bufferSize; + + volatile SimplePlainQueue queue; + + volatile boolean done; + + final AtomicThrowable errors = new AtomicThrowable(); + + volatile boolean cancelled; + + final AtomicReference[]> observers; + + static final InnerObserver[] EMPTY = new InnerObserver[0]; + + static final InnerObserver[] CANCELLED = new InnerObserver[0]; + + Disposable upstream; + + long uniqueId; + long lastId; + int lastIndex; + + Queue> sources; + + int wip; + + MergeObserver(Observer actual, Function> mapper, + boolean delayErrors, int maxConcurrency, int bufferSize) { + this.downstream = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.bufferSize = bufferSize; + if (maxConcurrency != Integer.MAX_VALUE) { + sources = new ArrayDeque>(maxConcurrency); + } + this.observers = new AtomicReference[]>(EMPTY); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + // safeguard against misbehaving sources + if (done) { + return; + } + ObservableSource p; + try { + p = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null ObservableSource"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + + if (maxConcurrency != Integer.MAX_VALUE) { + synchronized (this) { + if (wip == maxConcurrency) { + sources.offer(p); + return; + } + wip++; + } + } + + subscribeInner(p); + } + + @SuppressWarnings("unchecked") + void subscribeInner(ObservableSource p) { + for (;;) { + if (p instanceof Callable) { + if (tryEmitScalar(((Callable)p)) && maxConcurrency != Integer.MAX_VALUE) { + boolean empty = false; + synchronized (this) { + p = sources.poll(); + if (p == null) { + wip--; + empty = true; + } + } + if (empty) { + drain(); + break; + } + } else { + break; + } + } else { + InnerObserver inner = new InnerObserver(this, uniqueId++); + if (addInner(inner)) { + p.subscribe(inner); + } + break; + } + } + } + + boolean addInner(InnerObserver inner) { + for (;;) { + InnerObserver[] a = observers.get(); + if (a == CANCELLED) { + inner.dispose(); + return false; + } + int n = a.length; + InnerObserver[] b = new InnerObserver[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (observers.compareAndSet(a, b)) { + return true; + } + } + } + + void removeInner(InnerObserver inner) { + for (;;) { + InnerObserver[] a = observers.get(); + int n = a.length; + if (n == 0) { + return; + } + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + if (j < 0) { + return; + } + InnerObserver[] b; + if (n == 1) { + b = EMPTY; + } else { + b = new InnerObserver[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (observers.compareAndSet(a, b)) { + return; + } + } + } + + boolean tryEmitScalar(Callable value) { + U u; + try { + u = value.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errors.addThrowable(ex); + drain(); + return true; + } + + if (u == null) { + return true; + } + + if (get() == 0 && compareAndSet(0, 1)) { + downstream.onNext(u); + if (decrementAndGet() == 0) { + return true; + } + } else { + SimplePlainQueue q = queue; + if (q == null) { + if (maxConcurrency == Integer.MAX_VALUE) { + q = new SpscLinkedArrayQueue(bufferSize); + } else { + q = new SpscArrayQueue(maxConcurrency); + } + queue = q; + } + + if (!q.offer(u)) { + onError(new IllegalStateException("Scalar queue full?!")); + return true; + } + if (getAndIncrement() != 0) { + return false; + } + } + drainLoop(); + return true; + } + + void tryEmit(U value, InnerObserver inner) { + if (get() == 0 && compareAndSet(0, 1)) { + downstream.onNext(value); + if (decrementAndGet() == 0) { + return; + } + } else { + SimpleQueue q = inner.queue; + if (q == null) { + q = new SpscLinkedArrayQueue(bufferSize); + inner.queue = q; + } + q.offer(value); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + if (errors.addThrowable(t)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + drain(); + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + if (disposeAll()) { + Throwable ex = errors.terminate(); + if (ex != null && ex != ExceptionHelper.TERMINATED) { + RxJavaPlugins.onError(ex); + } + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + final Observer child = this.downstream; + int missed = 1; + for (;;) { + if (checkTerminate()) { + return; + } + int innerCompleted = 0; + SimplePlainQueue svq = queue; + + if (svq != null) { + for (;;) { + if (checkTerminate()) { + return; + } + + U o = svq.poll(); + + if (o == null) { + break; + } + + child.onNext(o); + innerCompleted++; + } + } + + if (innerCompleted != 0) { + if (maxConcurrency != Integer.MAX_VALUE) { + subscribeMore(innerCompleted); + innerCompleted = 0; + } + continue; + } + + boolean d = done; + svq = queue; + InnerObserver[] inner = observers.get(); + int n = inner.length; + + int nSources = 0; + if (maxConcurrency != Integer.MAX_VALUE) { + synchronized (this) { + nSources = sources.size(); + } + } + + if (d && (svq == null || svq.isEmpty()) && n == 0 && nSources == 0) { + Throwable ex = errors.terminate(); + if (ex != ExceptionHelper.TERMINATED) { + if (ex == null) { + child.onComplete(); + } else { + child.onError(ex); + } + } + return; + } + + if (n != 0) { + long startId = lastId; + int index = lastIndex; + + if (n <= index || inner[index].id != startId) { + if (n <= index) { + index = 0; + } + int j = index; + for (int i = 0; i < n; i++) { + if (inner[j].id == startId) { + break; + } + j++; + if (j == n) { + j = 0; + } + } + index = j; + lastIndex = j; + lastId = inner[j].id; + } + + int j = index; + sourceLoop: + for (int i = 0; i < n; i++) { + if (checkTerminate()) { + return; + } + + @SuppressWarnings("unchecked") + InnerObserver is = (InnerObserver)inner[j]; + SimpleQueue q = is.queue; + if (q != null) { + for (;;) { + U o; + try { + o = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + is.dispose(); + errors.addThrowable(ex); + if (checkTerminate()) { + return; + } + removeInner(is); + innerCompleted++; + j++; + if (j == n) { + j = 0; + } + continue sourceLoop; + } + if (o == null) { + break; + } + + child.onNext(o); + + if (checkTerminate()) { + return; + } + } + } + + boolean innerDone = is.done; + SimpleQueue innerQueue = is.queue; + if (innerDone && (innerQueue == null || innerQueue.isEmpty())) { + removeInner(is); + if (checkTerminate()) { + return; + } + innerCompleted++; + } + + j++; + if (j == n) { + j = 0; + } + } + lastIndex = j; + lastId = inner[j].id; + } + + if (innerCompleted != 0) { + if (maxConcurrency != Integer.MAX_VALUE) { + subscribeMore(innerCompleted); + innerCompleted = 0; + } + continue; + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void subscribeMore(int innerCompleted) { + while (innerCompleted-- != 0) { + ObservableSource p; + synchronized (this) { + p = sources.poll(); + if (p == null) { + wip--; + continue; + } + } + subscribeInner(p); + } + } + + boolean checkTerminate() { + if (cancelled) { + return true; + } + Throwable e = errors.get(); + if (!delayErrors && (e != null)) { + disposeAll(); + e = errors.terminate(); + if (e != ExceptionHelper.TERMINATED) { + downstream.onError(e); + } + return true; + } + return false; + } + + boolean disposeAll() { + upstream.dispose(); + InnerObserver[] a = observers.get(); + if (a != CANCELLED) { + a = observers.getAndSet(CANCELLED); + if (a != CANCELLED) { + for (InnerObserver inner : a) { + inner.dispose(); + } + return true; + } + } + return false; + } + } + + static final class InnerObserver extends AtomicReference + implements Observer { + + private static final long serialVersionUID = -4606175640614850599L; + final long id; + final MergeObserver parent; + + volatile boolean done; + volatile SimpleQueue queue; + + int fusionMode; + + InnerObserver(MergeObserver parent, long id) { + this.id = id; + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + if (d instanceof QueueDisposable) { + @SuppressWarnings("unchecked") + QueueDisposable qd = (QueueDisposable) d; + + int m = qd.requestFusion(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + if (m == QueueDisposable.SYNC) { + fusionMode = m; + queue = qd; + done = true; + parent.drain(); + return; + } + if (m == QueueDisposable.ASYNC) { + fusionMode = m; + queue = qd; + } + } + } + } + + @Override + public void onNext(U t) { + if (fusionMode == QueueDisposable.NONE) { + parent.tryEmit(t, this); + } else { + parent.drain(); + } + } + + @Override + public void onError(Throwable t) { + if (parent.errors.addThrowable(t)) { + if (!parent.delayErrors) { + parent.disposeAll(); + } + done = true; + parent.drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + + public void dispose() { + DisposableHelper.dispose(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletable.java new file mode 100755 index 0000000..727d0bc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletable.java @@ -0,0 +1,213 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.BasicIntQueueDisposable; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps a sequence of values into CompletableSources and awaits their termination. + * @param the value type + */ +public final class ObservableFlatMapCompletable extends AbstractObservableWithUpstream { + + final Function mapper; + + final boolean delayErrors; + + public ObservableFlatMapCompletable(ObservableSource source, + Function mapper, boolean delayErrors) { + super(source); + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new FlatMapCompletableMainObserver(observer, mapper, delayErrors)); + } + + static final class FlatMapCompletableMainObserver extends BasicIntQueueDisposable + implements Observer { + private static final long serialVersionUID = 8443155186132538303L; + + final Observer downstream; + + final AtomicThrowable errors; + + final Function mapper; + + final boolean delayErrors; + + final CompositeDisposable set; + + Disposable upstream; + + volatile boolean disposed; + + FlatMapCompletableMainObserver(Observer observer, Function mapper, boolean delayErrors) { + this.downstream = observer; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.set = new CompositeDisposable(); + this.lazySet(1); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T value) { + CompletableSource cs; + + try { + cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + + getAndIncrement(); + + InnerObserver inner = new InnerObserver(); + + if (!disposed && set.add(inner)) { + cs.subscribe(inner); + } + } + + @Override + public void onError(Throwable e) { + if (errors.addThrowable(e)) { + if (delayErrors) { + if (decrementAndGet() == 0) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } else { + dispose(); + if (getAndSet(0) > 0) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (decrementAndGet() == 0) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + } + } + + @Override + public void dispose() { + disposed = true; + upstream.dispose(); + set.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Nullable + @Override + public T poll() throws Exception { + return null; // always empty + } + + @Override + public boolean isEmpty() { + return true; // always empty + } + + @Override + public void clear() { + // nothing to clear + } + + @Override + public int requestFusion(int mode) { + return mode & ASYNC; + } + + void innerComplete(InnerObserver inner) { + set.delete(inner); + onComplete(); + } + + void innerError(InnerObserver inner, Throwable e) { + set.delete(inner); + onError(e); + } + + final class InnerObserver extends AtomicReference implements CompletableObserver, Disposable { + private static final long serialVersionUID = 8606673141535671828L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onComplete() { + innerComplete(this); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableCompletable.java new file mode 100755 index 0000000..67691a1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapCompletableCompletable.java @@ -0,0 +1,197 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.FuseToObservable; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps a sequence of values into CompletableSources and awaits their termination. + * @param the value type + */ +public final class ObservableFlatMapCompletableCompletable extends Completable implements FuseToObservable { + + final ObservableSource source; + + final Function mapper; + + final boolean delayErrors; + + public ObservableFlatMapCompletableCompletable(ObservableSource source, + Function mapper, boolean delayErrors) { + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + source.subscribe(new FlatMapCompletableMainObserver(observer, mapper, delayErrors)); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableFlatMapCompletable(source, mapper, delayErrors)); + } + + static final class FlatMapCompletableMainObserver extends AtomicInteger implements Disposable, Observer { + private static final long serialVersionUID = 8443155186132538303L; + + final CompletableObserver downstream; + + final AtomicThrowable errors; + + final Function mapper; + + final boolean delayErrors; + + final CompositeDisposable set; + + Disposable upstream; + + volatile boolean disposed; + + FlatMapCompletableMainObserver(CompletableObserver observer, Function mapper, boolean delayErrors) { + this.downstream = observer; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + this.set = new CompositeDisposable(); + this.lazySet(1); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T value) { + CompletableSource cs; + + try { + cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + + getAndIncrement(); + + InnerObserver inner = new InnerObserver(); + + if (!disposed && set.add(inner)) { + cs.subscribe(inner); + } + } + + @Override + public void onError(Throwable e) { + if (errors.addThrowable(e)) { + if (delayErrors) { + if (decrementAndGet() == 0) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } else { + dispose(); + if (getAndSet(0) > 0) { + Throwable ex = errors.terminate(); + downstream.onError(ex); + } + } + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (decrementAndGet() == 0) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + } + } + + @Override + public void dispose() { + disposed = true; + upstream.dispose(); + set.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + void innerComplete(InnerObserver inner) { + set.delete(inner); + onComplete(); + } + + void innerError(InnerObserver inner, Throwable e) { + set.delete(inner); + onError(e); + } + + final class InnerObserver extends AtomicReference implements CompletableObserver, Disposable { + private static final long serialVersionUID = 8606673141535671828L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onComplete() { + innerComplete(this); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybe.java new file mode 100755 index 0000000..122572a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapMaybe.java @@ -0,0 +1,334 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps upstream values into MaybeSources and merges their signals into one sequence. + * @param the source value type + * @param the result value type + */ +public final class ObservableFlatMapMaybe extends AbstractObservableWithUpstream { + + final Function> mapper; + + final boolean delayErrors; + + public ObservableFlatMapMaybe(ObservableSource source, Function> mapper, + boolean delayError) { + super(source); + this.mapper = mapper; + this.delayErrors = delayError; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new FlatMapMaybeObserver(observer, mapper, delayErrors)); + } + + static final class FlatMapMaybeObserver + extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = 8600231336733376951L; + + final Observer downstream; + + final boolean delayErrors; + + final CompositeDisposable set; + + final AtomicInteger active; + + final AtomicThrowable errors; + + final Function> mapper; + + final AtomicReference> queue; + + Disposable upstream; + + volatile boolean cancelled; + + FlatMapMaybeObserver(Observer actual, + Function> mapper, boolean delayErrors) { + this.downstream = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.set = new CompositeDisposable(); + this.errors = new AtomicThrowable(); + this.active = new AtomicInteger(1); + this.queue = new AtomicReference>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + MaybeSource ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + + active.getAndIncrement(); + + InnerObserver inner = new InnerObserver(); + + if (!cancelled && set.add(inner)) { + ms.subscribe(inner); + } + } + + @Override + public void onError(Throwable t) { + active.decrementAndGet(); + if (errors.addThrowable(t)) { + if (!delayErrors) { + set.dispose(); + } + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + active.decrementAndGet(); + drain(); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + set.dispose(); + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerSuccess(InnerObserver inner, R value) { + set.delete(inner); + if (get() == 0 && compareAndSet(0, 1)) { + downstream.onNext(value); + + boolean d = active.decrementAndGet() == 0; + SpscLinkedArrayQueue q = queue.get(); + + if (d && (q == null || q.isEmpty())) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + if (decrementAndGet() == 0) { + return; + } + } else { + SpscLinkedArrayQueue q = getOrCreateQueue(); + synchronized (q) { + q.offer(value); + } + active.decrementAndGet(); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + SpscLinkedArrayQueue getOrCreateQueue() { + for (;;) { + SpscLinkedArrayQueue current = queue.get(); + if (current != null) { + return current; + } + current = new SpscLinkedArrayQueue(Observable.bufferSize()); + if (queue.compareAndSet(null, current)) { + return current; + } + } + } + + void innerError(InnerObserver inner, Throwable e) { + set.delete(inner); + if (errors.addThrowable(e)) { + if (!delayErrors) { + upstream.dispose(); + set.dispose(); + } + active.decrementAndGet(); + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + void innerComplete(InnerObserver inner) { + set.delete(inner); + + if (get() == 0 && compareAndSet(0, 1)) { + boolean d = active.decrementAndGet() == 0; + SpscLinkedArrayQueue q = queue.get(); + + if (d && (q == null || q.isEmpty())) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + if (decrementAndGet() == 0) { + return; + } + drainLoop(); + } else { + active.decrementAndGet(); + drain(); + } + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void clear() { + SpscLinkedArrayQueue q = queue.get(); + if (q != null) { + q.clear(); + } + } + + void drainLoop() { + int missed = 1; + Observer a = downstream; + AtomicInteger n = active; + AtomicReference> qr = queue; + + for (;;) { + for (;;) { + if (cancelled) { + clear(); + return; + } + + if (!delayErrors) { + Throwable ex = errors.get(); + if (ex != null) { + ex = errors.terminate(); + clear(); + a.onError(ex); + return; + } + } + + boolean d = n.get() == 0; + SpscLinkedArrayQueue q = qr.get(); + R v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + final class InnerObserver extends AtomicReference + implements MaybeObserver, Disposable { + private static final long serialVersionUID = -502562646270949838L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R value) { + innerSuccess(this, value); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public void onComplete() { + innerComplete(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingle.java new file mode 100755 index 0000000..bedda3c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMapSingle.java @@ -0,0 +1,303 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps upstream values into SingleSources and merges their signals into one sequence. + * @param the source value type + * @param the result value type + */ +public final class ObservableFlatMapSingle extends AbstractObservableWithUpstream { + + final Function> mapper; + + final boolean delayErrors; + + public ObservableFlatMapSingle(ObservableSource source, Function> mapper, + boolean delayError) { + super(source); + this.mapper = mapper; + this.delayErrors = delayError; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new FlatMapSingleObserver(observer, mapper, delayErrors)); + } + + static final class FlatMapSingleObserver + extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = 8600231336733376951L; + + final Observer downstream; + + final boolean delayErrors; + + final CompositeDisposable set; + + final AtomicInteger active; + + final AtomicThrowable errors; + + final Function> mapper; + + final AtomicReference> queue; + + Disposable upstream; + + volatile boolean cancelled; + + FlatMapSingleObserver(Observer actual, + Function> mapper, boolean delayErrors) { + this.downstream = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.set = new CompositeDisposable(); + this.errors = new AtomicThrowable(); + this.active = new AtomicInteger(1); + this.queue = new AtomicReference>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + SingleSource ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + + active.getAndIncrement(); + + InnerObserver inner = new InnerObserver(); + + if (!cancelled && set.add(inner)) { + ms.subscribe(inner); + } + } + + @Override + public void onError(Throwable t) { + active.decrementAndGet(); + if (errors.addThrowable(t)) { + if (!delayErrors) { + set.dispose(); + } + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + active.decrementAndGet(); + drain(); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + set.dispose(); + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void innerSuccess(InnerObserver inner, R value) { + set.delete(inner); + if (get() == 0 && compareAndSet(0, 1)) { + downstream.onNext(value); + + boolean d = active.decrementAndGet() == 0; + SpscLinkedArrayQueue q = queue.get(); + + if (d && (q == null || q.isEmpty())) { + Throwable ex = errors.terminate(); + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + return; + } + if (decrementAndGet() == 0) { + return; + } + } else { + SpscLinkedArrayQueue q = getOrCreateQueue(); + synchronized (q) { + q.offer(value); + } + active.decrementAndGet(); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + SpscLinkedArrayQueue getOrCreateQueue() { + for (;;) { + SpscLinkedArrayQueue current = queue.get(); + if (current != null) { + return current; + } + current = new SpscLinkedArrayQueue(Observable.bufferSize()); + if (queue.compareAndSet(null, current)) { + return current; + } + } + } + + void innerError(InnerObserver inner, Throwable e) { + set.delete(inner); + if (errors.addThrowable(e)) { + if (!delayErrors) { + upstream.dispose(); + set.dispose(); + } + active.decrementAndGet(); + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void clear() { + SpscLinkedArrayQueue q = queue.get(); + if (q != null) { + q.clear(); + } + } + + void drainLoop() { + int missed = 1; + Observer a = downstream; + AtomicInteger n = active; + AtomicReference> qr = queue; + + for (;;) { + for (;;) { + if (cancelled) { + clear(); + return; + } + + if (!delayErrors) { + Throwable ex = errors.get(); + if (ex != null) { + ex = errors.terminate(); + clear(); + a.onError(ex); + return; + } + } + + boolean d = n.get() == 0; + SpscLinkedArrayQueue q = qr.get(); + R v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + final class InnerObserver extends AtomicReference + implements SingleObserver, Disposable { + private static final long serialVersionUID = -502562646270949838L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(R value) { + innerSuccess(this, value); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlattenIterable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlattenIterable.java new file mode 100755 index 0000000..74b1839 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlattenIterable.java @@ -0,0 +1,148 @@ +/** + * 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.internal.operators.observable; + +import java.util.Iterator; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps a sequence into an Iterable and emits its values. + * + * @param the input value type to map to Iterable + * @param the element type of the Iterable and the output + */ +public final class ObservableFlattenIterable extends AbstractObservableWithUpstream { + + final Function> mapper; + + public ObservableFlattenIterable(ObservableSource source, + Function> mapper) { + super(source); + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new FlattenIterableObserver(observer, mapper)); + } + + static final class FlattenIterableObserver implements Observer, Disposable { + final Observer downstream; + + final Function> mapper; + + Disposable upstream; + + FlattenIterableObserver(Observer actual, Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T value) { + if (upstream == DisposableHelper.DISPOSED) { + return; + } + + Iterator it; + + try { + it = mapper.apply(value).iterator(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + + Observer a = downstream; + + for (;;) { + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + + if (b) { + R v; + + try { + v = ObjectHelper.requireNonNull(it.next(), "The iterator returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + return; + } + + a.onNext(v); + } else { + break; + } + } + } + + @Override + public void onError(Throwable e) { + if (upstream == DisposableHelper.DISPOSED) { + RxJavaPlugins.onError(e); + return; + } + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void onComplete() { + if (upstream == DisposableHelper.DISPOSED) { + return; + } + upstream = DisposableHelper.DISPOSED; + downstream.onComplete(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java new file mode 100755 index 0000000..9a04a4f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java @@ -0,0 +1,115 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.BasicQueueDisposable; + +public final class ObservableFromArray extends Observable { + final T[] array; + public ObservableFromArray(T[] array) { + this.array = array; + } + + @Override + public void subscribeActual(Observer observer) { + FromArrayDisposable d = new FromArrayDisposable(observer, array); + + observer.onSubscribe(d); + + if (d.fusionMode) { + return; + } + + d.run(); + } + + static final class FromArrayDisposable extends BasicQueueDisposable { + + final Observer downstream; + + final T[] array; + + int index; + + boolean fusionMode; + + volatile boolean disposed; + + FromArrayDisposable(Observer actual, T[] array) { + this.downstream = actual; + this.array = array; + } + + @Override + public int requestFusion(int mode) { + if ((mode & SYNC) != 0) { + fusionMode = true; + return SYNC; + } + return NONE; + } + + @Nullable + @Override + public T poll() { + int i = index; + T[] a = array; + if (i != a.length) { + index = i + 1; + return ObjectHelper.requireNonNull(a[i], "The array element is null"); + } + return null; + } + + @Override + public boolean isEmpty() { + return index == array.length; + } + + @Override + public void clear() { + index = array.length; + } + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + void run() { + T[] a = array; + int n = a.length; + + for (int i = 0; i < n && !isDisposed(); i++) { + T value = a[i]; + if (value == null) { + downstream.onError(new NullPointerException("The element at index " + i + " is null")); + return; + } + downstream.onNext(value); + } + if (!isDisposed()) { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java new file mode 100755 index 0000000..fe3c364 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromCallable.java @@ -0,0 +1,60 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.DeferredScalarDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Calls a Callable and emits its resulting single value or signals its exception. + * @param the value type + */ +public final class ObservableFromCallable extends Observable implements Callable { + final Callable callable; + public ObservableFromCallable(Callable callable) { + this.callable = callable; + } + + @Override + public void subscribeActual(Observer observer) { + DeferredScalarDisposable d = new DeferredScalarDisposable(observer); + observer.onSubscribe(d); + if (d.isDisposed()) { + return; + } + T value; + try { + value = ObjectHelper.requireNonNull(callable.call(), "Callable returned null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + if (!d.isDisposed()) { + observer.onError(e); + } else { + RxJavaPlugins.onError(e); + } + return; + } + d.complete(value); + } + + @Override + public T call() throws Exception { + return ObjectHelper.requireNonNull(callable.call(), "The callable returned a null value"); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromFuture.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromFuture.java new file mode 100755 index 0000000..ec6e902 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromFuture.java @@ -0,0 +1,52 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.DeferredScalarDisposable; + +public final class ObservableFromFuture extends Observable { + final Future future; + final long timeout; + final TimeUnit unit; + + public ObservableFromFuture(Future future, long timeout, TimeUnit unit) { + this.future = future; + this.timeout = timeout; + this.unit = unit; + } + + @Override + public void subscribeActual(Observer observer) { + DeferredScalarDisposable d = new DeferredScalarDisposable(observer); + observer.onSubscribe(d); + if (!d.isDisposed()) { + T v; + try { + v = ObjectHelper.requireNonNull(unit != null ? future.get(timeout, unit) : future.get(), "Future returned null"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (!d.isDisposed()) { + observer.onError(ex); + } + return; + } + d.complete(v); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromIterable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromIterable.java new file mode 100755 index 0000000..f937f4d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromIterable.java @@ -0,0 +1,164 @@ +/** + * 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.internal.operators.observable; + +import java.util.Iterator; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.BasicQueueDisposable; + +public final class ObservableFromIterable extends Observable { + final Iterable source; + public ObservableFromIterable(Iterable source) { + this.source = source; + } + + @Override + public void subscribeActual(Observer observer) { + Iterator it; + try { + it = source.iterator(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + boolean hasNext; + try { + hasNext = it.hasNext(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + if (!hasNext) { + EmptyDisposable.complete(observer); + return; + } + + FromIterableDisposable d = new FromIterableDisposable(observer, it); + observer.onSubscribe(d); + + if (!d.fusionMode) { + d.run(); + } + } + + static final class FromIterableDisposable extends BasicQueueDisposable { + + final Observer downstream; + + final Iterator it; + + volatile boolean disposed; + + boolean fusionMode; + + boolean done; + + boolean checkNext; + + FromIterableDisposable(Observer actual, Iterator it) { + this.downstream = actual; + this.it = it; + } + + void run() { + boolean hasNext; + + do { + if (isDisposed()) { + return; + } + T v; + + try { + v = ObjectHelper.requireNonNull(it.next(), "The iterator returned a null value"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + + downstream.onNext(v); + + if (isDisposed()) { + return; + } + try { + hasNext = it.hasNext(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + } while (hasNext); + + if (!isDisposed()) { + downstream.onComplete(); + } + } + + @Override + public int requestFusion(int mode) { + if ((mode & SYNC) != 0) { + fusionMode = true; + return SYNC; + } + return NONE; + } + + @Nullable + @Override + public T poll() { + if (done) { + return null; + } + if (checkNext) { + if (!it.hasNext()) { + done = true; + return null; + } + } else { + checkNext = true; + } + + return ObjectHelper.requireNonNull(it.next(), "The iterator returned a null value"); + } + + @Override + public boolean isEmpty() { + return done; + } + + @Override + public void clear() { + done = true; + } + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromPublisher.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromPublisher.java new file mode 100755 index 0000000..27b4305 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromPublisher.java @@ -0,0 +1,79 @@ +/** + * 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.internal.operators.observable; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +public final class ObservableFromPublisher extends Observable { + + final Publisher source; + + public ObservableFromPublisher(Publisher publisher) { + this.source = publisher; + } + + @Override + protected void subscribeActual(final Observer o) { + source.subscribe(new PublisherSubscriber(o)); + } + + static final class PublisherSubscriber + implements FlowableSubscriber, Disposable { + + final Observer downstream; + Subscription upstream; + + PublisherSubscriber(Observer o) { + this.downstream = o; + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void dispose() { + upstream.cancel(); + upstream = SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDisposed() { + return upstream == SubscriptionHelper.CANCELLED; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromUnsafeSource.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromUnsafeSource.java new file mode 100755 index 0000000..81a6a70 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromUnsafeSource.java @@ -0,0 +1,29 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; + +public final class ObservableFromUnsafeSource extends Observable { + final ObservableSource source; + + public ObservableFromUnsafeSource(ObservableSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(observer); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java new file mode 100755 index 0000000..61f4891 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableGenerate.java @@ -0,0 +1,176 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableGenerate extends Observable { + final Callable stateSupplier; + final BiFunction, S> generator; + final Consumer disposeState; + + public ObservableGenerate(Callable stateSupplier, BiFunction, S> generator, + Consumer disposeState) { + this.stateSupplier = stateSupplier; + this.generator = generator; + this.disposeState = disposeState; + } + + @Override + public void subscribeActual(Observer observer) { + S state; + + try { + state = stateSupplier.call(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + + GeneratorDisposable gd = new GeneratorDisposable(observer, generator, disposeState, state); + observer.onSubscribe(gd); + gd.run(); + } + + static final class GeneratorDisposable + implements Emitter, Disposable { + + final Observer downstream; + final BiFunction, S> generator; + final Consumer disposeState; + + S state; + + volatile boolean cancelled; + + boolean terminate; + + boolean hasNext; + + GeneratorDisposable(Observer actual, + BiFunction, S> generator, + Consumer disposeState, S initialState) { + this.downstream = actual; + this.generator = generator; + this.disposeState = disposeState; + this.state = initialState; + } + + public void run() { + S s = state; + + if (cancelled) { + state = null; + dispose(s); + return; + } + + final BiFunction, S> f = generator; + + for (;;) { + + if (cancelled) { + state = null; + dispose(s); + return; + } + + hasNext = false; + + try { + s = f.apply(s, this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + state = null; + cancelled = true; + onError(ex); + dispose(s); + return; + } + + if (terminate) { + cancelled = true; + state = null; + dispose(s); + return; + } + } + + } + + private void dispose(S s) { + try { + disposeState.accept(s); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public void dispose() { + cancelled = true; + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public void onNext(T t) { + if (!terminate) { + if (hasNext) { + onError(new IllegalStateException("onNext already called in this generate turn")); + } else { + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + } else { + hasNext = true; + downstream.onNext(t); + } + } + } + } + + @Override + public void onError(Throwable t) { + if (terminate) { + RxJavaPlugins.onError(t); + } else { + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + terminate = true; + downstream.onError(t); + } + } + + @Override + public void onComplete() { + if (!terminate) { + terminate = true; + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupBy.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupBy.java new file mode 100755 index 0000000..0c0390a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupBy.java @@ -0,0 +1,357 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.*; + +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.observables.GroupedObservable; + +public final class ObservableGroupBy extends AbstractObservableWithUpstream> { + final Function keySelector; + final Function valueSelector; + final int bufferSize; + final boolean delayError; + + public ObservableGroupBy(ObservableSource source, + Function keySelector, Function valueSelector, + int bufferSize, boolean delayError) { + super(source); + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + } + + @Override + public void subscribeActual(Observer> t) { + source.subscribe(new GroupByObserver(t, keySelector, valueSelector, bufferSize, delayError)); + } + + public static final class GroupByObserver extends AtomicInteger implements Observer, Disposable { + + private static final long serialVersionUID = -3688291656102519502L; + + final Observer> downstream; + final Function keySelector; + final Function valueSelector; + final int bufferSize; + final boolean delayError; + final Map> groups; + + static final Object NULL_KEY = new Object(); + + Disposable upstream; + + final AtomicBoolean cancelled = new AtomicBoolean(); + + public GroupByObserver(Observer> actual, Function keySelector, Function valueSelector, int bufferSize, boolean delayError) { + this.downstream = actual; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + this.groups = new ConcurrentHashMap>(); + this.lazySet(1); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + K key; + try { + key = keySelector.apply(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + + Object mapKey = key != null ? key : NULL_KEY; + GroupedUnicast group = groups.get(mapKey); + if (group == null) { + // if the main has been cancelled, stop creating groups + // and skip this value + if (cancelled.get()) { + return; + } + + group = GroupedUnicast.createWith(key, bufferSize, this, delayError); + groups.put(mapKey, group); + + getAndIncrement(); + + downstream.onNext(group); + } + + V v; + try { + v = ObjectHelper.requireNonNull(valueSelector.apply(t), "The value supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + + group.onNext(v); + } + + @Override + public void onError(Throwable t) { + List> list = new ArrayList>(groups.values()); + groups.clear(); + + for (GroupedUnicast e : list) { + e.onError(t); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + List> list = new ArrayList>(groups.values()); + groups.clear(); + + for (GroupedUnicast e : list) { + e.onComplete(); + } + + downstream.onComplete(); + } + + @Override + public void dispose() { + // cancelling the main source means we don't want any more groups + // but running groups still require new values + if (cancelled.compareAndSet(false, true)) { + if (decrementAndGet() == 0) { + upstream.dispose(); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled.get(); + } + + public void cancel(K key) { + Object mapKey = key != null ? key : NULL_KEY; + groups.remove(mapKey); + if (decrementAndGet() == 0) { + upstream.dispose(); + } + } + } + + static final class GroupedUnicast extends GroupedObservable { + + final State state; + + public static GroupedUnicast createWith(K key, int bufferSize, GroupByObserver parent, boolean delayError) { + State state = new State(bufferSize, parent, key, delayError); + return new GroupedUnicast(key, state); + } + + protected GroupedUnicast(K key, State state) { + super(key); + this.state = state; + } + + @Override + protected void subscribeActual(Observer observer) { + state.subscribe(observer); + } + + public void onNext(T t) { + state.onNext(t); + } + + public void onError(Throwable e) { + state.onError(e); + } + + public void onComplete() { + state.onComplete(); + } + } + + static final class State extends AtomicInteger implements Disposable, ObservableSource { + + private static final long serialVersionUID = -3852313036005250360L; + + final K key; + final SpscLinkedArrayQueue queue; + final GroupByObserver parent; + final boolean delayError; + + volatile boolean done; + Throwable error; + + final AtomicBoolean cancelled = new AtomicBoolean(); + + final AtomicBoolean once = new AtomicBoolean(); + + final AtomicReference> actual = new AtomicReference>(); + + State(int bufferSize, GroupByObserver parent, K key, boolean delayError) { + this.queue = new SpscLinkedArrayQueue(bufferSize); + this.parent = parent; + this.key = key; + this.delayError = delayError; + } + + @Override + public void dispose() { + if (cancelled.compareAndSet(false, true)) { + if (getAndIncrement() == 0) { + actual.lazySet(null); + parent.cancel(key); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled.get(); + } + + @Override + public void subscribe(Observer observer) { + if (once.compareAndSet(false, true)) { + observer.onSubscribe(this); + actual.lazySet(observer); + if (cancelled.get()) { + actual.lazySet(null); + } else { + drain(); + } + } else { + EmptyDisposable.error(new IllegalStateException("Only one Observer allowed!"), observer); + } + } + + public void onNext(T t) { + queue.offer(t); + drain(); + } + + public void onError(Throwable e) { + error = e; + done = true; + drain(); + } + + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + int missed = 1; + + final SpscLinkedArrayQueue q = queue; + final boolean delayError = this.delayError; + Observer a = actual.get(); + for (;;) { + if (a != null) { + for (;;) { + boolean d = done; + T v = q.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, delayError)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + if (a == null) { + a = actual.get(); + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Observer a, boolean delayError) { + if (cancelled.get()) { + queue.clear(); + parent.cancel(key); + actual.lazySet(null); + return true; + } + + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + actual.lazySet(null); + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + return true; + } + } else { + Throwable e = error; + if (e != null) { + queue.clear(); + actual.lazySet(null); + a.onError(e); + return true; + } else + if (empty) { + actual.lazySet(null); + a.onComplete(); + return true; + } + } + } + + return false; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java new file mode 100755 index 0000000..23e39af --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableGroupJoin.java @@ -0,0 +1,481 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.atomic.*; + +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.UnicastSubject; + +public final class ObservableGroupJoin extends AbstractObservableWithUpstream { + + final ObservableSource other; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction, ? extends R> resultSelector; + + public ObservableGroupJoin( + ObservableSource source, + ObservableSource other, + Function> leftEnd, + Function> rightEnd, + BiFunction, ? extends R> resultSelector) { + super(source); + this.other = other; + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + } + + @Override + protected void subscribeActual(Observer observer) { + + GroupJoinDisposable parent = + new GroupJoinDisposable(observer, leftEnd, rightEnd, resultSelector); + + observer.onSubscribe(parent); + + LeftRightObserver left = new LeftRightObserver(parent, true); + parent.disposables.add(left); + LeftRightObserver right = new LeftRightObserver(parent, false); + parent.disposables.add(right); + + source.subscribe(left); + other.subscribe(right); + } + + interface JoinSupport { + + void innerError(Throwable ex); + + void innerComplete(LeftRightObserver sender); + + void innerValue(boolean isLeft, Object o); + + void innerClose(boolean isLeft, LeftRightEndObserver index); + + void innerCloseError(Throwable ex); + } + + static final class GroupJoinDisposable + extends AtomicInteger implements Disposable, JoinSupport { + + private static final long serialVersionUID = -6071216598687999801L; + + final Observer downstream; + + final SpscLinkedArrayQueue queue; + + final CompositeDisposable disposables; + + final Map> lefts; + + final Map rights; + + final AtomicReference error; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction, ? extends R> resultSelector; + + final AtomicInteger active; + + int leftIndex; + + int rightIndex; + + volatile boolean cancelled; + + static final Integer LEFT_VALUE = 1; + + static final Integer RIGHT_VALUE = 2; + + static final Integer LEFT_CLOSE = 3; + + static final Integer RIGHT_CLOSE = 4; + + GroupJoinDisposable( + Observer actual, + Function> leftEnd, + Function> rightEnd, + BiFunction, ? extends R> resultSelector) { + this.downstream = actual; + this.disposables = new CompositeDisposable(); + this.queue = new SpscLinkedArrayQueue(bufferSize()); + this.lefts = new LinkedHashMap>(); + this.rights = new LinkedHashMap(); + this.error = new AtomicReference(); + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + this.active = new AtomicInteger(2); + } + + @Override + public void dispose() { + if (cancelled) { + return; + } + cancelled = true; + cancelAll(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void cancelAll() { + disposables.dispose(); + } + + void errorAll(Observer a) { + Throwable ex = ExceptionHelper.terminate(error); + + for (UnicastSubject up : lefts.values()) { + up.onError(ex); + } + + lefts.clear(); + rights.clear(); + + a.onError(ex); + } + + void fail(Throwable exc, Observer a, SpscLinkedArrayQueue q) { + Exceptions.throwIfFatal(exc); + ExceptionHelper.addThrowable(error, exc); + q.clear(); + cancelAll(); + errorAll(a); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + SpscLinkedArrayQueue q = queue; + Observer a = downstream; + + for (;;) { + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + boolean d = active.get() == 0; + + Integer mode = (Integer)q.poll(); + + boolean empty = mode == null; + + if (d && empty) { + for (UnicastSubject up : lefts.values()) { + up.onComplete(); + } + + lefts.clear(); + rights.clear(); + disposables.dispose(); + + a.onComplete(); + return; + } + + if (empty) { + break; + } + + Object val = q.poll(); + + if (mode == LEFT_VALUE) { + @SuppressWarnings("unchecked") + TLeft left = (TLeft)val; + + UnicastSubject up = UnicastSubject.create(); + int idx = leftIndex++; + lefts.put(idx, up); + + ObservableSource p; + + try { + p = ObjectHelper.requireNonNull(leftEnd.apply(left), "The leftEnd returned a null ObservableSource"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + LeftRightEndObserver end = new LeftRightEndObserver(this, true, idx); + disposables.add(end); + + p.subscribe(end); + + ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + R w; + + try { + w = ObjectHelper.requireNonNull(resultSelector.apply(left, up), "The resultSelector returned a null value"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + a.onNext(w); + + for (TRight right : rights.values()) { + up.onNext(right); + } + } + else if (mode == RIGHT_VALUE) { + @SuppressWarnings("unchecked") + TRight right = (TRight)val; + + int idx = rightIndex++; + + rights.put(idx, right); + + ObservableSource p; + + try { + p = ObjectHelper.requireNonNull(rightEnd.apply(right), "The rightEnd returned a null ObservableSource"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + LeftRightEndObserver end = new LeftRightEndObserver(this, false, idx); + disposables.add(end); + + p.subscribe(end); + + ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + for (UnicastSubject up : lefts.values()) { + up.onNext(right); + } + } + else if (mode == LEFT_CLOSE) { + LeftRightEndObserver end = (LeftRightEndObserver)val; + + UnicastSubject up = lefts.remove(end.index); + disposables.remove(end); + if (up != null) { + up.onComplete(); + } + } + else if (mode == RIGHT_CLOSE) { + LeftRightEndObserver end = (LeftRightEndObserver)val; + + rights.remove(end.index); + disposables.remove(end); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void innerError(Throwable ex) { + if (ExceptionHelper.addThrowable(error, ex)) { + active.decrementAndGet(); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void innerComplete(LeftRightObserver sender) { + disposables.delete(sender); + active.decrementAndGet(); + drain(); + } + + @Override + public void innerValue(boolean isLeft, Object o) { + synchronized (this) { + queue.offer(isLeft ? LEFT_VALUE : RIGHT_VALUE, o); + } + drain(); + } + + @Override + public void innerClose(boolean isLeft, LeftRightEndObserver index) { + synchronized (this) { + queue.offer(isLeft ? LEFT_CLOSE : RIGHT_CLOSE, index); + } + drain(); + } + + @Override + public void innerCloseError(Throwable ex) { + if (ExceptionHelper.addThrowable(error, ex)) { + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + } + + static final class LeftRightObserver + extends AtomicReference + implements Observer, Disposable { + + private static final long serialVersionUID = 1883890389173668373L; + + final JoinSupport parent; + + final boolean isLeft; + + LeftRightObserver(JoinSupport parent, boolean isLeft) { + this.parent = parent; + this.isLeft = isLeft; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Object t) { + parent.innerValue(isLeft, t); + } + + @Override + public void onError(Throwable t) { + parent.innerError(t); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + } + + static final class LeftRightEndObserver + extends AtomicReference + implements Observer, Disposable { + + private static final long serialVersionUID = 1883890389173668373L; + + final JoinSupport parent; + + final boolean isLeft; + + final int index; + + LeftRightEndObserver(JoinSupport parent, + boolean isLeft, int index) { + this.parent = parent; + this.isLeft = isLeft; + this.index = index; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Object t) { + if (DisposableHelper.dispose(this)) { + parent.innerClose(isLeft, this); + } + } + + @Override + public void onError(Throwable t) { + parent.innerCloseError(t); + } + + @Override + public void onComplete() { + parent.innerClose(isLeft, this); + } + + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableHide.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableHide.java new file mode 100755 index 0000000..6685146 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableHide.java @@ -0,0 +1,80 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Hides the identity of the wrapped ObservableSource and its Disposable. + * @param the value type + * + * @since 2.0 + */ +public final class ObservableHide extends AbstractObservableWithUpstream { + + public ObservableHide(ObservableSource source) { + super(source); + } + + @Override + protected void subscribeActual(Observer o) { + source.subscribe(new HideDisposable(o)); + } + + static final class HideDisposable implements Observer, Disposable { + + final Observer downstream; + + Disposable upstream; + + HideDisposable(Observer downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElements.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElements.java new file mode 100755 index 0000000..b609cde --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElements.java @@ -0,0 +1,71 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; + +public final class ObservableIgnoreElements extends AbstractObservableWithUpstream { + + public ObservableIgnoreElements(ObservableSource source) { + super(source); + } + + @Override + public void subscribeActual(final Observer t) { + source.subscribe(new IgnoreObservable(t)); + } + + static final class IgnoreObservable implements Observer, Disposable { + final Observer downstream; + + Disposable upstream; + + IgnoreObservable(Observer t) { + this.downstream = t; + } + + @Override + public void onSubscribe(Disposable d) { + this.upstream = d; + downstream.onSubscribe(this); + } + + @Override + public void onNext(T v) { + // deliberately ignored + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsCompletable.java new file mode 100755 index 0000000..15b3789 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableIgnoreElementsCompletable.java @@ -0,0 +1,80 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.fuseable.FuseToObservable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableIgnoreElementsCompletable extends Completable implements FuseToObservable { + + final ObservableSource source; + + public ObservableIgnoreElementsCompletable(ObservableSource source) { + this.source = source; + } + + @Override + public void subscribeActual(final CompletableObserver t) { + source.subscribe(new IgnoreObservable(t)); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableIgnoreElements(source)); + } + + static final class IgnoreObservable implements Observer, Disposable { + final CompletableObserver downstream; + + Disposable upstream; + + IgnoreObservable(CompletableObserver t) { + this.downstream = t; + } + + @Override + public void onSubscribe(Disposable d) { + this.upstream = d; + downstream.onSubscribe(this); + } + + @Override + public void onNext(T v) { + // deliberately ignored + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java new file mode 100755 index 0000000..733b18f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableInternalHelper.java @@ -0,0 +1,322 @@ +/** + * 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.internal.operators.observable; + +import java.util.List; +import java.util.concurrent.*; + +import io.reactivex.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; +import io.reactivex.observables.ConnectableObservable; + +/** + * Helper utility class to support Observable with inner classes. + */ +public final class ObservableInternalHelper { + + private ObservableInternalHelper() { + throw new IllegalStateException("No instances!"); + } + + static final class SimpleGenerator implements BiFunction, S> { + final Consumer> consumer; + + SimpleGenerator(Consumer> consumer) { + this.consumer = consumer; + } + + @Override + public S apply(S t1, Emitter t2) throws Exception { + consumer.accept(t2); + return t1; + } + } + + public static BiFunction, S> simpleGenerator(Consumer> consumer) { + return new SimpleGenerator(consumer); + } + + static final class SimpleBiGenerator implements BiFunction, S> { + final BiConsumer> consumer; + + SimpleBiGenerator(BiConsumer> consumer) { + this.consumer = consumer; + } + + @Override + public S apply(S t1, Emitter t2) throws Exception { + consumer.accept(t1, t2); + return t1; + } + } + + public static BiFunction, S> simpleBiGenerator(BiConsumer> consumer) { + return new SimpleBiGenerator(consumer); + } + + static final class ItemDelayFunction implements Function> { + final Function> itemDelay; + + ItemDelayFunction(Function> itemDelay) { + this.itemDelay = itemDelay; + } + + @Override + public ObservableSource apply(final T v) throws Exception { + ObservableSource o = ObjectHelper.requireNonNull(itemDelay.apply(v), "The itemDelay returned a null ObservableSource"); + return new ObservableTake(o, 1).map(Functions.justFunction(v)).defaultIfEmpty(v); + } + } + + public static Function> itemDelay(final Function> itemDelay) { + return new ItemDelayFunction(itemDelay); + } + + static final class ObserverOnNext implements Consumer { + final Observer observer; + + ObserverOnNext(Observer observer) { + this.observer = observer; + } + + @Override + public void accept(T v) throws Exception { + observer.onNext(v); + } + } + + static final class ObserverOnError implements Consumer { + final Observer observer; + + ObserverOnError(Observer observer) { + this.observer = observer; + } + + @Override + public void accept(Throwable v) throws Exception { + observer.onError(v); + } + } + + static final class ObserverOnComplete implements Action { + final Observer observer; + + ObserverOnComplete(Observer observer) { + this.observer = observer; + } + + @Override + public void run() throws Exception { + observer.onComplete(); + } + } + + public static Consumer observerOnNext(Observer observer) { + return new ObserverOnNext(observer); + } + + public static Consumer observerOnError(Observer observer) { + return new ObserverOnError(observer); + } + + public static Action observerOnComplete(Observer observer) { + return new ObserverOnComplete(observer); + } + + static final class FlatMapWithCombinerInner implements Function { + private final BiFunction combiner; + private final T t; + + FlatMapWithCombinerInner(BiFunction combiner, T t) { + this.combiner = combiner; + this.t = t; + } + + @Override + public R apply(U w) throws Exception { + return combiner.apply(t, w); + } + } + + static final class FlatMapWithCombinerOuter implements Function> { + private final BiFunction combiner; + private final Function> mapper; + + FlatMapWithCombinerOuter(BiFunction combiner, + Function> mapper) { + this.combiner = combiner; + this.mapper = mapper; + } + + @Override + public ObservableSource apply(final T t) throws Exception { + @SuppressWarnings("unchecked") + ObservableSource u = (ObservableSource)ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null ObservableSource"); + return new ObservableMap(u, new FlatMapWithCombinerInner(combiner, t)); + } + } + + public static Function> flatMapWithCombiner( + final Function> mapper, + final BiFunction combiner) { + return new FlatMapWithCombinerOuter(combiner, mapper); + } + + static final class FlatMapIntoIterable implements Function> { + private final Function> mapper; + + FlatMapIntoIterable(Function> mapper) { + this.mapper = mapper; + } + + @Override + public ObservableSource apply(T t) throws Exception { + return new ObservableFromIterable(ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Iterable")); + } + } + + public static Function> flatMapIntoIterable(final Function> mapper) { + return new FlatMapIntoIterable(mapper); + } + + enum MapToInt implements Function { + INSTANCE; + @Override + public Object apply(Object t) throws Exception { + return 0; + } + } + + public static Callable> replayCallable(final Observable parent) { + return new ReplayCallable(parent); + } + + public static Callable> replayCallable(final Observable parent, final int bufferSize) { + return new BufferedReplayCallable(parent, bufferSize); + } + + public static Callable> replayCallable(final Observable parent, final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { + return new BufferedTimedReplayCallable(parent, bufferSize, time, unit, scheduler); + } + + public static Callable> replayCallable(final Observable parent, final long time, final TimeUnit unit, final Scheduler scheduler) { + return new TimedReplayCallable(parent, time, unit, scheduler); + } + + public static Function, ObservableSource> replayFunction(final Function, ? extends ObservableSource> selector, final Scheduler scheduler) { + return new ReplayFunction(selector, scheduler); + } + + static final class ZipIterableFunction + implements Function>, ObservableSource> { + private final Function zipper; + + ZipIterableFunction(Function zipper) { + this.zipper = zipper; + } + + @Override + public ObservableSource apply(List> list) { + return Observable.zipIterable(list, zipper, false, Observable.bufferSize()); + } + } + + public static Function>, ObservableSource> zipIterable(final Function zipper) { + return new ZipIterableFunction(zipper); + } + + static final class ReplayCallable implements Callable> { + private final Observable parent; + + ReplayCallable(Observable parent) { + this.parent = parent; + } + + @Override + public ConnectableObservable call() { + return parent.replay(); + } + } + + static final class BufferedReplayCallable implements Callable> { + private final Observable parent; + private final int bufferSize; + + BufferedReplayCallable(Observable parent, int bufferSize) { + this.parent = parent; + this.bufferSize = bufferSize; + } + + @Override + public ConnectableObservable call() { + return parent.replay(bufferSize); + } + } + + static final class BufferedTimedReplayCallable implements Callable> { + private final Observable parent; + private final int bufferSize; + private final long time; + private final TimeUnit unit; + private final Scheduler scheduler; + + BufferedTimedReplayCallable(Observable parent, int bufferSize, long time, TimeUnit unit, Scheduler scheduler) { + this.parent = parent; + this.bufferSize = bufferSize; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public ConnectableObservable call() { + return parent.replay(bufferSize, time, unit, scheduler); + } + } + + static final class TimedReplayCallable implements Callable> { + private final Observable parent; + private final long time; + private final TimeUnit unit; + private final Scheduler scheduler; + + TimedReplayCallable(Observable parent, long time, TimeUnit unit, Scheduler scheduler) { + this.parent = parent; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public ConnectableObservable call() { + return parent.replay(time, unit, scheduler); + } + } + + static final class ReplayFunction implements Function, ObservableSource> { + private final Function, ? extends ObservableSource> selector; + private final Scheduler scheduler; + + ReplayFunction(Function, ? extends ObservableSource> selector, Scheduler scheduler) { + this.selector = selector; + this.scheduler = scheduler; + } + + @Override + public ObservableSource apply(Observable t) throws Exception { + ObservableSource apply = ObjectHelper.requireNonNull(selector.apply(t), "The selector returned a null ObservableSource"); + return Observable.wrap(apply).observeOn(scheduler); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java new file mode 100755 index 0000000..947a394 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableInterval.java @@ -0,0 +1,90 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.schedulers.TrampolineScheduler; + +public final class ObservableInterval extends Observable { + final Scheduler scheduler; + final long initialDelay; + final long period; + final TimeUnit unit; + + public ObservableInterval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + this.initialDelay = initialDelay; + this.period = period; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void subscribeActual(Observer observer) { + IntervalObserver is = new IntervalObserver(observer); + observer.onSubscribe(is); + + Scheduler sch = scheduler; + + if (sch instanceof TrampolineScheduler) { + Worker worker = sch.createWorker(); + is.setResource(worker); + worker.schedulePeriodically(is, initialDelay, period, unit); + } else { + Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit); + is.setResource(d); + } + } + + static final class IntervalObserver + extends AtomicReference + implements Disposable, Runnable { + + private static final long serialVersionUID = 346773832286157679L; + + final Observer downstream; + + long count; + + IntervalObserver(Observer downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + + @Override + public void run() { + if (get() != DisposableHelper.DISPOSED) { + downstream.onNext(count++); + } + } + + public void setResource(Disposable d) { + DisposableHelper.setOnce(this, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java new file mode 100755 index 0000000..5d48d1d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableIntervalRange.java @@ -0,0 +1,107 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.schedulers.TrampolineScheduler; + +public final class ObservableIntervalRange extends Observable { + final Scheduler scheduler; + final long start; + final long end; + final long initialDelay; + final long period; + final TimeUnit unit; + + public ObservableIntervalRange(long start, long end, long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + this.initialDelay = initialDelay; + this.period = period; + this.unit = unit; + this.scheduler = scheduler; + this.start = start; + this.end = end; + } + + @Override + public void subscribeActual(Observer observer) { + IntervalRangeObserver is = new IntervalRangeObserver(observer, start, end); + observer.onSubscribe(is); + + Scheduler sch = scheduler; + + if (sch instanceof TrampolineScheduler) { + Worker worker = sch.createWorker(); + is.setResource(worker); + worker.schedulePeriodically(is, initialDelay, period, unit); + } else { + Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit); + is.setResource(d); + } + } + + static final class IntervalRangeObserver + extends AtomicReference + implements Disposable, Runnable { + + private static final long serialVersionUID = 1891866368734007884L; + + final Observer downstream; + final long end; + + long count; + + IntervalRangeObserver(Observer actual, long start, long end) { + this.downstream = actual; + this.count = start; + this.end = end; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + + @Override + public void run() { + if (!isDisposed()) { + long c = count; + downstream.onNext(c); + + if (c == end) { + DisposableHelper.dispose(this); + downstream.onComplete(); + return; + } + + count = c + 1; + + } + } + + public void setResource(Disposable d) { + DisposableHelper.setOnce(this, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java new file mode 100755 index 0000000..9a293ca --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableJoin.java @@ -0,0 +1,361 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.atomic.*; + +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.observable.ObservableGroupJoin.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableJoin extends AbstractObservableWithUpstream { + + final ObservableSource other; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction resultSelector; + + public ObservableJoin( + ObservableSource source, + ObservableSource other, + Function> leftEnd, + Function> rightEnd, + BiFunction resultSelector) { + super(source); + this.other = other; + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + } + + @Override + protected void subscribeActual(Observer observer) { + + JoinDisposable parent = + new JoinDisposable( + observer, leftEnd, rightEnd, resultSelector); + + observer.onSubscribe(parent); + + LeftRightObserver left = new LeftRightObserver(parent, true); + parent.disposables.add(left); + LeftRightObserver right = new LeftRightObserver(parent, false); + parent.disposables.add(right); + + source.subscribe(left); + other.subscribe(right); + } + + static final class JoinDisposable + extends AtomicInteger implements Disposable, JoinSupport { + + private static final long serialVersionUID = -6071216598687999801L; + + final Observer downstream; + + final SpscLinkedArrayQueue queue; + + final CompositeDisposable disposables; + + final Map lefts; + + final Map rights; + + final AtomicReference error; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction resultSelector; + + final AtomicInteger active; + + int leftIndex; + + int rightIndex; + + volatile boolean cancelled; + + static final Integer LEFT_VALUE = 1; + + static final Integer RIGHT_VALUE = 2; + + static final Integer LEFT_CLOSE = 3; + + static final Integer RIGHT_CLOSE = 4; + + JoinDisposable(Observer actual, + Function> leftEnd, + Function> rightEnd, + BiFunction resultSelector) { + this.downstream = actual; + this.disposables = new CompositeDisposable(); + this.queue = new SpscLinkedArrayQueue(bufferSize()); + this.lefts = new LinkedHashMap(); + this.rights = new LinkedHashMap(); + this.error = new AtomicReference(); + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + this.active = new AtomicInteger(2); + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + cancelAll(); + if (getAndIncrement() == 0) { + queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void cancelAll() { + disposables.dispose(); + } + + void errorAll(Observer a) { + Throwable ex = ExceptionHelper.terminate(error); + + lefts.clear(); + rights.clear(); + + a.onError(ex); + } + + void fail(Throwable exc, Observer a, SpscLinkedArrayQueue q) { + Exceptions.throwIfFatal(exc); + ExceptionHelper.addThrowable(error, exc); + q.clear(); + cancelAll(); + errorAll(a); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + SpscLinkedArrayQueue q = queue; + Observer a = downstream; + + for (;;) { + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + boolean d = active.get() == 0; + + Integer mode = (Integer)q.poll(); + + boolean empty = mode == null; + + if (d && empty) { + + lefts.clear(); + rights.clear(); + disposables.dispose(); + + a.onComplete(); + return; + } + + if (empty) { + break; + } + + Object val = q.poll(); + + if (mode == LEFT_VALUE) { + @SuppressWarnings("unchecked") + TLeft left = (TLeft)val; + + int idx = leftIndex++; + lefts.put(idx, left); + + ObservableSource p; + + try { + p = ObjectHelper.requireNonNull(leftEnd.apply(left), "The leftEnd returned a null ObservableSource"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + LeftRightEndObserver end = new LeftRightEndObserver(this, true, idx); + disposables.add(end); + + p.subscribe(end); + + ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + for (TRight right : rights.values()) { + + R w; + + try { + w = ObjectHelper.requireNonNull(resultSelector.apply(left, right), "The resultSelector returned a null value"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + a.onNext(w); + } + } + else if (mode == RIGHT_VALUE) { + @SuppressWarnings("unchecked") + TRight right = (TRight)val; + + int idx = rightIndex++; + + rights.put(idx, right); + + ObservableSource p; + + try { + p = ObjectHelper.requireNonNull(rightEnd.apply(right), "The rightEnd returned a null ObservableSource"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + LeftRightEndObserver end = new LeftRightEndObserver(this, false, idx); + disposables.add(end); + + p.subscribe(end); + + ex = error.get(); + if (ex != null) { + q.clear(); + cancelAll(); + errorAll(a); + return; + } + + for (TLeft left : lefts.values()) { + + R w; + + try { + w = ObjectHelper.requireNonNull(resultSelector.apply(left, right), "The resultSelector returned a null value"); + } catch (Throwable exc) { + fail(exc, a, q); + return; + } + + a.onNext(w); + } + } + else if (mode == LEFT_CLOSE) { + LeftRightEndObserver end = (LeftRightEndObserver)val; + + lefts.remove(end.index); + disposables.remove(end); + } else { + LeftRightEndObserver end = (LeftRightEndObserver)val; + + rights.remove(end.index); + disposables.remove(end); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void innerError(Throwable ex) { + if (ExceptionHelper.addThrowable(error, ex)) { + active.decrementAndGet(); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void innerComplete(LeftRightObserver sender) { + disposables.delete(sender); + active.decrementAndGet(); + drain(); + } + + @Override + public void innerValue(boolean isLeft, Object o) { + synchronized (this) { + queue.offer(isLeft ? LEFT_VALUE : RIGHT_VALUE, o); + } + drain(); + } + + @Override + public void innerClose(boolean isLeft, LeftRightEndObserver index) { + synchronized (this) { + queue.offer(isLeft ? LEFT_CLOSE : RIGHT_CLOSE, index); + } + drain(); + } + + @Override + public void innerCloseError(Throwable ex) { + if (ExceptionHelper.addThrowable(error, ex)) { + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableJust.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableJust.java new file mode 100755 index 0000000..0ae53fb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableJust.java @@ -0,0 +1,42 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.internal.fuseable.ScalarCallable; +import io.reactivex.internal.operators.observable.ObservableScalarXMap.ScalarDisposable; + +/** + * Represents a constant scalar value. + * @param the value type + */ +public final class ObservableJust extends Observable implements ScalarCallable { + + private final T value; + public ObservableJust(final T value) { + this.value = value; + } + + @Override + protected void subscribeActual(Observer observer) { + ScalarDisposable sd = new ScalarDisposable(observer, value); + observer.onSubscribe(sd); + sd.run(); + } + + @Override + public T call() { + return value; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableLastMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableLastMaybe.java new file mode 100755 index 0000000..d6b0da1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableLastMaybe.java @@ -0,0 +1,97 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Consumes the source ObservableSource and emits its last item, the defaultItem + * if empty or a NoSuchElementException if even the defaultItem is null. + * + * @param the value type + */ +public final class ObservableLastMaybe extends Maybe { + + final ObservableSource source; + + public ObservableLastMaybe(ObservableSource source) { + this.source = source; + } + + // TODO fuse back to Observable + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new LastObserver(observer)); + } + + static final class LastObserver implements Observer, Disposable { + + final MaybeObserver downstream; + + Disposable upstream; + + T item; + + LastObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream == DisposableHelper.DISPOSED; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + item = t; + } + + @Override + public void onError(Throwable t) { + upstream = DisposableHelper.DISPOSED; + item = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + T v = item; + if (v != null) { + item = null; + downstream.onSuccess(v); + } else { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableLastSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableLastSingle.java new file mode 100755 index 0000000..b1355f2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableLastSingle.java @@ -0,0 +1,110 @@ +/** + * 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.internal.operators.observable; + +import java.util.NoSuchElementException; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Consumes the source ObservableSource and emits its last item, the defaultItem + * if empty or a NoSuchElementException if even the defaultItem is null. + * + * @param the value type + */ +public final class ObservableLastSingle extends Single { + + final ObservableSource source; + + final T defaultItem; + + public ObservableLastSingle(ObservableSource source, T defaultItem) { + this.source = source; + this.defaultItem = defaultItem; + } + + // TODO fuse back to Observable + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new LastObserver(observer, defaultItem)); + } + + static final class LastObserver implements Observer, Disposable { + + final SingleObserver downstream; + + final T defaultItem; + + Disposable upstream; + + T item; + + LastObserver(SingleObserver actual, T defaultItem) { + this.downstream = actual; + this.defaultItem = defaultItem; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream == DisposableHelper.DISPOSED; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + item = t; + } + + @Override + public void onError(Throwable t) { + upstream = DisposableHelper.DISPOSED; + item = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + upstream = DisposableHelper.DISPOSED; + T v = item; + if (v != null) { + item = null; + downstream.onSuccess(v); + } else { + v = defaultItem; + if (v != null) { + downstream.onSuccess(v); + } else { + downstream.onError(new NoSuchElementException()); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableLift.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableLift.java new file mode 100755 index 0000000..8cdd918 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableLift.java @@ -0,0 +1,59 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Allows lifting operators into a chain of Observables. + * + *

By having a concrete ObservableSource as lift, operator fusing can now identify + * both the source and the operation inside it via casting, unlike the lambda version of this. + * + * @param the upstream value type + * @param the downstream parameter type + */ +public final class ObservableLift extends AbstractObservableWithUpstream { + /** The actual operator. */ + final ObservableOperator operator; + + public ObservableLift(ObservableSource source, ObservableOperator operator) { + super(source); + this.operator = operator; + } + + @Override + public void subscribeActual(Observer observer) { + Observer liftedObserver; + try { + liftedObserver = ObjectHelper.requireNonNull(operator.apply(observer), "Operator " + operator + " returned a null Observer"); + } catch (NullPointerException e) { // NOPMD + throw e; + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because no way to know if a Disposable has been set or not + // can't call onSubscribe because the call might have set a Disposable already + RxJavaPlugins.onError(e); + + NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS"); + npe.initCause(e); + throw npe; + } + + source.subscribe(liftedObserver); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java new file mode 100755 index 0000000..475963c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMap.java @@ -0,0 +1,77 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.BasicFuseableObserver; + +public final class ObservableMap extends AbstractObservableWithUpstream { + final Function function; + + public ObservableMap(ObservableSource source, Function function) { + super(source); + this.function = function; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new MapObserver(t, function)); + } + + static final class MapObserver extends BasicFuseableObserver { + final Function mapper; + + MapObserver(Observer actual, Function mapper) { + super(actual); + this.mapper = mapper; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + if (sourceMode != NONE) { + downstream.onNext(null); + return; + } + + U v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value."); + } catch (Throwable ex) { + fail(ex); + return; + } + downstream.onNext(v); + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public U poll() throws Exception { + T t = qd.poll(); + return t != null ? ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value.") : null; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java new file mode 100755 index 0000000..f4418e9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMapNotification.java @@ -0,0 +1,131 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +public final class ObservableMapNotification extends AbstractObservableWithUpstream> { + + final Function> onNextMapper; + final Function> onErrorMapper; + final Callable> onCompleteSupplier; + + public ObservableMapNotification( + ObservableSource source, + Function> onNextMapper, + Function> onErrorMapper, + Callable> onCompleteSupplier) { + super(source); + this.onNextMapper = onNextMapper; + this.onErrorMapper = onErrorMapper; + this.onCompleteSupplier = onCompleteSupplier; + } + + @Override + public void subscribeActual(Observer> t) { + source.subscribe(new MapNotificationObserver(t, onNextMapper, onErrorMapper, onCompleteSupplier)); + } + + static final class MapNotificationObserver + implements Observer, Disposable { + final Observer> downstream; + final Function> onNextMapper; + final Function> onErrorMapper; + final Callable> onCompleteSupplier; + + Disposable upstream; + + MapNotificationObserver(Observer> actual, + Function> onNextMapper, + Function> onErrorMapper, + Callable> onCompleteSupplier) { + this.downstream = actual; + this.onNextMapper = onNextMapper; + this.onErrorMapper = onErrorMapper; + this.onCompleteSupplier = onCompleteSupplier; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + ObservableSource p; + + try { + p = ObjectHelper.requireNonNull(onNextMapper.apply(t), "The onNext ObservableSource returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + + downstream.onNext(p); + } + + @Override + public void onError(Throwable t) { + ObservableSource p; + + try { + p = ObjectHelper.requireNonNull(onErrorMapper.apply(t), "The onError ObservableSource returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + return; + } + + downstream.onNext(p); + downstream.onComplete(); + } + + @Override + public void onComplete() { + ObservableSource p; + + try { + p = ObjectHelper.requireNonNull(onCompleteSupplier.call(), "The onComplete ObservableSource returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + + downstream.onNext(p); + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java new file mode 100755 index 0000000..9cfe82b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMaterialize.java @@ -0,0 +1,78 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class ObservableMaterialize extends AbstractObservableWithUpstream> { + + public ObservableMaterialize(ObservableSource source) { + super(source); + } + + @Override + public void subscribeActual(Observer> t) { + source.subscribe(new MaterializeObserver(t)); + } + + static final class MaterializeObserver implements Observer, Disposable { + final Observer> downstream; + + Disposable upstream; + + MaterializeObserver(Observer> downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + downstream.onNext(Notification.createOnNext(t)); + } + + @Override + public void onError(Throwable t) { + Notification v = Notification.createOnError(t); + downstream.onNext(v); + downstream.onComplete(); + } + + @Override + public void onComplete() { + Notification v = Notification.createOnComplete(); + + downstream.onNext(v); + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java new file mode 100755 index 0000000..3b9e649 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java @@ -0,0 +1,145 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.*; + +/** + * Merges an Observable and a Completable by emitting the items of the Observable and waiting until + * both the Observable and Completable complete normally. + *

History: 2.1.10 - experimental + * @param the element type of the Observable + * @since 2.2 + */ +public final class ObservableMergeWithCompletable extends AbstractObservableWithUpstream { + + final CompletableSource other; + + public ObservableMergeWithCompletable(Observable source, CompletableSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer observer) { + MergeWithObserver parent = new MergeWithObserver(observer); + observer.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = -4592979584110982903L; + + final Observer downstream; + + final AtomicReference mainDisposable; + + final OtherObserver otherObserver; + + final AtomicThrowable error; + + volatile boolean mainDone; + + volatile boolean otherDone; + + MergeWithObserver(Observer downstream) { + this.downstream = downstream; + this.mainDisposable = new AtomicReference(); + this.otherObserver = new OtherObserver(this); + this.error = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(mainDisposable, d); + } + + @Override + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); + } + + @Override + public void onError(Throwable ex) { + DisposableHelper.dispose(otherObserver); + HalfSerializer.onError(downstream, ex, this, error); + } + + @Override + public void onComplete() { + mainDone = true; + if (otherDone) { + HalfSerializer.onComplete(downstream, this, error); + } + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(mainDisposable.get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(mainDisposable); + DisposableHelper.dispose(otherObserver); + } + + void otherError(Throwable ex) { + DisposableHelper.dispose(mainDisposable); + HalfSerializer.onError(downstream, ex, this, error); + } + + void otherComplete() { + otherDone = true; + if (mainDone) { + HalfSerializer.onComplete(downstream, this, error); + } + } + + static final class OtherObserver extends AtomicReference + implements CompletableObserver { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver parent; + + OtherObserver(MergeWithObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java new file mode 100755 index 0000000..e7caad3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java @@ -0,0 +1,266 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Merges an Observable and a Maybe by emitting the items of the Observable and the success + * value of the Maybe and waiting until both the Observable and Maybe terminate normally. + *

History: 2.1.10 - experimental + * @param the element type of the Observable + * @since 2.2 + */ +public final class ObservableMergeWithMaybe extends AbstractObservableWithUpstream { + + final MaybeSource other; + + public ObservableMergeWithMaybe(Observable source, MaybeSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer observer) { + MergeWithObserver parent = new MergeWithObserver(observer); + observer.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = -4592979584110982903L; + + final Observer downstream; + + final AtomicReference mainDisposable; + + final OtherObserver otherObserver; + + final AtomicThrowable error; + + volatile SimplePlainQueue queue; + + T singleItem; + + volatile boolean disposed; + + volatile boolean mainDone; + + volatile int otherState; + + static final int OTHER_STATE_HAS_VALUE = 1; + + static final int OTHER_STATE_CONSUMED_OR_EMPTY = 2; + + MergeWithObserver(Observer downstream) { + this.downstream = downstream; + this.mainDisposable = new AtomicReference(); + this.otherObserver = new OtherObserver(this); + this.error = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(mainDisposable, d); + } + + @Override + public void onNext(T t) { + if (compareAndSet(0, 1)) { + downstream.onNext(t); + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue q = getOrCreateQueue(); + q.offer(t); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(otherObserver); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onComplete() { + mainDone = true; + drain(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(mainDisposable.get()); + } + + @Override + public void dispose() { + disposed = true; + DisposableHelper.dispose(mainDisposable); + DisposableHelper.dispose(otherObserver); + if (getAndIncrement() == 0) { + queue = null; + singleItem = null; + } + } + + void otherSuccess(T value) { + if (compareAndSet(0, 1)) { + downstream.onNext(value); + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + void otherError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(mainDisposable); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + void otherComplete() { + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + drain(); + } + + SimplePlainQueue getOrCreateQueue() { + SimplePlainQueue q = queue; + if (q == null) { + q = new SpscLinkedArrayQueue(bufferSize()); + queue = q; + } + return q; + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + Observer actual = this.downstream; + int missed = 1; + for (;;) { + + for (;;) { + if (disposed) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + int os = otherState; + if (os == OTHER_STATE_HAS_VALUE) { + T v = singleItem; + singleItem = null; + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + os = OTHER_STATE_CONSUMED_OR_EMPTY; + actual.onNext(v); + } + + boolean d = mainDone; + SimplePlainQueue q = queue; + T v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty && os == OTHER_STATE_CONSUMED_OR_EMPTY) { + queue = null; + actual.onComplete(); + return; + } + + if (empty) { + break; + } + + actual.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class OtherObserver extends AtomicReference + implements MaybeObserver { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver parent; + + OtherObserver(MergeWithObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + parent.otherSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + @Override + public void onComplete() { + parent.otherComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java new file mode 100755 index 0000000..7332a29 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java @@ -0,0 +1,257 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Merges an Observable and a Single by emitting the items of the Observable and the success + * value of the Single and waiting until both the Observable and Single terminate normally. + *

History: 2.1.10 - experimental + * @param the element type of the Observable + * @since 2.2 + */ +public final class ObservableMergeWithSingle extends AbstractObservableWithUpstream { + + final SingleSource other; + + public ObservableMergeWithSingle(Observable source, SingleSource other) { + super(source); + this.other = other; + } + + @Override + protected void subscribeActual(Observer observer) { + MergeWithObserver parent = new MergeWithObserver(observer); + observer.onSubscribe(parent); + source.subscribe(parent); + other.subscribe(parent.otherObserver); + } + + static final class MergeWithObserver extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = -4592979584110982903L; + + final Observer downstream; + + final AtomicReference mainDisposable; + + final OtherObserver otherObserver; + + final AtomicThrowable error; + + volatile SimplePlainQueue queue; + + T singleItem; + + volatile boolean disposed; + + volatile boolean mainDone; + + volatile int otherState; + + static final int OTHER_STATE_HAS_VALUE = 1; + + static final int OTHER_STATE_CONSUMED_OR_EMPTY = 2; + + MergeWithObserver(Observer downstream) { + this.downstream = downstream; + this.mainDisposable = new AtomicReference(); + this.otherObserver = new OtherObserver(this); + this.error = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(mainDisposable, d); + } + + @Override + public void onNext(T t) { + if (compareAndSet(0, 1)) { + downstream.onNext(t); + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue q = getOrCreateQueue(); + q.offer(t); + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(otherObserver); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onComplete() { + mainDone = true; + drain(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(mainDisposable.get()); + } + + @Override + public void dispose() { + disposed = true; + DisposableHelper.dispose(mainDisposable); + DisposableHelper.dispose(otherObserver); + if (getAndIncrement() == 0) { + queue = null; + singleItem = null; + } + } + + void otherSuccess(T value) { + if (compareAndSet(0, 1)) { + downstream.onNext(value); + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + } else { + singleItem = value; + otherState = OTHER_STATE_HAS_VALUE; + if (getAndIncrement() != 0) { + return; + } + } + drainLoop(); + } + + void otherError(Throwable ex) { + if (error.addThrowable(ex)) { + DisposableHelper.dispose(mainDisposable); + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + + SimplePlainQueue getOrCreateQueue() { + SimplePlainQueue q = queue; + if (q == null) { + q = new SpscLinkedArrayQueue(bufferSize()); + queue = q; + } + return q; + } + + void drain() { + if (getAndIncrement() == 0) { + drainLoop(); + } + } + + void drainLoop() { + Observer actual = this.downstream; + int missed = 1; + for (;;) { + + for (;;) { + if (disposed) { + singleItem = null; + queue = null; + return; + } + + if (error.get() != null) { + singleItem = null; + queue = null; + actual.onError(error.terminate()); + return; + } + + int os = otherState; + if (os == OTHER_STATE_HAS_VALUE) { + T v = singleItem; + singleItem = null; + otherState = OTHER_STATE_CONSUMED_OR_EMPTY; + os = OTHER_STATE_CONSUMED_OR_EMPTY; + actual.onNext(v); + } + + boolean d = mainDone; + SimplePlainQueue q = queue; + T v = q != null ? q.poll() : null; + boolean empty = v == null; + + if (d && empty && os == OTHER_STATE_CONSUMED_OR_EMPTY) { + queue = null; + actual.onComplete(); + return; + } + + if (empty) { + break; + } + + actual.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class OtherObserver extends AtomicReference + implements SingleObserver { + + private static final long serialVersionUID = -2935427570954647017L; + + final MergeWithObserver parent; + + OtherObserver(MergeWithObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + parent.otherSuccess(t); + } + + @Override + public void onError(Throwable e) { + parent.otherError(e); + } + + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableNever.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableNever.java new file mode 100755 index 0000000..db84913 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableNever.java @@ -0,0 +1,29 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.internal.disposables.EmptyDisposable; + +public final class ObservableNever extends Observable { + public static final Observable INSTANCE = new ObservableNever(); + + private ObservableNever() { + } + + @Override + protected void subscribeActual(Observer o) { + o.onSubscribe(EmptyDisposable.NEVER); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java new file mode 100755 index 0000000..56de300 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java @@ -0,0 +1,321 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.observers.BasicIntQueueDisposable; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.schedulers.TrampolineScheduler; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableObserveOn extends AbstractObservableWithUpstream { + final Scheduler scheduler; + final boolean delayError; + final int bufferSize; + public ObservableObserveOn(ObservableSource source, Scheduler scheduler, boolean delayError, int bufferSize) { + super(source); + this.scheduler = scheduler; + this.delayError = delayError; + this.bufferSize = bufferSize; + } + + @Override + protected void subscribeActual(Observer observer) { + if (scheduler instanceof TrampolineScheduler) { + source.subscribe(observer); + } else { + Scheduler.Worker w = scheduler.createWorker(); + + source.subscribe(new ObserveOnObserver(observer, w, delayError, bufferSize)); + } + } + + static final class ObserveOnObserver extends BasicIntQueueDisposable + implements Observer, Runnable { + + private static final long serialVersionUID = 6576896619930983584L; + final Observer downstream; + final Scheduler.Worker worker; + final boolean delayError; + final int bufferSize; + + SimpleQueue queue; + + Disposable upstream; + + Throwable error; + volatile boolean done; + + volatile boolean disposed; + + int sourceMode; + + boolean outputFused; + + ObserveOnObserver(Observer actual, Scheduler.Worker worker, boolean delayError, int bufferSize) { + this.downstream = actual; + this.worker = worker; + this.delayError = delayError; + this.bufferSize = bufferSize; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + if (d instanceof QueueDisposable) { + @SuppressWarnings("unchecked") + QueueDisposable qd = (QueueDisposable) d; + + int m = qd.requestFusion(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + + if (m == QueueDisposable.SYNC) { + sourceMode = m; + queue = qd; + done = true; + downstream.onSubscribe(this); + schedule(); + return; + } + if (m == QueueDisposable.ASYNC) { + sourceMode = m; + queue = qd; + downstream.onSubscribe(this); + return; + } + } + + queue = new SpscLinkedArrayQueue(bufferSize); + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + if (sourceMode != QueueDisposable.ASYNC) { + queue.offer(t); + } + schedule(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + schedule(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + schedule(); + } + + @Override + public void dispose() { + if (!disposed) { + disposed = true; + upstream.dispose(); + worker.dispose(); + if (!outputFused && getAndIncrement() == 0) { + queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + + void schedule() { + if (getAndIncrement() == 0) { + worker.schedule(this); + } + } + + void drainNormal() { + int missed = 1; + + final SimpleQueue q = queue; + final Observer a = downstream; + + for (;;) { + if (checkTerminated(done, q.isEmpty(), a)) { + return; + } + + for (;;) { + boolean d = done; + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + disposed = true; + upstream.dispose(); + q.clear(); + a.onError(ex); + worker.dispose(); + return; + } + boolean empty = v == null; + + if (checkTerminated(d, empty, a)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void drainFused() { + int missed = 1; + + for (;;) { + if (disposed) { + return; + } + + boolean d = done; + Throwable ex = error; + + if (!delayError && d && ex != null) { + disposed = true; + downstream.onError(error); + worker.dispose(); + return; + } + + downstream.onNext(null); + + if (d) { + disposed = true; + ex = error; + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onComplete(); + } + worker.dispose(); + return; + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void run() { + if (outputFused) { + drainFused(); + } else { + drainNormal(); + } + } + + boolean checkTerminated(boolean d, boolean empty, Observer a) { + if (disposed) { + queue.clear(); + return true; + } + if (d) { + Throwable e = error; + if (delayError) { + if (empty) { + disposed = true; + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + worker.dispose(); + return true; + } + } else { + if (e != null) { + disposed = true; + queue.clear(); + a.onError(e); + worker.dispose(); + return true; + } else + if (empty) { + disposed = true; + a.onComplete(); + worker.dispose(); + return true; + } + } + } + return false; + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Nullable + @Override + public T poll() throws Exception { + return queue.poll(); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorNext.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorNext.java new file mode 100755 index 0000000..649831d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorNext.java @@ -0,0 +1,118 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.SequentialDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableOnErrorNext extends AbstractObservableWithUpstream { + final Function> nextSupplier; + final boolean allowFatal; + + public ObservableOnErrorNext(ObservableSource source, + Function> nextSupplier, boolean allowFatal) { + super(source); + this.nextSupplier = nextSupplier; + this.allowFatal = allowFatal; + } + + @Override + public void subscribeActual(Observer t) { + OnErrorNextObserver parent = new OnErrorNextObserver(t, nextSupplier, allowFatal); + t.onSubscribe(parent.arbiter); + source.subscribe(parent); + } + + static final class OnErrorNextObserver implements Observer { + final Observer downstream; + final Function> nextSupplier; + final boolean allowFatal; + final SequentialDisposable arbiter; + + boolean once; + + boolean done; + + OnErrorNextObserver(Observer actual, Function> nextSupplier, boolean allowFatal) { + this.downstream = actual; + this.nextSupplier = nextSupplier; + this.allowFatal = allowFatal; + this.arbiter = new SequentialDisposable(); + } + + @Override + public void onSubscribe(Disposable d) { + arbiter.replace(d); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (once) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + downstream.onError(t); + return; + } + once = true; + + if (allowFatal && !(t instanceof Exception)) { + downstream.onError(t); + return; + } + + ObservableSource p; + + try { + p = nextSupplier.apply(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + return; + } + + if (p == null) { + NullPointerException npe = new NullPointerException("Observable is null"); + npe.initCause(t); + downstream.onError(npe); + return; + } + + p.subscribe(this); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + once = true; + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java new file mode 100755 index 0000000..b91e364 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableOnErrorReturn.java @@ -0,0 +1,95 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class ObservableOnErrorReturn extends AbstractObservableWithUpstream { + final Function valueSupplier; + public ObservableOnErrorReturn(ObservableSource source, Function valueSupplier) { + super(source); + this.valueSupplier = valueSupplier; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new OnErrorReturnObserver(t, valueSupplier)); + } + + static final class OnErrorReturnObserver implements Observer, Disposable { + final Observer downstream; + final Function valueSupplier; + + Disposable upstream; + + OnErrorReturnObserver(Observer actual, Function valueSupplier) { + this.downstream = actual; + this.valueSupplier = valueSupplier; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + T v; + try { + v = valueSupplier.apply(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + return; + } + + if (v == null) { + NullPointerException e = new NullPointerException("The supplied value is null"); + e.initCause(t); + downstream.onError(e); + return; + } + + downstream.onNext(v); + downstream.onComplete(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java new file mode 100755 index 0000000..04b9050 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java @@ -0,0 +1,391 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.HasUpstreamObservableSource; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * A connectable observable which shares an underlying source and dispatches source values to observers in a backpressure-aware + * manner. + * @param the value type + */ +public final class ObservablePublish extends ConnectableObservable +implements HasUpstreamObservableSource, ObservablePublishClassic { + /** The source observable. */ + final ObservableSource source; + /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ + final AtomicReference> current; + + final ObservableSource onSubscribe; + + /** + * Creates a OperatorPublish instance to publish values of the given source observable. + * @param the source value type + * @param source the source observable + * @return the connectable observable + */ + public static ConnectableObservable create(ObservableSource source) { + // the current connection to source needs to be shared between the operator and its onSubscribe call + final AtomicReference> curr = new AtomicReference>(); + ObservableSource onSubscribe = new PublishSource(curr); + return RxJavaPlugins.onAssembly(new ObservablePublish(onSubscribe, source, curr)); + } + + private ObservablePublish(ObservableSource onSubscribe, ObservableSource source, + final AtomicReference> current) { + this.onSubscribe = onSubscribe; + this.source = source; + this.current = current; + } + + @Override + public ObservableSource source() { + return source; + } + + @Override + public ObservableSource publishSource() { + return source; + } + + @Override + protected void subscribeActual(Observer observer) { + onSubscribe.subscribe(observer); + } + + @Override + public void connect(Consumer connection) { + boolean doConnect; + PublishObserver ps; + // we loop because concurrent connect/disconnect and termination may change the state + for (;;) { + // retrieve the current subscriber-to-source instance + ps = current.get(); + // if there is none yet or the current has been disposed + if (ps == null || ps.isDisposed()) { + // create a new subscriber-to-source + PublishObserver u = new PublishObserver(current); + // try setting it as the current subscriber-to-source + if (!current.compareAndSet(ps, u)) { + // did not work, perhaps a new subscriber arrived + // and created a new subscriber-to-source as well, retry + continue; + } + ps = u; + } + // if connect() was called concurrently, only one of them should actually + // connect to the source + doConnect = !ps.shouldConnect.get() && ps.shouldConnect.compareAndSet(false, true); + break; // NOPMD + } + /* + * Notify the callback that we have a (new) connection which it can dispose + * but since ps is unique to a connection, multiple calls to connect() will return the + * same Disposable and even if there was a connect-disconnect-connect pair, the older + * references won't disconnect the newer connection. + * Synchronous source consumers have the opportunity to disconnect via dispose on the + * Disposable as subscribe() may never return in its own. + * + * Note however, that asynchronously disconnecting a running source might leave + * child observers without any terminal event; PublishSubject does not have this + * issue because the dispose() was always triggered by the child observers + * themselves. + */ + try { + connection.accept(ps); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + if (doConnect) { + source.subscribe(ps); + } + } + + @SuppressWarnings("rawtypes") + static final class PublishObserver + implements Observer, Disposable { + /** Holds onto the current connected PublishObserver. */ + final AtomicReference> current; + + /** Indicates an empty array of inner observers. */ + static final InnerDisposable[] EMPTY = new InnerDisposable[0]; + /** Indicates a terminated PublishObserver. */ + static final InnerDisposable[] TERMINATED = new InnerDisposable[0]; + + /** Tracks the subscribed observers. */ + final AtomicReference[]> observers; + /** + * Atomically changed from false to true by connect to make sure the + * connection is only performed by one thread. + */ + final AtomicBoolean shouldConnect; + + final AtomicReference upstream = new AtomicReference(); + + @SuppressWarnings("unchecked") + PublishObserver(AtomicReference> current) { + this.observers = new AtomicReference[]>(EMPTY); + this.current = current; + this.shouldConnect = new AtomicBoolean(); + } + + @SuppressWarnings("unchecked") + @Override + public void dispose() { + InnerDisposable[] ps = observers.getAndSet(TERMINATED); + if (ps != TERMINATED) { + current.compareAndSet(PublishObserver.this, null); + + DisposableHelper.dispose(upstream); + } + } + + @Override + public boolean isDisposed() { + return observers.get() == TERMINATED; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); + } + + @Override + public void onNext(T t) { + for (InnerDisposable inner : observers.get()) { + inner.child.onNext(t); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable e) { + current.compareAndSet(this, null); + InnerDisposable[] a = observers.getAndSet(TERMINATED); + if (a.length != 0) { + for (InnerDisposable inner : a) { + inner.child.onError(e); + } + } else { + RxJavaPlugins.onError(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + current.compareAndSet(this, null); + for (InnerDisposable inner : observers.getAndSet(TERMINATED)) { + inner.child.onComplete(); + } + } + + /** + * Atomically try adding a new InnerDisposable to this Observer or return false if this + * Observer was terminated. + * @param producer the producer to add + * @return true if succeeded, false otherwise + */ + boolean add(InnerDisposable producer) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // get the current producer array + InnerDisposable[] c = observers.get(); + // if this subscriber-to-source reached a terminal state by receiving + // an onError or onComplete, just refuse to add the new producer + if (c == TERMINATED) { + return false; + } + // we perform a copy-on-write logic + int len = c.length; + @SuppressWarnings("unchecked") + InnerDisposable[] u = new InnerDisposable[len + 1]; + System.arraycopy(c, 0, u, 0, len); + u[len] = producer; + // try setting the observers array + if (observers.compareAndSet(c, u)) { + return true; + } + // if failed, some other operation succeeded (another add, remove or termination) + // so retry + } + } + + /** + * Atomically removes the given producer from the observers array. + * @param producer the producer to remove + */ + @SuppressWarnings("unchecked") + void remove(InnerDisposable producer) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // let's read the current observers array + InnerDisposable[] c = observers.get(); + // if it is either empty or terminated, there is nothing to remove so we quit + int len = c.length; + if (len == 0) { + return; + } + // let's find the supplied producer in the array + // although this is O(n), we don't expect too many child observers in general + int j = -1; + for (int i = 0; i < len; i++) { + if (c[i].equals(producer)) { + j = i; + break; + } + } + // we didn't find it so just quit + if (j < 0) { + return; + } + // we do copy-on-write logic here + InnerDisposable[] u; + // we don't create a new empty array if producer was the single inhabitant + // but rather reuse an empty array + if (len == 1) { + u = EMPTY; + } else { + // otherwise, create a new array one less in size + u = new InnerDisposable[len - 1]; + // copy elements being before the given producer + System.arraycopy(c, 0, u, 0, j); + // copy elements being after the given producer + System.arraycopy(c, j + 1, u, j, len - j - 1); + } + // try setting this new array as + if (observers.compareAndSet(c, u)) { + return; + } + // if we failed, it means something else happened + // (a concurrent add/remove or termination), we need to retry + } + } + } + /** + * A Disposable that manages the request and disposed state of a + * child Observer in thread-safe manner. + * {@code this} holds the parent PublishObserver or itself if disposed + * @param the value type + */ + static final class InnerDisposable + extends AtomicReference + implements Disposable { + private static final long serialVersionUID = -1100270633763673112L; + /** The actual child subscriber. */ + final Observer child; + + InnerDisposable(Observer child) { + this.child = child; + } + + @Override + public boolean isDisposed() { + return get() == this; + } + + @SuppressWarnings("unchecked") + @Override + public void dispose() { + Object o = getAndSet(this); + if (o != null && o != this) { + ((PublishObserver)o).remove(this); + } + } + + void setParent(PublishObserver p) { + if (!compareAndSet(null, p)) { + p.remove(this); + } + } + } + + static final class PublishSource implements ObservableSource { + private final AtomicReference> curr; + + PublishSource(AtomicReference> curr) { + this.curr = curr; + } + + @Override + public void subscribe(Observer child) { + // create the backpressure-managing producer for this child + InnerDisposable inner = new InnerDisposable(child); + child.onSubscribe(inner); + // concurrent connection/disconnection may change the state, + // we loop to be atomic while the child subscribes + for (;;) { + // get the current subscriber-to-source + PublishObserver r = curr.get(); + // if there isn't one or it is disposed + if (r == null || r.isDisposed()) { + // create a new subscriber to source + PublishObserver u = new PublishObserver(curr); + // let's try setting it as the current subscriber-to-source + if (!curr.compareAndSet(r, u)) { + // didn't work, maybe someone else did it or the current subscriber + // to source has just finished + continue; + } + // we won, let's use it going onwards + r = u; + } + + /* + * Try adding it to the current subscriber-to-source, add is atomic in respect + * to other adds and the termination of the subscriber-to-source. + */ + if (r.add(inner)) { + inner.setParent(r); + break; // NOPMD + } + /* + * The current PublishObserver has been terminated, try with a newer one. + */ + /* + * Note: although technically correct, concurrent disconnects can cause + * unexpected behavior such as child observers never receiving anything + * (unless connected again). An alternative approach, similar to + * PublishSubject would be to immediately terminate such child + * observers as well: + * + * Object term = r.terminalEvent; + * if (r.nl.isCompleted(term)) { + * child.onComplete(); + * } else { + * child.onError(r.nl.getError(term)); + * } + * return; + * + * The original concurrent behavior was non-deterministic in this regard as well. + * Allowing this behavior, however, may introduce another unexpected behavior: + * after disconnecting a previous connection, one might not be able to prepare + * a new connection right after a previous termination by subscribing new child + * observers asynchronously before a connect call. + */ + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishAlt.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishAlt.java new file mode 100755 index 0000000..a9e62ba --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishAlt.java @@ -0,0 +1,282 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.fuseable.HasUpstreamObservableSource; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.observables.ConnectableObservable; + +/** + * Shares a single underlying connection to the upstream ObservableSource + * and multicasts events to all subscribed observers until the upstream + * completes or the connection is disposed. + *

+ * The difference to ObservablePublish is that when the upstream terminates, + * late observers will receive that terminal event until the connection is + * disposed and the ConnectableObservable is reset to its fresh state. + * + * @param the element type + * @since 2.2.10 + */ +public final class ObservablePublishAlt extends ConnectableObservable +implements HasUpstreamObservableSource, ResettableConnectable { + + final ObservableSource source; + + final AtomicReference> current; + + public ObservablePublishAlt(ObservableSource source) { + this.source = source; + this.current = new AtomicReference>(); + } + + @Override + public void connect(Consumer connection) { + boolean doConnect = false; + PublishConnection conn; + + for (;;) { + conn = current.get(); + + if (conn == null || conn.isDisposed()) { + PublishConnection fresh = new PublishConnection(current); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + + doConnect = !conn.connect.get() && conn.connect.compareAndSet(false, true); + break; + } + + try { + connection.accept(conn); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + + if (doConnect) { + source.subscribe(conn); + } + } + + @Override + protected void subscribeActual(Observer observer) { + PublishConnection conn; + + for (;;) { + conn = current.get(); + // we don't create a fresh connection if the current is terminated + if (conn == null) { + PublishConnection fresh = new PublishConnection(current); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + break; + } + + InnerDisposable inner = new InnerDisposable(observer, conn); + observer.onSubscribe(inner); + if (conn.add(inner)) { + if (inner.isDisposed()) { + conn.remove(inner); + } + return; + } + // Late observers will be simply terminated + Throwable error = conn.error; + if (error != null) { + observer.onError(error); + } else { + observer.onComplete(); + } + } + + @Override + @SuppressWarnings("unchecked") + public void resetIf(Disposable connection) { + current.compareAndSet((PublishConnection)connection, null); + } + + @Override + public ObservableSource source() { + return source; + } + + static final class PublishConnection + extends AtomicReference[]> + implements Observer, Disposable { + + private static final long serialVersionUID = -3251430252873581268L; + + final AtomicBoolean connect; + + final AtomicReference> current; + + final AtomicReference upstream; + + @SuppressWarnings("rawtypes") + static final InnerDisposable[] EMPTY = new InnerDisposable[0]; + + @SuppressWarnings("rawtypes") + static final InnerDisposable[] TERMINATED = new InnerDisposable[0]; + + Throwable error; + + @SuppressWarnings("unchecked") + PublishConnection(AtomicReference> current) { + this.connect = new AtomicBoolean(); + this.current = current; + this.upstream = new AtomicReference(); + lazySet(EMPTY); + } + + @SuppressWarnings("unchecked") + @Override + public void dispose() { + getAndSet(TERMINATED); + current.compareAndSet(this, null); + DisposableHelper.dispose(upstream); + } + + @Override + public boolean isDisposed() { + return get() == TERMINATED; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); + } + + @Override + public void onNext(T t) { + for (InnerDisposable inner : get()) { + inner.downstream.onNext(t); + } + } + + @Override + @SuppressWarnings("unchecked") + public void onError(Throwable e) { + error = e; + upstream.lazySet(DisposableHelper.DISPOSED); + for (InnerDisposable inner : getAndSet(TERMINATED)) { + inner.downstream.onError(e); + } + } + + @Override + @SuppressWarnings("unchecked") + public void onComplete() { + upstream.lazySet(DisposableHelper.DISPOSED); + for (InnerDisposable inner : getAndSet(TERMINATED)) { + inner.downstream.onComplete(); + } + } + + public boolean add(InnerDisposable inner) { + for (;;) { + InnerDisposable[] a = get(); + if (a == TERMINATED) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + InnerDisposable[] b = new InnerDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + public void remove(InnerDisposable inner) { + for (;;) { + InnerDisposable[] a = get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + InnerDisposable[] b = EMPTY; + if (n != 1) { + b = new InnerDisposable[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (compareAndSet(a, b)) { + return; + } + } + } + } + + /** + * Intercepts the dispose signal from the downstream and + * removes itself from the connection's observers array + * at most once. + * @param the element type + */ + static final class InnerDisposable + extends AtomicReference> + implements Disposable { + + private static final long serialVersionUID = 7463222674719692880L; + + final Observer downstream; + + InnerDisposable(Observer downstream, PublishConnection parent) { + this.downstream = downstream; + lazySet(parent); + } + + @Override + public void dispose() { + PublishConnection p = getAndSet(null); + if (p != null) { + p.remove(this); + } + } + + @Override + public boolean isDisposed() { + return get() == null; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishClassic.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishClassic.java new file mode 100755 index 0000000..b2bfe87 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishClassic.java @@ -0,0 +1,37 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.ObservableSource; + +/** + * Interface to mark classic publish() operators to + * indicate refCount() should replace them with the Alt + * implementation. + *

+ * Without this, hooking the connectables with an intercept + * implementation would result in the unintended lack + * or presense of the replacement by refCount(). + * + * @param the element type of the sequence + * @since 2.2.10 + */ +public interface ObservablePublishClassic { + + /** + * The upstream source of this publish operator. + * @return the upstream source of this publish operator + */ + ObservableSource publishSource(); +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishSelector.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishSelector.java new file mode 100755 index 0000000..17824fb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishSelector.java @@ -0,0 +1,143 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.subjects.PublishSubject; + +/** + * Shares a source Observable for the duration of a selector function. + * @param the input value type + * @param the output value type + */ +public final class ObservablePublishSelector extends AbstractObservableWithUpstream { + + final Function, ? extends ObservableSource> selector; + + public ObservablePublishSelector(final ObservableSource source, + final Function, ? extends ObservableSource> selector) { + super(source); + this.selector = selector; + } + + @Override + protected void subscribeActual(Observer observer) { + PublishSubject subject = PublishSubject.create(); + + ObservableSource target; + + try { + target = ObjectHelper.requireNonNull(selector.apply(subject), "The selector returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + TargetObserver o = new TargetObserver(observer); + + target.subscribe(o); + + source.subscribe(new SourceObserver(subject, o)); + } + + static final class SourceObserver implements Observer { + + final PublishSubject subject; + + final AtomicReference target; + + SourceObserver(PublishSubject subject, AtomicReference target) { + this.subject = subject; + this.target = target; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(target, d); + } + + @Override + public void onNext(T value) { + subject.onNext(value); + } + + @Override + public void onError(Throwable e) { + subject.onError(e); + } + + @Override + public void onComplete() { + subject.onComplete(); + } + } + + static final class TargetObserver + extends AtomicReference implements Observer, Disposable { + private static final long serialVersionUID = 854110278590336484L; + + final Observer downstream; + + Disposable upstream; + + TargetObserver(Observer downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(R value) { + downstream.onNext(value); + } + + @Override + public void onError(Throwable e) { + DisposableHelper.dispose(this); + downstream.onError(e); + } + + @Override + public void onComplete() { + DisposableHelper.dispose(this); + downstream.onComplete(); + } + + @Override + public void dispose() { + upstream.dispose(); + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRange.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRange.java new file mode 100755 index 0000000..19cb08c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRange.java @@ -0,0 +1,114 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.observers.BasicIntQueueDisposable; + +/** + * Emits a range of integer values from start to end. + */ +public final class ObservableRange extends Observable { + private final int start; + private final long end; + + public ObservableRange(int start, int count) { + this.start = start; + this.end = (long)start + count; + } + + @Override + protected void subscribeActual(Observer o) { + RangeDisposable parent = new RangeDisposable(o, start, end); + o.onSubscribe(parent); + parent.run(); + } + + static final class RangeDisposable + extends BasicIntQueueDisposable { + + private static final long serialVersionUID = 396518478098735504L; + + final Observer downstream; + + final long end; + + long index; + + boolean fused; + + RangeDisposable(Observer actual, long start, long end) { + this.downstream = actual; + this.index = start; + this.end = end; + } + + void run() { + if (fused) { + return; + } + Observer actual = this.downstream; + long e = end; + for (long i = index; i != e && get() == 0; i++) { + actual.onNext((int)i); + } + if (get() == 0) { + lazySet(1); + actual.onComplete(); + } + } + + @Nullable + @Override + public Integer poll() throws Exception { + long i = index; + if (i != end) { + index = i + 1; + return (int)i; + } + lazySet(1); + return null; + } + + @Override + public boolean isEmpty() { + return index == end; + } + + @Override + public void clear() { + index = end; + lazySet(1); + } + + @Override + public void dispose() { + set(1); + } + + @Override + public boolean isDisposed() { + return get() != 0; + } + + @Override + public int requestFusion(int mode) { + if ((mode & SYNC) != 0) { + fused = true; + return SYNC; + } + return NONE; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRangeLong.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRangeLong.java new file mode 100755 index 0000000..55d23d2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRangeLong.java @@ -0,0 +1,111 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.observers.BasicIntQueueDisposable; + +public final class ObservableRangeLong extends Observable { + private final long start; + private final long count; + + public ObservableRangeLong(long start, long count) { + this.start = start; + this.count = count; + } + + @Override + protected void subscribeActual(Observer o) { + RangeDisposable parent = new RangeDisposable(o, start, start + count); + o.onSubscribe(parent); + parent.run(); + } + + static final class RangeDisposable + extends BasicIntQueueDisposable { + + private static final long serialVersionUID = 396518478098735504L; + + final Observer downstream; + + final long end; + + long index; + + boolean fused; + + RangeDisposable(Observer actual, long start, long end) { + this.downstream = actual; + this.index = start; + this.end = end; + } + + void run() { + if (fused) { + return; + } + Observer actual = this.downstream; + long e = end; + for (long i = index; i != e && get() == 0; i++) { + actual.onNext(i); + } + if (get() == 0) { + lazySet(1); + actual.onComplete(); + } + } + + @Nullable + @Override + public Long poll() throws Exception { + long i = index; + if (i != end) { + index = i + 1; + return i; + } + lazySet(1); + return null; + } + + @Override + public boolean isEmpty() { + return index == end; + } + + @Override + public void clear() { + index = end; + lazySet(1); + } + + @Override + public void dispose() { + set(1); + } + + @Override + public boolean isDisposed() { + return get() != 0; + } + + @Override + public int requestFusion(int mode) { + if ((mode & SYNC) != 0) { + fused = true; + return SYNC; + } + return NONE; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceMaybe.java new file mode 100755 index 0000000..63d0e6c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceMaybe.java @@ -0,0 +1,127 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Reduce a sequence of values into a single value via an aggregator function and emit the final value or complete + * if the source is empty. + * + * @param the source and result value type + */ +public final class ObservableReduceMaybe extends Maybe { + + final ObservableSource source; + + final BiFunction reducer; + + public ObservableReduceMaybe(ObservableSource source, BiFunction reducer) { + this.source = source; + this.reducer = reducer; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new ReduceObserver(observer, reducer)); + } + + static final class ReduceObserver implements Observer, Disposable { + + final MaybeObserver downstream; + + final BiFunction reducer; + + boolean done; + + T value; + + Disposable upstream; + + ReduceObserver(MaybeObserver observer, BiFunction reducer) { + this.downstream = observer; + this.reducer = reducer; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T value) { + if (!done) { + T v = this.value; + + if (v == null) { + this.value = value; + } else { + try { + this.value = ObjectHelper.requireNonNull(reducer.apply(v, value), "The reducer returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + } + } + } + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaPlugins.onError(e); + return; + } + done = true; + value = null; + downstream.onError(e); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + T v = value; + value = null; + if (v != null) { + downstream.onSuccess(v); + } else { + downstream.onComplete(); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceSeedSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceSeedSingle.java new file mode 100755 index 0000000..6a11b91 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceSeedSingle.java @@ -0,0 +1,119 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Reduce a sequence of values, starting from a seed value and by using + * an accumulator function and return the last accumulated value. + * + * @param the source value type + * @param the accumulated result type + */ +public final class ObservableReduceSeedSingle extends Single { + + final ObservableSource source; + + final R seed; + + final BiFunction reducer; + + public ObservableReduceSeedSingle(ObservableSource source, R seed, BiFunction reducer) { + this.source = source; + this.seed = seed; + this.reducer = reducer; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new ReduceSeedObserver(observer, reducer, seed)); + } + + static final class ReduceSeedObserver implements Observer, Disposable { + + final SingleObserver downstream; + + final BiFunction reducer; + + R value; + + Disposable upstream; + + ReduceSeedObserver(SingleObserver actual, BiFunction reducer, R value) { + this.downstream = actual; + this.value = value; + this.reducer = reducer; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T value) { + R v = this.value; + if (v != null) { + try { + this.value = ObjectHelper.requireNonNull(reducer.apply(v, value), "The reducer returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + } + } + } + + @Override + public void onError(Throwable e) { + R v = value; + if (v != null) { + value = null; + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + R v = value; + if (v != null) { + value = null; + downstream.onSuccess(v); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceWithSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceWithSingle.java new file mode 100755 index 0000000..bf492b5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableReduceWithSingle.java @@ -0,0 +1,59 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.observable.ObservableReduceSeedSingle.ReduceSeedObserver; + +/** + * Reduce a sequence of values, starting from a generated seed value and by using + * an accumulator function and return the last accumulated value. + * + * @param the source value type + * @param the accumulated result type + */ +public final class ObservableReduceWithSingle extends Single { + + final ObservableSource source; + + final Callable seedSupplier; + + final BiFunction reducer; + + public ObservableReduceWithSingle(ObservableSource source, Callable seedSupplier, BiFunction reducer) { + this.source = source; + this.seedSupplier = seedSupplier; + this.reducer = reducer; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + R seed; + + try { + seed = ObjectHelper.requireNonNull(seedSupplier.call(), "The seedSupplier returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + source.subscribe(new ReduceSeedObserver(observer, reducer, seed)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java new file mode 100755 index 0000000..27e633c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java @@ -0,0 +1,270 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.*; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Returns an observable sequence that stays connected to the source as long as + * there is at least one subscription to the observable sequence. + * + * @param + * the value type + */ +public final class ObservableRefCount extends Observable { + + final ConnectableObservable source; + + final int n; + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + RefConnection connection; + + public ObservableRefCount(ConnectableObservable source) { + this(source, 1, 0L, TimeUnit.NANOSECONDS, null); + } + + public ObservableRefCount(ConnectableObservable source, int n, long timeout, TimeUnit unit, + Scheduler scheduler) { + this.source = source; + this.n = n; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(Observer observer) { + + RefConnection conn; + + boolean connect = false; + synchronized (this) { + conn = connection; + if (conn == null) { + conn = new RefConnection(this); + connection = conn; + } + + long c = conn.subscriberCount; + if (c == 0L && conn.timer != null) { + conn.timer.dispose(); + } + conn.subscriberCount = c + 1; + if (!conn.connected && c + 1 == n) { + connect = true; + conn.connected = true; + } + } + + source.subscribe(new RefCountObserver(observer, this, conn)); + + if (connect) { + source.connect(conn); + } + } + + void cancel(RefConnection rc) { + SequentialDisposable sd; + synchronized (this) { + if (connection == null || connection != rc) { + return; + } + long c = rc.subscriberCount - 1; + rc.subscriberCount = c; + if (c != 0L || !rc.connected) { + return; + } + if (timeout == 0L) { + timeout(rc); + return; + } + sd = new SequentialDisposable(); + rc.timer = sd; + } + + sd.replace(scheduler.scheduleDirect(rc, timeout, unit)); + } + + void terminated(RefConnection rc) { + synchronized (this) { + if (source instanceof ObservablePublishClassic) { + if (connection != null && connection == rc) { + connection = null; + clearTimer(rc); + } + + if (--rc.subscriberCount == 0) { + reset(rc); + } + } else { + if (connection != null && connection == rc) { + clearTimer(rc); + if (--rc.subscriberCount == 0) { + connection = null; + reset(rc); + } + } + } + } + } + + void clearTimer(RefConnection rc) { + if (rc.timer != null) { + rc.timer.dispose(); + rc.timer = null; + } + } + + void reset(RefConnection rc) { + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + ((ResettableConnectable)source).resetIf(rc.get()); + } + } + + void timeout(RefConnection rc) { + synchronized (this) { + if (rc.subscriberCount == 0 && rc == connection) { + connection = null; + Disposable connectionObject = rc.get(); + DisposableHelper.dispose(rc); + + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + if (connectionObject == null) { + rc.disconnectedEarly = true; + } else { + ((ResettableConnectable)source).resetIf(connectionObject); + } + } + } + } + } + + static final class RefConnection extends AtomicReference + implements Runnable, Consumer { + + private static final long serialVersionUID = -4552101107598366241L; + + final ObservableRefCount parent; + + Disposable timer; + + long subscriberCount; + + boolean connected; + + boolean disconnectedEarly; + + RefConnection(ObservableRefCount parent) { + this.parent = parent; + } + + @Override + public void run() { + parent.timeout(this); + } + + @Override + public void accept(Disposable t) throws Exception { + DisposableHelper.replace(this, t); + synchronized (parent) { + if (disconnectedEarly) { + ((ResettableConnectable)parent.source).resetIf(t); + } + } + } + } + + static final class RefCountObserver + extends AtomicBoolean implements Observer, Disposable { + + private static final long serialVersionUID = -7419642935409022375L; + + final Observer downstream; + + final ObservableRefCount parent; + + final RefConnection connection; + + Disposable upstream; + + RefCountObserver(Observer downstream, ObservableRefCount parent, RefConnection connection) { + this.downstream = downstream; + this.parent = parent; + this.connection = connection; + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (compareAndSet(false, true)) { + parent.terminated(connection); + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (compareAndSet(false, true)) { + parent.terminated(connection); + downstream.onComplete(); + } + } + + @Override + public void dispose() { + upstream.dispose(); + if (compareAndSet(false, true)) { + parent.cancel(connection); + } + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java new file mode 100755 index 0000000..a42a8e8 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeat.java @@ -0,0 +1,101 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.SequentialDisposable; + +public final class ObservableRepeat extends AbstractObservableWithUpstream { + final long count; + public ObservableRepeat(Observable source, long count) { + super(source); + this.count = count; + } + + @Override + public void subscribeActual(Observer observer) { + SequentialDisposable sd = new SequentialDisposable(); + observer.onSubscribe(sd); + + RepeatObserver rs = new RepeatObserver(observer, count != Long.MAX_VALUE ? count - 1 : Long.MAX_VALUE, sd, source); + rs.subscribeNext(); + } + + static final class RepeatObserver extends AtomicInteger implements Observer { + + private static final long serialVersionUID = -7098360935104053232L; + + final Observer downstream; + final SequentialDisposable sd; + final ObservableSource source; + long remaining; + RepeatObserver(Observer actual, long count, SequentialDisposable sd, ObservableSource source) { + this.downstream = actual; + this.sd = sd; + this.source = source; + this.remaining = count; + } + + @Override + public void onSubscribe(Disposable d) { + sd.replace(d); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + long r = remaining; + if (r != Long.MAX_VALUE) { + remaining = r - 1; + } + if (r != 0L) { + subscribeNext(); + } else { + downstream.onComplete(); + } + } + + /** + * Subscribes to the source again via trampolining. + */ + void subscribeNext() { + if (getAndIncrement() == 0) { + int missed = 1; + for (;;) { + if (sd.isDisposed()) { + return; + } + source.subscribe(this); + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java new file mode 100755 index 0000000..485eb1f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatUntil.java @@ -0,0 +1,104 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BooleanSupplier; +import io.reactivex.internal.disposables.SequentialDisposable; + +public final class ObservableRepeatUntil extends AbstractObservableWithUpstream { + final BooleanSupplier until; + public ObservableRepeatUntil(Observable source, BooleanSupplier until) { + super(source); + this.until = until; + } + + @Override + public void subscribeActual(Observer observer) { + SequentialDisposable sd = new SequentialDisposable(); + observer.onSubscribe(sd); + + RepeatUntilObserver rs = new RepeatUntilObserver(observer, until, sd, source); + rs.subscribeNext(); + } + + static final class RepeatUntilObserver extends AtomicInteger implements Observer { + + private static final long serialVersionUID = -7098360935104053232L; + + final Observer downstream; + final SequentialDisposable upstream; + final ObservableSource source; + final BooleanSupplier stop; + RepeatUntilObserver(Observer actual, BooleanSupplier until, SequentialDisposable sd, ObservableSource source) { + this.downstream = actual; + this.upstream = sd; + this.source = source; + this.stop = until; + } + + @Override + public void onSubscribe(Disposable d) { + upstream.replace(d); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + boolean b; + try { + b = stop.getAsBoolean(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + if (b) { + downstream.onComplete(); + } else { + subscribeNext(); + } + } + + /** + * Subscribes to the source again via trampolining. + */ + void subscribeNext() { + if (getAndIncrement() == 0) { + int missed = 1; + for (;;) { + source.subscribe(this); + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java new file mode 100755 index 0000000..2472599 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRepeatWhen.java @@ -0,0 +1,182 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.*; +import io.reactivex.subjects.*; + +/** + * Repeatedly subscribe to a source if a handler ObservableSource signals an item. + * + * @param the value type + */ +public final class ObservableRepeatWhen extends AbstractObservableWithUpstream { + + final Function, ? extends ObservableSource> handler; + + public ObservableRepeatWhen(ObservableSource source, Function, ? extends ObservableSource> handler) { + super(source); + this.handler = handler; + } + + @Override + protected void subscribeActual(Observer observer) { + Subject signaller = PublishSubject.create().toSerialized(); + + ObservableSource other; + + try { + other = ObjectHelper.requireNonNull(handler.apply(signaller), "The handler returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + RepeatWhenObserver parent = new RepeatWhenObserver(observer, signaller, source); + observer.onSubscribe(parent); + + other.subscribe(parent.inner); + + parent.subscribeNext(); + } + + static final class RepeatWhenObserver extends AtomicInteger implements Observer, Disposable { + + private static final long serialVersionUID = 802743776666017014L; + + final Observer downstream; + + final AtomicInteger wip; + + final AtomicThrowable error; + + final Subject signaller; + + final InnerRepeatObserver inner; + + final AtomicReference upstream; + + final ObservableSource source; + + volatile boolean active; + + RepeatWhenObserver(Observer actual, Subject signaller, ObservableSource source) { + this.downstream = actual; + this.signaller = signaller; + this.source = source; + this.wip = new AtomicInteger(); + this.error = new AtomicThrowable(); + this.inner = new InnerRepeatObserver(); + this.upstream = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); + } + + @Override + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); + } + + @Override + public void onError(Throwable e) { + DisposableHelper.dispose(inner); + HalfSerializer.onError(downstream, e, this, error); + } + + @Override + public void onComplete() { + DisposableHelper.replace(upstream, null); + active = false; + signaller.onNext(0); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(inner); + } + + void innerNext() { + subscribeNext(); + } + + void innerError(Throwable ex) { + DisposableHelper.dispose(upstream); + HalfSerializer.onError(downstream, ex, this, error); + } + + void innerComplete() { + DisposableHelper.dispose(upstream); + HalfSerializer.onComplete(downstream, this, error); + } + + void subscribeNext() { + if (wip.getAndIncrement() == 0) { + + do { + if (isDisposed()) { + return; + } + + if (!active) { + active = true; + source.subscribe(this); + } + } while (wip.decrementAndGet() != 0); + } + } + + final class InnerRepeatObserver extends AtomicReference implements Observer { + + private static final long serialVersionUID = 3254781284376480842L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Object t) { + innerNext(); + } + + @Override + public void onError(Throwable e) { + innerError(e); + } + + @Override + public void onComplete() { + innerComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java new file mode 100755 index 0000000..2818d97 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java @@ -0,0 +1,1082 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.HasUpstreamObservableSource; +import io.reactivex.internal.util.*; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Timed; + +public final class ObservableReplay extends ConnectableObservable implements HasUpstreamObservableSource, ResettableConnectable { + /** The source observable. */ + final ObservableSource source; + /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ + final AtomicReference> current; + /** A factory that creates the appropriate buffer for the ReplayObserver. */ + final BufferSupplier bufferFactory; + + final ObservableSource onSubscribe; + + interface BufferSupplier { + ReplayBuffer call(); + } + + @SuppressWarnings("rawtypes") + static final BufferSupplier DEFAULT_UNBOUNDED_FACTORY = new UnBoundedFactory(); + + /** + * Given a connectable observable factory, it multicasts over the generated + * ConnectableObservable via a selector function. + * @param the value type of the ConnectableObservable + * @param the result value type + * @param connectableFactory the factory that returns a ConnectableObservable for each individual subscriber + * @param selector the function that receives an Observable and should return another Observable that will be subscribed to + * @return the new Observable instance + */ + public static Observable multicastSelector( + final Callable> connectableFactory, + final Function, ? extends ObservableSource> selector) { + return RxJavaPlugins.onAssembly(new MulticastReplay(connectableFactory, selector)); + } + + /** + * Child Observers will observe the events of the ConnectableObservable on the + * specified scheduler. + * @param the value type + * @param co the connectable observable instance + * @param scheduler the target scheduler + * @return the new ConnectableObservable instance + */ + public static ConnectableObservable observeOn(final ConnectableObservable co, final Scheduler scheduler) { + final Observable observable = co.observeOn(scheduler); + return RxJavaPlugins.onAssembly(new Replay(co, observable)); + } + + /** + * Creates a replaying ConnectableObservable with an unbounded buffer. + * @param the value type + * @param source the source observable + * @return the new ConnectableObservable instance + */ + @SuppressWarnings("unchecked") + public static ConnectableObservable createFrom(ObservableSource source) { + return create(source, DEFAULT_UNBOUNDED_FACTORY); + } + + /** + * Creates a replaying ConnectableObservable with a size bound buffer. + * @param the value type + * @param source the source ObservableSource to use + * @param bufferSize the maximum number of elements to hold + * @return the new ConnectableObservable instance + */ + public static ConnectableObservable create(ObservableSource source, + final int bufferSize) { + if (bufferSize == Integer.MAX_VALUE) { + return createFrom(source); + } + return create(source, new ReplayBufferSupplier(bufferSize)); + } + + /** + * Creates a replaying ConnectableObservable with a time bound buffer. + * @param the value type + * @param source the source ObservableSource to use + * @param maxAge the maximum age of entries + * @param unit the unit of measure of the age amount + * @param scheduler the target scheduler providing the current time + * @return the new ConnectableObservable instance + */ + public static ConnectableObservable create(ObservableSource source, + long maxAge, TimeUnit unit, Scheduler scheduler) { + return create(source, maxAge, unit, scheduler, Integer.MAX_VALUE); + } + + /** + * Creates a replaying ConnectableObservable with a size and time bound buffer. + * @param the value type + * @param source the source ObservableSource to use + * @param maxAge the maximum age of entries + * @param unit the unit of measure of the age amount + * @param scheduler the target scheduler providing the current time + * @param bufferSize the maximum number of elements to hold + * @return the new ConnectableObservable instance + */ + public static ConnectableObservable create(ObservableSource source, + final long maxAge, final TimeUnit unit, final Scheduler scheduler, final int bufferSize) { + return create(source, new ScheduledReplaySupplier(bufferSize, maxAge, unit, scheduler)); + } + + /** + * Creates a OperatorReplay instance to replay values of the given source observable. + * @param source the source observable + * @param bufferFactory the factory to instantiate the appropriate buffer when the observable becomes active + * @return the connectable observable + */ + static ConnectableObservable create(ObservableSource source, + final BufferSupplier bufferFactory) { + // the current connection to source needs to be shared between the operator and its onSubscribe call + final AtomicReference> curr = new AtomicReference>(); + ObservableSource onSubscribe = new ReplaySource(curr, bufferFactory); + return RxJavaPlugins.onAssembly(new ObservableReplay(onSubscribe, source, curr, bufferFactory)); + } + + private ObservableReplay(ObservableSource onSubscribe, ObservableSource source, + final AtomicReference> current, + final BufferSupplier bufferFactory) { + this.onSubscribe = onSubscribe; + this.source = source; + this.current = current; + this.bufferFactory = bufferFactory; + } + + @Override + public ObservableSource source() { + return source; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void resetIf(Disposable connectionObject) { + current.compareAndSet((ReplayObserver)connectionObject, null); + } + + @Override + protected void subscribeActual(Observer observer) { + onSubscribe.subscribe(observer); + } + + @Override + public void connect(Consumer connection) { + boolean doConnect; + ReplayObserver ps; + // we loop because concurrent connect/disconnect and termination may change the state + for (;;) { + // retrieve the current subscriber-to-source instance + ps = current.get(); + // if there is none yet or the current has been disposed + if (ps == null || ps.isDisposed()) { + // create a new subscriber-to-source + ReplayBuffer buf = bufferFactory.call(); + + ReplayObserver u = new ReplayObserver(buf); + // try setting it as the current subscriber-to-source + if (!current.compareAndSet(ps, u)) { + // did not work, perhaps a new subscriber arrived + // and created a new subscriber-to-source as well, retry + continue; + } + ps = u; + } + // if connect() was called concurrently, only one of them should actually + // connect to the source + doConnect = !ps.shouldConnect.get() && ps.shouldConnect.compareAndSet(false, true); + break; // NOPMD + } + /* + * Notify the callback that we have a (new) connection which it can dispose + * but since ps is unique to a connection, multiple calls to connect() will return the + * same Disposable and even if there was a connect-disconnect-connect pair, the older + * references won't disconnect the newer connection. + * Synchronous source consumers have the opportunity to disconnect via dispose() on the + * Disposable as subscribe() may never return in its own. + * + * Note however, that asynchronously disconnecting a running source might leave + * child observers without any terminal event; ReplaySubject does not have this + * issue because the dispose() call was always triggered by the child observers + * themselves. + */ + + try { + connection.accept(ps); + } catch (Throwable ex) { + if (doConnect) { + ps.shouldConnect.compareAndSet(true, false); + } + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + if (doConnect) { + source.subscribe(ps); + } + } + + @SuppressWarnings("rawtypes") + static final class ReplayObserver + extends AtomicReference + implements Observer, Disposable { + private static final long serialVersionUID = -533785617179540163L; + /** Holds notifications from upstream. */ + final ReplayBuffer buffer; + /** Indicates this Observer received a terminal event. */ + boolean done; + + /** Indicates an empty array of inner observers. */ + static final InnerDisposable[] EMPTY = new InnerDisposable[0]; + /** Indicates a terminated ReplayObserver. */ + static final InnerDisposable[] TERMINATED = new InnerDisposable[0]; + + /** Tracks the subscribed observers. */ + final AtomicReference observers; + /** + * Atomically changed from false to true by connect to make sure the + * connection is only performed by one thread. + */ + final AtomicBoolean shouldConnect; + + ReplayObserver(ReplayBuffer buffer) { + this.buffer = buffer; + + this.observers = new AtomicReference(EMPTY); + this.shouldConnect = new AtomicBoolean(); + } + + @Override + public boolean isDisposed() { + return observers.get() == TERMINATED; + } + + @Override + public void dispose() { + observers.set(TERMINATED); + // unlike OperatorPublish, we can't null out the terminated so + // late observers can still get replay + // current.compareAndSet(ReplayObserver.this, null); + // we don't care if it fails because it means the current has + // been replaced in the meantime + DisposableHelper.dispose(this); + } + + /** + * Atomically try adding a new InnerDisposable to this Observer or return false if this + * Observer was terminated. + * @param producer the producer to add + * @return true if succeeded, false otherwise + */ + boolean add(InnerDisposable producer) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // get the current producer array + InnerDisposable[] c = observers.get(); + // if this subscriber-to-source reached a terminal state by receiving + // an onError or onComplete, just refuse to add the new producer + if (c == TERMINATED) { + return false; + } + // we perform a copy-on-write logic + int len = c.length; + InnerDisposable[] u = new InnerDisposable[len + 1]; + System.arraycopy(c, 0, u, 0, len); + u[len] = producer; + // try setting the observers array + if (observers.compareAndSet(c, u)) { + return true; + } + // if failed, some other operation succeeded (another add, remove or termination) + // so retry + } + } + + /** + * Atomically removes the given InnerDisposable from the observers array. + * @param producer the producer to remove + */ + void remove(InnerDisposable producer) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // let's read the current observers array + InnerDisposable[] c = observers.get(); + + int len = c.length; + // if it is either empty or terminated, there is nothing to remove so we quit + if (len == 0) { + return; + } + // let's find the supplied producer in the array + // although this is O(n), we don't expect too many child observers in general + int j = -1; + for (int i = 0; i < len; i++) { + if (c[i].equals(producer)) { + j = i; + break; + } + } + // we didn't find it so just quit + if (j < 0) { + return; + } + // we do copy-on-write logic here + InnerDisposable[] u; + // we don't create a new empty array if producer was the single inhabitant + // but rather reuse an empty array + if (len == 1) { + u = EMPTY; + } else { + // otherwise, create a new array one less in size + u = new InnerDisposable[len - 1]; + // copy elements being before the given producer + System.arraycopy(c, 0, u, 0, j); + // copy elements being after the given producer + System.arraycopy(c, j + 1, u, j, len - j - 1); + } + // try setting this new array as + if (observers.compareAndSet(c, u)) { + return; + } + // if we failed, it means something else happened + // (a concurrent add/remove or termination), we need to retry + } + } + + @Override + public void onSubscribe(Disposable p) { + if (DisposableHelper.setOnce(this, p)) { + replay(); + } + } + + @Override + public void onNext(T t) { + if (!done) { + buffer.next(t); + replay(); + } + } + + @Override + public void onError(Throwable e) { + // The observer front is accessed serially as required by spec so + // no need to CAS in the terminal value + if (!done) { + done = true; + buffer.error(e); + replayFinal(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + // The observer front is accessed serially as required by spec so + // no need to CAS in the terminal value + if (!done) { + done = true; + buffer.complete(); + replayFinal(); + } + } + + /** + * Tries to replay the buffer contents to all known observers. + */ + void replay() { + @SuppressWarnings("unchecked") + InnerDisposable[] a = observers.get(); + for (InnerDisposable rp : a) { + buffer.replay(rp); + } + } + + /** + * Tries to replay the buffer contents to all known observers. + */ + void replayFinal() { + @SuppressWarnings("unchecked") + InnerDisposable[] a = observers.getAndSet(TERMINATED); + for (InnerDisposable rp : a) { + buffer.replay(rp); + } + } + } + /** + * A Disposable that manages the disposed state of a + * child Observer in thread-safe manner. + * @param the value type + */ + static final class InnerDisposable + extends AtomicInteger + implements Disposable { + private static final long serialVersionUID = 2728361546769921047L; + /** + * The parent subscriber-to-source used to allow removing the child in case of + * child dispose() call. + */ + final ReplayObserver parent; + /** The actual child subscriber. */ + final Observer child; + /** + * Holds an object that represents the current location in the buffer. + * Guarded by the emitter loop. + */ + Object index; + + volatile boolean cancelled; + + InnerDisposable(ReplayObserver parent, Observer child) { + this.parent = parent; + this.child = child; + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + // remove this from the parent + parent.remove(this); + // make sure the last known node is not retained + index = null; + } + } + /** + * Convenience method to auto-cast the index object. + * @return the index Object or null + */ + @SuppressWarnings("unchecked") + U index() { + return (U)index; + } + } + /** + * The interface for interacting with various buffering logic. + * + * @param the value type + */ + interface ReplayBuffer { + /** + * Adds a regular value to the buffer. + * @param value the value to be stored in the buffer + */ + void next(T value); + /** + * Adds a terminal exception to the buffer. + * @param e the error to be stored in the buffer + */ + void error(Throwable e); + /** + * Adds a completion event to the buffer. + */ + void complete(); + /** + * Tries to replay the buffered values to the + * subscriber inside the output if there + * is new value and requests available at the + * same time. + * @param output the receiver of the buffered events + */ + void replay(InnerDisposable output); + } + + /** + * Holds an unbounded list of events. + * + * @param the value type + */ + static final class UnboundedReplayBuffer extends ArrayList implements ReplayBuffer { + + private static final long serialVersionUID = 7063189396499112664L; + /** The total number of events in the buffer. */ + volatile int size; + + UnboundedReplayBuffer(int capacityHint) { + super(capacityHint); + } + + @Override + public void next(T value) { + add(NotificationLite.next(value)); + size++; + } + + @Override + public void error(Throwable e) { + add(NotificationLite.error(e)); + size++; + } + + @Override + public void complete() { + add(NotificationLite.complete()); + size++; + } + + @Override + public void replay(InnerDisposable output) { + if (output.getAndIncrement() != 0) { + return; + } + + final Observer child = output.child; + + int missed = 1; + + for (;;) { + if (output.isDisposed()) { + return; + } + int sourceIndex = size; + + Integer destinationIndexObject = output.index(); + int destinationIndex = destinationIndexObject != null ? destinationIndexObject : 0; + + while (destinationIndex < sourceIndex) { + Object o = get(destinationIndex); + if (NotificationLite.accept(o, child)) { + return; + } + if (output.isDisposed()) { + return; + } + destinationIndex++; + } + + output.index = destinationIndex; + missed = output.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + /** + * Represents a node in a bounded replay buffer's linked list. + */ + static final class Node extends AtomicReference { + + private static final long serialVersionUID = 245354315435971818L; + final Object value; + Node(Object value) { + this.value = value; + } + } + + /** + * Base class for bounded buffering with options to specify an + * enter and leave transforms and custom truncation behavior. + * + * @param the value type + */ + abstract static class BoundedReplayBuffer extends AtomicReference implements ReplayBuffer { + + private static final long serialVersionUID = 2346567790059478686L; + + Node tail; + int size; + + BoundedReplayBuffer() { + Node n = new Node(null); + tail = n; + set(n); + } + + /** + * Add a new node to the linked list. + * @param n the Node instance to add as last + */ + final void addLast(Node n) { + tail.set(n); + tail = n; + size++; + } + /** + * Remove the first node from the linked list. + */ + final void removeFirst() { + Node head = get(); + Node next = head.get(); + size--; + // can't just move the head because it would retain the very first value + // can't null out the head's value because of late replayers would see null + setFirst(next); + } + + final void trimHead() { + Node head = get(); + if (head.value != null) { + Node n = new Node(null); + n.lazySet(head.get()); + set(n); + } + } + + /* test */ final void removeSome(int n) { + Node head = get(); + while (n > 0) { + head = head.get(); + n--; + size--; + } + + setFirst(head); + // correct the tail if all items have been removed + head = get(); + if (head.get() == null) { + tail = head; + } + } + /** + * Arranges the given node is the new head from now on. + * @param n the Node instance to set as first + */ + final void setFirst(Node n) { + set(n); + } + + @Override + public final void next(T value) { + Object o = enterTransform(NotificationLite.next(value)); + Node n = new Node(o); + addLast(n); + truncate(); + } + + @Override + public final void error(Throwable e) { + Object o = enterTransform(NotificationLite.error(e)); + Node n = new Node(o); + addLast(n); + truncateFinal(); + } + + @Override + public final void complete() { + Object o = enterTransform(NotificationLite.complete()); + Node n = new Node(o); + addLast(n); + truncateFinal(); + } + + @Override + public final void replay(InnerDisposable output) { + if (output.getAndIncrement() != 0) { + return; + } + + int missed = 1; + + for (;;) { + Node node = output.index(); + if (node == null) { + node = getHead(); + output.index = node; + } + + for (;;) { + if (output.isDisposed()) { + output.index = null; + return; + } + + Node v = node.get(); + if (v != null) { + Object o = leaveTransform(v.value); + if (NotificationLite.accept(o, output.child)) { + output.index = null; + return; + } + node = v; + } else { + break; + } + } + + output.index = node; + + missed = output.addAndGet(-missed); + if (missed == 0) { + break; + } + } + + } + + /** + * Override this to wrap the NotificationLite object into a + * container to be used later by truncate. + * @param value the value to transform into the internal representation + * @return the transformed value + */ + Object enterTransform(Object value) { + return value; + } + /** + * Override this to unwrap the transformed value into a + * NotificationLite object. + * @param value the value in the internal representation to transform + * @return the transformed value + */ + Object leaveTransform(Object value) { + return value; + } + /** + * Override this method to truncate a non-terminated buffer + * based on its current properties. + */ + abstract void truncate(); + + /** + * Override this method to truncate a terminated buffer + * based on its properties (i.e., truncate but the very last node). + */ + void truncateFinal() { + trimHead(); + } + /* test */ final void collect(Collection output) { + Node n = getHead(); + for (;;) { + Node next = n.get(); + if (next != null) { + Object o = next.value; + Object v = leaveTransform(o); + if (NotificationLite.isComplete(v) || NotificationLite.isError(v)) { + break; + } + output.add(NotificationLite.getValue(v)); + n = next; + } else { + break; + } + } + } + /* test */ boolean hasError() { + return tail.value != null && NotificationLite.isError(leaveTransform(tail.value)); + } + /* test */ boolean hasCompleted() { + return tail.value != null && NotificationLite.isComplete(leaveTransform(tail.value)); + } + + Node getHead() { + return get(); + } + } + + /** + * A bounded replay buffer implementation with size limit only. + * + * @param the value type + */ + static final class SizeBoundReplayBuffer extends BoundedReplayBuffer { + + private static final long serialVersionUID = -5898283885385201806L; + + final int limit; + SizeBoundReplayBuffer(int limit) { + this.limit = limit; + } + + @Override + void truncate() { + // overflow can be at most one element + if (size > limit) { + removeFirst(); + } + } + + // no need for final truncation because values are truncated one by one + } + + /** + * Size and time bound replay buffer. + * + * @param the buffered value type + */ + static final class SizeAndTimeBoundReplayBuffer extends BoundedReplayBuffer { + + private static final long serialVersionUID = 3457957419649567404L; + final Scheduler scheduler; + final long maxAge; + final TimeUnit unit; + final int limit; + SizeAndTimeBoundReplayBuffer(int limit, long maxAge, TimeUnit unit, Scheduler scheduler) { + this.scheduler = scheduler; + this.limit = limit; + this.maxAge = maxAge; + this.unit = unit; + } + + @Override + Object enterTransform(Object value) { + return new Timed(value, scheduler.now(unit), unit); + } + + @Override + Object leaveTransform(Object value) { + return ((Timed)value).value(); + } + + @Override + void truncate() { + long timeLimit = scheduler.now(unit) - maxAge; + + Node prev = get(); + Node next = prev.get(); + + int e = 0; + for (;;) { + if (next != null) { + if (size > limit && size > 1) { // never truncate the very last item just added + e++; + size--; + prev = next; + next = next.get(); + } else { + Timed v = (Timed)next.value; + if (v.time() <= timeLimit) { + e++; + size--; + prev = next; + next = next.get(); + } else { + break; + } + } + } else { + break; + } + } + if (e != 0) { + setFirst(prev); + } + } + + @Override + void truncateFinal() { + long timeLimit = scheduler.now(unit) - maxAge; + + Node prev = get(); + Node next = prev.get(); + + int e = 0; + for (;;) { + if (next != null && size > 1) { + Timed v = (Timed)next.value; + if (v.time() <= timeLimit) { + e++; + size--; + prev = next; + next = next.get(); + } else { + break; + } + } else { + break; + } + } + if (e != 0) { + setFirst(prev); + } + } + + @Override + Node getHead() { + long timeLimit = scheduler.now(unit) - maxAge; + Node prev = get(); + Node next = prev.get(); + for (;;) { + if (next == null) { + break; + } + Timed v = (Timed)next.value; + if (NotificationLite.isComplete(v.value()) || NotificationLite.isError(v.value())) { + break; + } + if (v.time() <= timeLimit) { + prev = next; + next = next.get(); + } else { + break; + } + } + return prev; + } + } + + static final class UnBoundedFactory implements BufferSupplier { + @Override + public ReplayBuffer call() { + return new UnboundedReplayBuffer(16); + } + } + + static final class DisposeConsumer implements Consumer { + private final ObserverResourceWrapper srw; + + DisposeConsumer(ObserverResourceWrapper srw) { + this.srw = srw; + } + + @Override + public void accept(Disposable r) { + srw.setResource(r); + } + } + + static final class ReplayBufferSupplier implements BufferSupplier { + private final int bufferSize; + + ReplayBufferSupplier(int bufferSize) { + this.bufferSize = bufferSize; + } + + @Override + public ReplayBuffer call() { + return new SizeBoundReplayBuffer(bufferSize); + } + } + + static final class ScheduledReplaySupplier implements BufferSupplier { + private final int bufferSize; + private final long maxAge; + private final TimeUnit unit; + private final Scheduler scheduler; + + ScheduledReplaySupplier(int bufferSize, long maxAge, TimeUnit unit, Scheduler scheduler) { + this.bufferSize = bufferSize; + this.maxAge = maxAge; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public ReplayBuffer call() { + return new SizeAndTimeBoundReplayBuffer(bufferSize, maxAge, unit, scheduler); + } + } + + static final class ReplaySource implements ObservableSource { + private final AtomicReference> curr; + private final BufferSupplier bufferFactory; + + ReplaySource(AtomicReference> curr, BufferSupplier bufferFactory) { + this.curr = curr; + this.bufferFactory = bufferFactory; + } + + @Override + public void subscribe(Observer child) { + // concurrent connection/disconnection may change the state, + // we loop to be atomic while the child subscribes + for (;;) { + // get the current subscriber-to-source + ReplayObserver r = curr.get(); + // if there isn't one + if (r == null) { + // create a new subscriber to source + ReplayBuffer buf = bufferFactory.call(); + + ReplayObserver u = new ReplayObserver(buf); + // let's try setting it as the current subscriber-to-source + if (!curr.compareAndSet(null, u)) { + // didn't work, maybe someone else did it or the current subscriber + // to source has just finished + continue; + } + // we won, let's use it going onwards + r = u; + } + + // create the backpressure-managing producer for this child + InnerDisposable inner = new InnerDisposable(r, child); + // the producer has been registered with the current subscriber-to-source so + // at least it will receive the next terminal event + // setting the producer will trigger the first request to be considered by + // the subscriber-to-source. + child.onSubscribe(inner); + // we try to add it to the array of observers + // if it fails, no worries because we will still have its buffer + // so it is going to replay it for us + r.add(inner); + + if (inner.isDisposed()) { + r.remove(inner); + return; + } + + // replay the contents of the buffer + r.buffer.replay(inner); + + break; // NOPMD + } + } + } + + static final class MulticastReplay extends Observable { + private final Callable> connectableFactory; + private final Function, ? extends ObservableSource> selector; + + MulticastReplay(Callable> connectableFactory, Function, ? extends ObservableSource> selector) { + this.connectableFactory = connectableFactory; + this.selector = selector; + } + + @Override + protected void subscribeActual(Observer child) { + ConnectableObservable co; + ObservableSource observable; + try { + co = ObjectHelper.requireNonNull(connectableFactory.call(), "The connectableFactory returned a null ConnectableObservable"); + observable = ObjectHelper.requireNonNull(selector.apply(co), "The selector returned a null ObservableSource"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, child); + return; + } + + final ObserverResourceWrapper srw = new ObserverResourceWrapper(child); + + observable.subscribe(srw); + + co.connect(new DisposeConsumer(srw)); + } + } + + static final class Replay extends ConnectableObservable { + private final ConnectableObservable co; + private final Observable observable; + + Replay(ConnectableObservable co, Observable observable) { + this.co = co; + this.observable = observable; + } + + @Override + public void connect(Consumer connection) { + co.connect(connection); + } + + @Override + protected void subscribeActual(Observer observer) { + observable.subscribe(observer); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java new file mode 100755 index 0000000..f762142 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryBiPredicate.java @@ -0,0 +1,111 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.BiPredicate; +import io.reactivex.internal.disposables.SequentialDisposable; + +public final class ObservableRetryBiPredicate extends AbstractObservableWithUpstream { + final BiPredicate predicate; + public ObservableRetryBiPredicate( + Observable source, + BiPredicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + public void subscribeActual(Observer observer) { + SequentialDisposable sa = new SequentialDisposable(); + observer.onSubscribe(sa); + + RetryBiObserver rs = new RetryBiObserver(observer, predicate, sa, source); + rs.subscribeNext(); + } + + static final class RetryBiObserver extends AtomicInteger implements Observer { + + private static final long serialVersionUID = -7098360935104053232L; + + final Observer downstream; + final SequentialDisposable upstream; + final ObservableSource source; + final BiPredicate predicate; + int retries; + RetryBiObserver(Observer actual, + BiPredicate predicate, SequentialDisposable sa, ObservableSource source) { + this.downstream = actual; + this.upstream = sa; + this.source = source; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Disposable d) { + upstream.replace(d); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + boolean b; + try { + b = predicate.test(++retries, t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + return; + } + if (!b) { + downstream.onError(t); + return; + } + subscribeNext(); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + /** + * Subscribes to the source again via trampolining. + */ + void subscribeNext() { + if (getAndIncrement() == 0) { + int missed = 1; + for (;;) { + if (upstream.isDisposed()) { + return; + } + source.subscribe(this); + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java new file mode 100755 index 0000000..ee5e074 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryPredicate.java @@ -0,0 +1,122 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.SequentialDisposable; + +public final class ObservableRetryPredicate extends AbstractObservableWithUpstream { + final Predicate predicate; + final long count; + public ObservableRetryPredicate(Observable source, + long count, + Predicate predicate) { + super(source); + this.predicate = predicate; + this.count = count; + } + + @Override + public void subscribeActual(Observer observer) { + SequentialDisposable sa = new SequentialDisposable(); + observer.onSubscribe(sa); + + RepeatObserver rs = new RepeatObserver(observer, count, predicate, sa, source); + rs.subscribeNext(); + } + + static final class RepeatObserver extends AtomicInteger implements Observer { + + private static final long serialVersionUID = -7098360935104053232L; + + final Observer downstream; + final SequentialDisposable upstream; + final ObservableSource source; + final Predicate predicate; + long remaining; + RepeatObserver(Observer actual, long count, + Predicate predicate, SequentialDisposable sa, ObservableSource source) { + this.downstream = actual; + this.upstream = sa; + this.source = source; + this.predicate = predicate; + this.remaining = count; + } + + @Override + public void onSubscribe(Disposable d) { + upstream.replace(d); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + long r = remaining; + if (r != Long.MAX_VALUE) { + remaining = r - 1; + } + if (r == 0) { + downstream.onError(t); + } else { + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(new CompositeException(t, e)); + return; + } + if (!b) { + downstream.onError(t); + return; + } + subscribeNext(); + } + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + /** + * Subscribes to the source again via trampolining. + */ + void subscribeNext() { + if (getAndIncrement() == 0) { + int missed = 1; + for (;;) { + if (upstream.isDisposed()) { + return; + } + source.subscribe(this); + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryWhen.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryWhen.java new file mode 100755 index 0000000..0d48ef1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRetryWhen.java @@ -0,0 +1,182 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.*; +import io.reactivex.subjects.*; + +/** + * Repeatedly subscribe to a source if a handler ObservableSource signals an item. + * + * @param the value type + */ +public final class ObservableRetryWhen extends AbstractObservableWithUpstream { + + final Function, ? extends ObservableSource> handler; + + public ObservableRetryWhen(ObservableSource source, Function, ? extends ObservableSource> handler) { + super(source); + this.handler = handler; + } + + @Override + protected void subscribeActual(Observer observer) { + Subject signaller = PublishSubject.create().toSerialized(); + + ObservableSource other; + + try { + other = ObjectHelper.requireNonNull(handler.apply(signaller), "The handler returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + RepeatWhenObserver parent = new RepeatWhenObserver(observer, signaller, source); + observer.onSubscribe(parent); + + other.subscribe(parent.inner); + + parent.subscribeNext(); + } + + static final class RepeatWhenObserver extends AtomicInteger implements Observer, Disposable { + + private static final long serialVersionUID = 802743776666017014L; + + final Observer downstream; + + final AtomicInteger wip; + + final AtomicThrowable error; + + final Subject signaller; + + final InnerRepeatObserver inner; + + final AtomicReference upstream; + + final ObservableSource source; + + volatile boolean active; + + RepeatWhenObserver(Observer actual, Subject signaller, ObservableSource source) { + this.downstream = actual; + this.signaller = signaller; + this.source = source; + this.wip = new AtomicInteger(); + this.error = new AtomicThrowable(); + this.inner = new InnerRepeatObserver(); + this.upstream = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this.upstream, d); + } + + @Override + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); + } + + @Override + public void onError(Throwable e) { + DisposableHelper.replace(upstream, null); + active = false; + signaller.onNext(e); + } + + @Override + public void onComplete() { + DisposableHelper.dispose(inner); + HalfSerializer.onComplete(downstream, this, error); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(inner); + } + + void innerNext() { + subscribeNext(); + } + + void innerError(Throwable ex) { + DisposableHelper.dispose(upstream); + HalfSerializer.onError(downstream, ex, this, error); + } + + void innerComplete() { + DisposableHelper.dispose(upstream); + HalfSerializer.onComplete(downstream, this, error); + } + + void subscribeNext() { + if (wip.getAndIncrement() == 0) { + + do { + if (isDisposed()) { + return; + } + + if (!active) { + active = true; + source.subscribe(this); + } + } while (wip.decrementAndGet() != 0); + } + } + + final class InnerRepeatObserver extends AtomicReference implements Observer { + + private static final long serialVersionUID = 3254781284376480842L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Object t) { + innerNext(); + } + + @Override + public void onError(Throwable e) { + innerError(e); + } + + @Override + public void onComplete() { + innerComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleTimed.java new file mode 100755 index 0000000..a553a8d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleTimed.java @@ -0,0 +1,170 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.observers.SerializedObserver; + +public final class ObservableSampleTimed extends AbstractObservableWithUpstream { + final long period; + final TimeUnit unit; + final Scheduler scheduler; + + final boolean emitLast; + + public ObservableSampleTimed(ObservableSource source, long period, TimeUnit unit, Scheduler scheduler, boolean emitLast) { + super(source); + this.period = period; + this.unit = unit; + this.scheduler = scheduler; + this.emitLast = emitLast; + } + + @Override + public void subscribeActual(Observer t) { + SerializedObserver serial = new SerializedObserver(t); + if (emitLast) { + source.subscribe(new SampleTimedEmitLast(serial, period, unit, scheduler)); + } else { + source.subscribe(new SampleTimedNoLast(serial, period, unit, scheduler)); + } + } + + abstract static class SampleTimedObserver extends AtomicReference implements Observer, Disposable, Runnable { + + private static final long serialVersionUID = -3517602651313910099L; + + final Observer downstream; + final long period; + final TimeUnit unit; + final Scheduler scheduler; + + final AtomicReference timer = new AtomicReference(); + + Disposable upstream; + + SampleTimedObserver(Observer actual, long period, TimeUnit unit, Scheduler scheduler) { + this.downstream = actual; + this.period = period; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + + Disposable task = scheduler.schedulePeriodicallyDirect(this, period, period, unit); + DisposableHelper.replace(timer, task); + } + } + + @Override + public void onNext(T t) { + lazySet(t); + } + + @Override + public void onError(Throwable t) { + cancelTimer(); + downstream.onError(t); + } + + @Override + public void onComplete() { + cancelTimer(); + complete(); + } + + void cancelTimer() { + DisposableHelper.dispose(timer); + } + + @Override + public void dispose() { + cancelTimer(); + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + void emit() { + T value = getAndSet(null); + if (value != null) { + downstream.onNext(value); + } + } + + abstract void complete(); + } + + static final class SampleTimedNoLast extends SampleTimedObserver { + + private static final long serialVersionUID = -7139995637533111443L; + + SampleTimedNoLast(Observer actual, long period, TimeUnit unit, Scheduler scheduler) { + super(actual, period, unit, scheduler); + } + + @Override + void complete() { + downstream.onComplete(); + } + + @Override + public void run() { + emit(); + } + } + + static final class SampleTimedEmitLast extends SampleTimedObserver { + + private static final long serialVersionUID = -7139995637533111443L; + + final AtomicInteger wip; + + SampleTimedEmitLast(Observer actual, long period, TimeUnit unit, Scheduler scheduler) { + super(actual, period, unit, scheduler); + this.wip = new AtomicInteger(1); + } + + @Override + void complete() { + emit(); + if (wip.decrementAndGet() == 0) { + downstream.onComplete(); + } + } + + @Override + public void run() { + if (wip.incrementAndGet() == 2) { + emit(); + if (wip.decrementAndGet() == 0) { + downstream.onComplete(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java new file mode 100755 index 0000000..1d5f8a5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java @@ -0,0 +1,210 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.observers.SerializedObserver; + +public final class ObservableSampleWithObservable extends AbstractObservableWithUpstream { + + final ObservableSource other; + + final boolean emitLast; + + public ObservableSampleWithObservable(ObservableSource source, ObservableSource other, boolean emitLast) { + super(source); + this.other = other; + this.emitLast = emitLast; + } + + @Override + public void subscribeActual(Observer t) { + SerializedObserver serial = new SerializedObserver(t); + if (emitLast) { + source.subscribe(new SampleMainEmitLast(serial, other)); + } else { + source.subscribe(new SampleMainNoLast(serial, other)); + } + } + + abstract static class SampleMainObserver extends AtomicReference + implements Observer, Disposable { + + private static final long serialVersionUID = -3517602651313910099L; + + final Observer downstream; + final ObservableSource sampler; + + final AtomicReference other = new AtomicReference(); + + Disposable upstream; + + SampleMainObserver(Observer actual, ObservableSource other) { + this.downstream = actual; + this.sampler = other; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + if (other.get() == null) { + sampler.subscribe(new SamplerObserver(this)); + } + } + } + + @Override + public void onNext(T t) { + lazySet(t); + } + + @Override + public void onError(Throwable t) { + DisposableHelper.dispose(other); + downstream.onError(t); + } + + @Override + public void onComplete() { + DisposableHelper.dispose(other); + completion(); + } + + boolean setOther(Disposable o) { + return DisposableHelper.setOnce(other, o); + } + + @Override + public void dispose() { + DisposableHelper.dispose(other); + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return other.get() == DisposableHelper.DISPOSED; + } + + public void error(Throwable e) { + upstream.dispose(); + downstream.onError(e); + } + + public void complete() { + upstream.dispose(); + completion(); + } + + void emit() { + T value = getAndSet(null); + if (value != null) { + downstream.onNext(value); + } + } + + abstract void completion(); + + abstract void run(); + } + + static final class SamplerObserver implements Observer { + final SampleMainObserver parent; + SamplerObserver(SampleMainObserver parent) { + this.parent = parent; + + } + + @Override + public void onSubscribe(Disposable d) { + parent.setOther(d); + } + + @Override + public void onNext(Object t) { + parent.run(); + } + + @Override + public void onError(Throwable t) { + parent.error(t); + } + + @Override + public void onComplete() { + parent.complete(); + } + } + + static final class SampleMainNoLast extends SampleMainObserver { + + private static final long serialVersionUID = -3029755663834015785L; + + SampleMainNoLast(Observer actual, ObservableSource other) { + super(actual, other); + } + + @Override + void completion() { + downstream.onComplete(); + } + + @Override + void run() { + emit(); + } + } + + static final class SampleMainEmitLast extends SampleMainObserver { + + private static final long serialVersionUID = -3029755663834015785L; + + final AtomicInteger wip; + + volatile boolean done; + + SampleMainEmitLast(Observer actual, ObservableSource other) { + super(actual, other); + this.wip = new AtomicInteger(); + } + + @Override + void completion() { + done = true; + if (wip.getAndIncrement() == 0) { + emit(); + downstream.onComplete(); + } + } + + @Override + void run() { + if (wip.getAndIncrement() == 0) { + do { + boolean d = done; + emit(); + if (d) { + downstream.onComplete(); + return; + } + } while (wip.decrementAndGet() != 0); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableScalarXMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableScalarXMap.java new file mode 100755 index 0000000..d584379 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableScalarXMap.java @@ -0,0 +1,256 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.QueueDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Utility classes to work with scalar-sourced XMap operators (where X == { flat, concat, switch }). + */ +public final class ObservableScalarXMap { + + /** Utility class. */ + private ObservableScalarXMap() { + throw new IllegalStateException("No instances!"); + } + + /** + * Tries to subscribe to a possibly Callable source's mapped ObservableSource. + * @param the input value type + * @param the output value type + * @param source the source ObservableSource + * @param observer the subscriber + * @param mapper the function mapping a scalar value into an ObservableSource + * @return true if successful, false if the caller should continue with the regular path. + */ + @SuppressWarnings("unchecked") + public static boolean tryScalarXMapSubscribe(ObservableSource source, + Observer observer, + Function> mapper) { + if (source instanceof Callable) { + T t; + + try { + t = ((Callable)source).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (t == null) { + EmptyDisposable.complete(observer); + return true; + } + + ObservableSource r; + + try { + r = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (r instanceof Callable) { + R u; + + try { + u = ((Callable)r).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (u == null) { + EmptyDisposable.complete(observer); + return true; + } + ScalarDisposable sd = new ScalarDisposable(observer, u); + observer.onSubscribe(sd); + sd.run(); + } else { + r.subscribe(observer); + } + + return true; + } + return false; + } + + /** + * Maps a scalar value into an Observable and emits its values. + * + * @param the scalar value type + * @param the output value type + * @param value the scalar value to map + * @param mapper the function that gets the scalar value and should return + * an ObservableSource that gets streamed + * @return the new Observable instance + */ + public static Observable scalarXMap(T value, + Function> mapper) { + return RxJavaPlugins.onAssembly(new ScalarXMapObservable(value, mapper)); + } + + /** + * Maps a scalar value to an ObservableSource and subscribes to it. + * + * @param the scalar value type + * @param the mapped ObservableSource's element type. + */ + static final class ScalarXMapObservable extends Observable { + + final T value; + + final Function> mapper; + + ScalarXMapObservable(T value, + Function> mapper) { + this.value = value; + this.mapper = mapper; + } + + @SuppressWarnings("unchecked") + @Override + public void subscribeActual(Observer observer) { + ObservableSource other; + try { + other = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null ObservableSource"); + } catch (Throwable e) { + EmptyDisposable.error(e, observer); + return; + } + if (other instanceof Callable) { + R u; + + try { + u = ((Callable)other).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + if (u == null) { + EmptyDisposable.complete(observer); + return; + } + ScalarDisposable sd = new ScalarDisposable(observer, u); + observer.onSubscribe(sd); + sd.run(); + } else { + other.subscribe(observer); + } + } + } + + /** + * Represents a Disposable that signals one onNext followed by an onComplete. + * + * @param the value type + */ + public static final class ScalarDisposable + extends AtomicInteger + implements QueueDisposable, Runnable { + + private static final long serialVersionUID = 3880992722410194083L; + + final Observer observer; + + final T value; + + static final int START = 0; + static final int FUSED = 1; + static final int ON_NEXT = 2; + static final int ON_COMPLETE = 3; + + public ScalarDisposable(Observer observer, T value) { + this.observer = observer; + this.value = value; + } + + @Override + public boolean offer(T value) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public boolean offer(T v1, T v2) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Nullable + @Override + public T poll() throws Exception { + if (get() == FUSED) { + lazySet(ON_COMPLETE); + return value; + } + return null; + } + + @Override + public boolean isEmpty() { + return get() != FUSED; + } + + @Override + public void clear() { + lazySet(ON_COMPLETE); + } + + @Override + public void dispose() { + set(ON_COMPLETE); + } + + @Override + public boolean isDisposed() { + return get() == ON_COMPLETE; + } + + @Override + public int requestFusion(int mode) { + if ((mode & SYNC) != 0) { + lazySet(FUSED); + return SYNC; + } + return NONE; + } + + @Override + public void run() { + if (get() == START && compareAndSet(START, ON_NEXT)) { + observer.onNext(value); + if (get() == ON_NEXT) { + lazySet(ON_COMPLETE); + observer.onComplete(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java new file mode 100755 index 0000000..6b830e8 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableScan.java @@ -0,0 +1,115 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableScan extends AbstractObservableWithUpstream { + final BiFunction accumulator; + public ObservableScan(ObservableSource source, BiFunction accumulator) { + super(source); + this.accumulator = accumulator; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new ScanObserver(t, accumulator)); + } + + static final class ScanObserver implements Observer, Disposable { + final Observer downstream; + final BiFunction accumulator; + + Disposable upstream; + + T value; + + boolean done; + + ScanObserver(Observer actual, BiFunction accumulator) { + this.downstream = actual; + this.accumulator = accumulator; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + final Observer a = downstream; + T v = value; + if (v == null) { + value = t; + a.onNext(t); + } else { + T u; + + try { + u = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The value returned by the accumulator is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + + value = u; + a.onNext(u); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java new file mode 100755 index 0000000..a9b4a63 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableScanSeed.java @@ -0,0 +1,130 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableScanSeed extends AbstractObservableWithUpstream { + final BiFunction accumulator; + final Callable seedSupplier; + + public ObservableScanSeed(ObservableSource source, Callable seedSupplier, BiFunction accumulator) { + super(source); + this.accumulator = accumulator; + this.seedSupplier = seedSupplier; + } + + @Override + public void subscribeActual(Observer t) { + R r; + + try { + r = ObjectHelper.requireNonNull(seedSupplier.call(), "The seed supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, t); + return; + } + + source.subscribe(new ScanSeedObserver(t, accumulator, r)); + } + + static final class ScanSeedObserver implements Observer, Disposable { + final Observer downstream; + final BiFunction accumulator; + + R value; + + Disposable upstream; + + boolean done; + + ScanSeedObserver(Observer actual, BiFunction accumulator, R value) { + this.downstream = actual; + this.accumulator = accumulator; + this.value = value; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + + downstream.onNext(value); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + R v = value; + + R u; + + try { + u = ObjectHelper.requireNonNull(accumulator.apply(v, t), "The accumulator returned a null value"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + + value = u; + + downstream.onNext(u); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqual.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqual.java new file mode 100755 index 0000000..07da31f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqual.java @@ -0,0 +1,256 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiPredicate; +import io.reactivex.internal.disposables.ArrayCompositeDisposable; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; + +public final class ObservableSequenceEqual extends Observable { + final ObservableSource first; + final ObservableSource second; + final BiPredicate comparer; + final int bufferSize; + + public ObservableSequenceEqual(ObservableSource first, ObservableSource second, + BiPredicate comparer, int bufferSize) { + this.first = first; + this.second = second; + this.comparer = comparer; + this.bufferSize = bufferSize; + } + + @Override + public void subscribeActual(Observer observer) { + EqualCoordinator ec = new EqualCoordinator(observer, bufferSize, first, second, comparer); + observer.onSubscribe(ec); + ec.subscribe(); + } + + static final class EqualCoordinator extends AtomicInteger implements Disposable { + + private static final long serialVersionUID = -6178010334400373240L; + final Observer downstream; + final BiPredicate comparer; + final ArrayCompositeDisposable resources; + final ObservableSource first; + final ObservableSource second; + final EqualObserver[] observers; + + volatile boolean cancelled; + + T v1; + + T v2; + + EqualCoordinator(Observer actual, int bufferSize, + ObservableSource first, ObservableSource second, + BiPredicate comparer) { + this.downstream = actual; + this.first = first; + this.second = second; + this.comparer = comparer; + @SuppressWarnings("unchecked") + EqualObserver[] as = new EqualObserver[2]; + this.observers = as; + as[0] = new EqualObserver(this, 0, bufferSize); + as[1] = new EqualObserver(this, 1, bufferSize); + this.resources = new ArrayCompositeDisposable(2); + } + + boolean setDisposable(Disposable d, int index) { + return resources.setResource(index, d); + } + + void subscribe() { + EqualObserver[] as = observers; + first.subscribe(as[0]); + second.subscribe(as[1]); + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + resources.dispose(); + + if (getAndIncrement() == 0) { + EqualObserver[] as = observers; + as[0].queue.clear(); + as[1].queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void cancel(SpscLinkedArrayQueue q1, SpscLinkedArrayQueue q2) { + cancelled = true; + q1.clear(); + q2.clear(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + EqualObserver[] as = observers; + + final EqualObserver observer1 = as[0]; + final SpscLinkedArrayQueue q1 = observer1.queue; + final EqualObserver observer2 = as[1]; + final SpscLinkedArrayQueue q2 = observer2.queue; + + for (;;) { + + for (;;) { + if (cancelled) { + q1.clear(); + q2.clear(); + return; + } + + boolean d1 = observer1.done; + + if (d1) { + Throwable e = observer1.error; + if (e != null) { + cancel(q1, q2); + + downstream.onError(e); + return; + } + } + + boolean d2 = observer2.done; + if (d2) { + Throwable e = observer2.error; + if (e != null) { + cancel(q1, q2); + + downstream.onError(e); + return; + } + } + + if (v1 == null) { + v1 = q1.poll(); + } + boolean e1 = v1 == null; + + if (v2 == null) { + v2 = q2.poll(); + } + boolean e2 = v2 == null; + + if (d1 && d2 && e1 && e2) { + downstream.onNext(true); + downstream.onComplete(); + return; + } + if ((d1 && d2) && (e1 != e2)) { + cancel(q1, q2); + + downstream.onNext(false); + downstream.onComplete(); + return; + } + + if (!e1 && !e2) { + boolean c; + + try { + c = comparer.test(v1, v2); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(q1, q2); + + downstream.onError(ex); + return; + } + + if (!c) { + cancel(q1, q2); + + downstream.onNext(false); + downstream.onComplete(); + return; + } + + v1 = null; + v2 = null; + } + + if (e1 || e2) { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class EqualObserver implements Observer { + final EqualCoordinator parent; + final SpscLinkedArrayQueue queue; + final int index; + + volatile boolean done; + Throwable error; + + EqualObserver(EqualCoordinator parent, int index, int bufferSize) { + this.parent = parent; + this.index = index; + this.queue = new SpscLinkedArrayQueue(bufferSize); + } + + @Override + public void onSubscribe(Disposable d) { + parent.setDisposable(d, index); + } + + @Override + public void onNext(T t) { + queue.offer(t); + parent.drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + parent.drain(); + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualSingle.java new file mode 100755 index 0000000..88e059a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualSingle.java @@ -0,0 +1,260 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiPredicate; +import io.reactivex.internal.disposables.ArrayCompositeDisposable; +import io.reactivex.internal.fuseable.FuseToObservable; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableSequenceEqualSingle extends Single implements FuseToObservable { + final ObservableSource first; + final ObservableSource second; + final BiPredicate comparer; + final int bufferSize; + + public ObservableSequenceEqualSingle(ObservableSource first, ObservableSource second, + BiPredicate comparer, int bufferSize) { + this.first = first; + this.second = second; + this.comparer = comparer; + this.bufferSize = bufferSize; + } + + @Override + public void subscribeActual(SingleObserver observer) { + EqualCoordinator ec = new EqualCoordinator(observer, bufferSize, first, second, comparer); + observer.onSubscribe(ec); + ec.subscribe(); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableSequenceEqual(first, second, comparer, bufferSize)); + } + + static final class EqualCoordinator extends AtomicInteger implements Disposable { + + private static final long serialVersionUID = -6178010334400373240L; + final SingleObserver downstream; + final BiPredicate comparer; + final ArrayCompositeDisposable resources; + final ObservableSource first; + final ObservableSource second; + final EqualObserver[] observers; + + volatile boolean cancelled; + + T v1; + + T v2; + + EqualCoordinator(SingleObserver actual, int bufferSize, + ObservableSource first, ObservableSource second, + BiPredicate comparer) { + this.downstream = actual; + this.first = first; + this.second = second; + this.comparer = comparer; + @SuppressWarnings("unchecked") + EqualObserver[] as = new EqualObserver[2]; + this.observers = as; + as[0] = new EqualObserver(this, 0, bufferSize); + as[1] = new EqualObserver(this, 1, bufferSize); + this.resources = new ArrayCompositeDisposable(2); + } + + boolean setDisposable(Disposable d, int index) { + return resources.setResource(index, d); + } + + void subscribe() { + EqualObserver[] as = observers; + first.subscribe(as[0]); + second.subscribe(as[1]); + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + resources.dispose(); + + if (getAndIncrement() == 0) { + EqualObserver[] as = observers; + as[0].queue.clear(); + as[1].queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void cancel(SpscLinkedArrayQueue q1, SpscLinkedArrayQueue q2) { + cancelled = true; + q1.clear(); + q2.clear(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + EqualObserver[] as = observers; + + final EqualObserver observer1 = as[0]; + final SpscLinkedArrayQueue q1 = observer1.queue; + final EqualObserver observer2 = as[1]; + final SpscLinkedArrayQueue q2 = observer2.queue; + + for (;;) { + + for (;;) { + if (cancelled) { + q1.clear(); + q2.clear(); + return; + } + + boolean d1 = observer1.done; + + if (d1) { + Throwable e = observer1.error; + if (e != null) { + cancel(q1, q2); + + downstream.onError(e); + return; + } + } + + boolean d2 = observer2.done; + if (d2) { + Throwable e = observer2.error; + if (e != null) { + cancel(q1, q2); + + downstream.onError(e); + return; + } + } + + if (v1 == null) { + v1 = q1.poll(); + } + boolean e1 = v1 == null; + + if (v2 == null) { + v2 = q2.poll(); + } + boolean e2 = v2 == null; + + if (d1 && d2 && e1 && e2) { + downstream.onSuccess(true); + return; + } + if ((d1 && d2) && (e1 != e2)) { + cancel(q1, q2); + + downstream.onSuccess(false); + return; + } + + if (!e1 && !e2) { + boolean c; + + try { + c = comparer.test(v1, v2); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(q1, q2); + + downstream.onError(ex); + return; + } + + if (!c) { + cancel(q1, q2); + + downstream.onSuccess(false); + return; + } + + v1 = null; + v2 = null; + } + + if (e1 || e2) { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class EqualObserver implements Observer { + final EqualCoordinator parent; + final SpscLinkedArrayQueue queue; + final int index; + + volatile boolean done; + Throwable error; + + EqualObserver(EqualCoordinator parent, int index, int bufferSize) { + this.parent = parent; + this.index = index; + this.queue = new SpscLinkedArrayQueue(bufferSize); + } + + @Override + public void onSubscribe(Disposable d) { + parent.setDisposable(d, index); + } + + @Override + public void onNext(T t) { + queue.offer(t); + parent.drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + parent.drain(); + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSerialized.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSerialized.java new file mode 100755 index 0000000..eca7b42 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSerialized.java @@ -0,0 +1,28 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.observers.SerializedObserver; + +public final class ObservableSerialized extends AbstractObservableWithUpstream { + public ObservableSerialized(Observable upstream) { + super(upstream); + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new SerializedObserver(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java new file mode 100755 index 0000000..d4e1b9a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleMaybe.java @@ -0,0 +1,104 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableSingleMaybe extends Maybe { + + final ObservableSource source; + + public ObservableSingleMaybe(ObservableSource source) { + this.source = source; + } + + @Override + public void subscribeActual(MaybeObserver t) { + source.subscribe(new SingleElementObserver(t)); + } + + static final class SingleElementObserver implements Observer, Disposable { + final MaybeObserver downstream; + + Disposable upstream; + + T value; + + boolean done; + + SingleElementObserver(MaybeObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (value != null) { + done = true; + upstream.dispose(); + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); + return; + } + value = t; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + T v = value; + value = null; + if (v == null) { + downstream.onComplete(); + } else { + downstream.onSuccess(v); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java new file mode 100755 index 0000000..e1232f8 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSingleSingle.java @@ -0,0 +1,115 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; +import java.util.NoSuchElementException; + +public final class ObservableSingleSingle extends Single { + + final ObservableSource source; + + final T defaultValue; + + public ObservableSingleSingle(ObservableSource source, T defaultValue) { + this.source = source; + this.defaultValue = defaultValue; + } + + @Override + public void subscribeActual(SingleObserver t) { + source.subscribe(new SingleElementObserver(t, defaultValue)); + } + + static final class SingleElementObserver implements Observer, Disposable { + final SingleObserver downstream; + + final T defaultValue; + + Disposable upstream; + + T value; + + boolean done; + + SingleElementObserver(SingleObserver actual, T defaultValue) { + this.downstream = actual; + this.defaultValue = defaultValue; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (value != null) { + done = true; + upstream.dispose(); + downstream.onError(new IllegalArgumentException("Sequence contains more than one element!")); + return; + } + value = t; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + T v = value; + value = null; + if (v == null) { + v = defaultValue; + } + + if (v != null) { + downstream.onSuccess(v); + } else { + downstream.onError(new NoSuchElementException()); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java new file mode 100755 index 0000000..a03bca5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkip.java @@ -0,0 +1,80 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class ObservableSkip extends AbstractObservableWithUpstream { + final long n; + public ObservableSkip(ObservableSource source, long n) { + super(source); + this.n = n; + } + + @Override + public void subscribeActual(Observer observer) { + source.subscribe(new SkipObserver(observer, n)); + } + + static final class SkipObserver implements Observer, Disposable { + final Observer downstream; + long remaining; + + Disposable upstream; + + SkipObserver(Observer actual, long n) { + this.downstream = actual; + this.remaining = n; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (remaining != 0L) { + remaining--; + } else { + downstream.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java new file mode 100755 index 0000000..2ea5953 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLast.java @@ -0,0 +1,85 @@ +/** + * 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.internal.operators.observable; + +import java.util.ArrayDeque; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class ObservableSkipLast extends AbstractObservableWithUpstream { + final int skip; + + public ObservableSkipLast(ObservableSource source, int skip) { + super(source); + this.skip = skip; + } + + @Override + public void subscribeActual(Observer observer) { + source.subscribe(new SkipLastObserver(observer, skip)); + } + + static final class SkipLastObserver extends ArrayDeque implements Observer, Disposable { + + private static final long serialVersionUID = -3807491841935125653L; + final Observer downstream; + final int skip; + + Disposable upstream; + + SkipLastObserver(Observer actual, int skip) { + super(skip); + this.downstream = actual; + this.skip = skip; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (skip == size()) { + downstream.onNext(poll()); + } + offer(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimed.java new file mode 100755 index 0000000..3c9eb40 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimed.java @@ -0,0 +1,198 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; + +public final class ObservableSkipLastTimed extends AbstractObservableWithUpstream { + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final int bufferSize; + final boolean delayError; + + public ObservableSkipLastTimed(ObservableSource source, + long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { + super(source); + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSize = bufferSize; + this.delayError = delayError; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new SkipLastTimedObserver(t, time, unit, scheduler, bufferSize, delayError)); + } + + static final class SkipLastTimedObserver extends AtomicInteger implements Observer, Disposable { + + private static final long serialVersionUID = -5677354903406201275L; + final Observer downstream; + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final SpscLinkedArrayQueue queue; + final boolean delayError; + + Disposable upstream; + + volatile boolean cancelled; + + volatile boolean done; + Throwable error; + + SkipLastTimedObserver(Observer actual, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { + this.downstream = actual; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.queue = new SpscLinkedArrayQueue(bufferSize); + this.delayError = delayError; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + final SpscLinkedArrayQueue q = queue; + + long now = scheduler.now(unit); + + q.offer(now, t); + + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + upstream.dispose(); + + if (getAndIncrement() == 0) { + queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + final Observer a = downstream; + final SpscLinkedArrayQueue q = queue; + final boolean delayError = this.delayError; + final TimeUnit unit = this.unit; + final Scheduler scheduler = this.scheduler; + final long time = this.time; + + for (;;) { + + for (;;) { + if (cancelled) { + queue.clear(); + return; + } + + boolean d = done; + + Long ts = (Long)q.peek(); + + boolean empty = ts == null; + + long now = scheduler.now(unit); + + if (!empty && ts > now - time) { + empty = true; + } + + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + return; + } + } else { + Throwable e = error; + if (e != null) { + queue.clear(); + a.onError(e); + return; + } else + if (empty) { + a.onComplete(); + return; + } + } + } + + if (empty) { + break; + } + + q.poll(); + @SuppressWarnings("unchecked") + T v = (T)q.poll(); + + a.onNext(v); + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipUntil.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipUntil.java new file mode 100755 index 0000000..f02edec --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipUntil.java @@ -0,0 +1,128 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.observers.SerializedObserver; + +public final class ObservableSkipUntil extends AbstractObservableWithUpstream { + final ObservableSource other; + public ObservableSkipUntil(ObservableSource source, ObservableSource other) { + super(source); + this.other = other; + } + + @Override + public void subscribeActual(Observer child) { + + final SerializedObserver serial = new SerializedObserver(child); + + final ArrayCompositeDisposable frc = new ArrayCompositeDisposable(2); + + serial.onSubscribe(frc); + + final SkipUntilObserver sus = new SkipUntilObserver(serial, frc); + + other.subscribe(new SkipUntil(frc, sus, serial)); + + source.subscribe(sus); + } + + static final class SkipUntilObserver implements Observer { + + final Observer downstream; + final ArrayCompositeDisposable frc; + + Disposable upstream; + + volatile boolean notSkipping; + boolean notSkippingLocal; + + SkipUntilObserver(Observer actual, ArrayCompositeDisposable frc) { + this.downstream = actual; + this.frc = frc; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + frc.setResource(0, d); + } + } + + @Override + public void onNext(T t) { + if (notSkippingLocal) { + downstream.onNext(t); + } else + if (notSkipping) { + notSkippingLocal = true; + downstream.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + frc.dispose(); + downstream.onError(t); + } + + @Override + public void onComplete() { + frc.dispose(); + downstream.onComplete(); + } + } + + final class SkipUntil implements Observer { + final ArrayCompositeDisposable frc; + final SkipUntilObserver sus; + final SerializedObserver serial; + Disposable upstream; + + SkipUntil(ArrayCompositeDisposable frc, SkipUntilObserver sus, SerializedObserver serial) { + this.frc = frc; + this.sus = sus; + this.serial = serial; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + frc.setResource(1, d); + } + } + + @Override + public void onNext(U t) { + upstream.dispose(); + sus.notSkipping = true; + } + + @Override + public void onError(Throwable t) { + frc.dispose(); + serial.onError(t); + } + + @Override + public void onComplete() { + sus.notSkipping = true; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java new file mode 100755 index 0000000..d452fda --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSkipWhile.java @@ -0,0 +1,93 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class ObservableSkipWhile extends AbstractObservableWithUpstream { + final Predicate predicate; + public ObservableSkipWhile(ObservableSource source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + public void subscribeActual(Observer observer) { + source.subscribe(new SkipWhileObserver(observer, predicate)); + } + + static final class SkipWhileObserver implements Observer, Disposable { + final Observer downstream; + final Predicate predicate; + Disposable upstream; + boolean notSkipping; + SkipWhileObserver(Observer actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (notSkipping) { + downstream.onNext(t); + } else { + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + downstream.onError(e); + return; + } + if (!b) { + notSkipping = true; + downstream.onNext(t); + } + } + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSubscribeOn.java new file mode 100755 index 0000000..7e697d3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSubscribeOn.java @@ -0,0 +1,99 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class ObservableSubscribeOn extends AbstractObservableWithUpstream { + final Scheduler scheduler; + + public ObservableSubscribeOn(ObservableSource source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + public void subscribeActual(final Observer observer) { + final SubscribeOnObserver parent = new SubscribeOnObserver(observer); + + observer.onSubscribe(parent); + + parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent))); + } + + static final class SubscribeOnObserver extends AtomicReference implements Observer, Disposable { + + private static final long serialVersionUID = 8094547886072529208L; + final Observer downstream; + + final AtomicReference upstream; + + SubscribeOnObserver(Observer downstream) { + this.downstream = downstream; + this.upstream = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + void setDisposable(Disposable d) { + DisposableHelper.setOnce(this, d); + } + } + + final class SubscribeTask implements Runnable { + private final SubscribeOnObserver parent; + + SubscribeTask(SubscribeOnObserver parent) { + this.parent = parent; + } + + @Override + public void run() { + source.subscribe(parent); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmpty.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmpty.java new file mode 100755 index 0000000..6eec37f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchIfEmpty.java @@ -0,0 +1,76 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.SequentialDisposable; + +public final class ObservableSwitchIfEmpty extends AbstractObservableWithUpstream { + final ObservableSource other; + public ObservableSwitchIfEmpty(ObservableSource source, ObservableSource other) { + super(source); + this.other = other; + } + + @Override + public void subscribeActual(Observer t) { + SwitchIfEmptyObserver parent = new SwitchIfEmptyObserver(t, other); + t.onSubscribe(parent.arbiter); + source.subscribe(parent); + } + + static final class SwitchIfEmptyObserver implements Observer { + final Observer downstream; + final ObservableSource other; + final SequentialDisposable arbiter; + + boolean empty; + + SwitchIfEmptyObserver(Observer actual, ObservableSource other) { + this.downstream = actual; + this.other = other; + this.empty = true; + this.arbiter = new SequentialDisposable(); + } + + @Override + public void onSubscribe(Disposable d) { + arbiter.update(d); + } + + @Override + public void onNext(T t) { + if (empty) { + empty = false; + } + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + if (empty) { + empty = false; + other.subscribe(this); + } else { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java new file mode 100755 index 0000000..4e97ea4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java @@ -0,0 +1,396 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableSwitchMap extends AbstractObservableWithUpstream { + final Function> mapper; + final int bufferSize; + + final boolean delayErrors; + + public ObservableSwitchMap(ObservableSource source, + Function> mapper, int bufferSize, + boolean delayErrors) { + super(source); + this.mapper = mapper; + this.bufferSize = bufferSize; + this.delayErrors = delayErrors; + } + + @Override + public void subscribeActual(Observer t) { + + if (ObservableScalarXMap.tryScalarXMapSubscribe(source, t, mapper)) { + return; + } + + source.subscribe(new SwitchMapObserver(t, mapper, bufferSize, delayErrors)); + } + + static final class SwitchMapObserver extends AtomicInteger implements Observer, Disposable { + + private static final long serialVersionUID = -3491074160481096299L; + final Observer downstream; + final Function> mapper; + final int bufferSize; + + final boolean delayErrors; + + final AtomicThrowable errors; + + volatile boolean done; + + volatile boolean cancelled; + + Disposable upstream; + + final AtomicReference> active = new AtomicReference>(); + + static final SwitchMapInnerObserver CANCELLED; + static { + CANCELLED = new SwitchMapInnerObserver(null, -1L, 1); + CANCELLED.cancel(); + } + + volatile long unique; + + SwitchMapObserver(Observer actual, + Function> mapper, int bufferSize, + boolean delayErrors) { + this.downstream = actual; + this.mapper = mapper; + this.bufferSize = bufferSize; + this.delayErrors = delayErrors; + this.errors = new AtomicThrowable(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + long c = unique + 1; + unique = c; + + SwitchMapInnerObserver inner = active.get(); + if (inner != null) { + inner.cancel(); + } + + ObservableSource p; + try { + p = ObjectHelper.requireNonNull(mapper.apply(t), "The ObservableSource returned is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + + SwitchMapInnerObserver nextInner = new SwitchMapInnerObserver(this, c, bufferSize); + + for (;;) { + inner = active.get(); + if (inner == CANCELLED) { + break; + } + if (active.compareAndSet(inner, nextInner)) { + p.subscribe(nextInner); + break; + } + } + } + + @Override + public void onError(Throwable t) { + if (!done && errors.addThrowable(t)) { + if (!delayErrors) { + disposeInner(); + } + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + drain(); + } + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + upstream.dispose(); + disposeInner(); + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @SuppressWarnings("unchecked") + void disposeInner() { + SwitchMapInnerObserver a = active.get(); + if (a != CANCELLED) { + a = active.getAndSet((SwitchMapInnerObserver)CANCELLED); + if (a != CANCELLED && a != null) { + a.cancel(); + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + final Observer a = downstream; + final AtomicReference> active = this.active; + final boolean delayErrors = this.delayErrors; + + int missing = 1; + + for (;;) { + + if (cancelled) { + return; + } + + if (done) { + boolean empty = active.get() == null; + if (delayErrors) { + if (empty) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + } else { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + return; + } + if (empty) { + a.onComplete(); + return; + } + } + } + + SwitchMapInnerObserver inner = active.get(); + + if (inner != null) { + SimpleQueue q = inner.queue; + + if (q != null) { + if (inner.done) { + boolean empty = q.isEmpty(); + if (delayErrors) { + if (empty) { + active.compareAndSet(inner, null); + continue; + } + } else { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + return; + } + if (empty) { + active.compareAndSet(inner, null); + continue; + } + } + } + + boolean retry = false; + + for (;;) { + if (cancelled) { + return; + } + if (inner != active.get()) { + retry = true; + break; + } + + if (!delayErrors) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + return; + } + } + + boolean d = inner.done; + R v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errors.addThrowable(ex); + active.compareAndSet(inner, null); + if (!delayErrors) { + disposeInner(); + upstream.dispose(); + done = true; + } else { + inner.cancel(); + } + v = null; + retry = true; + } + boolean empty = v == null; + + if (d && empty) { + active.compareAndSet(inner, null); + retry = true; + break; + } + + if (empty) { + break; + } + + a.onNext(v); + } + + if (retry) { + continue; + } + } + } + + missing = addAndGet(-missing); + if (missing == 0) { + break; + } + } + } + + void innerError(SwitchMapInnerObserver inner, Throwable ex) { + if (inner.index == unique && errors.addThrowable(ex)) { + if (!delayErrors) { + upstream.dispose(); + done = true; + } + inner.done = true; + drain(); + } else { + RxJavaPlugins.onError(ex); + } + } + } + + static final class SwitchMapInnerObserver extends AtomicReference implements Observer { + + private static final long serialVersionUID = 3837284832786408377L; + final SwitchMapObserver parent; + final long index; + + final int bufferSize; + + volatile SimpleQueue queue; + + volatile boolean done; + + SwitchMapInnerObserver(SwitchMapObserver parent, long index, int bufferSize) { + this.parent = parent; + this.index = index; + this.bufferSize = bufferSize; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + if (d instanceof QueueDisposable) { + @SuppressWarnings("unchecked") + QueueDisposable qd = (QueueDisposable) d; + + int m = qd.requestFusion(QueueDisposable.ANY | QueueDisposable.BOUNDARY); + if (m == QueueDisposable.SYNC) { + queue = qd; + done = true; + parent.drain(); + return; + } + if (m == QueueDisposable.ASYNC) { + queue = qd; + return; + } + } + + queue = new SpscLinkedArrayQueue(bufferSize); + } + } + + @Override + public void onNext(R t) { + if (index == parent.unique) { + if (t != null) { + queue.offer(t); + } + parent.drain(); + } + } + + @Override + public void onError(Throwable t) { + parent.innerError(this, t); + } + + @Override + public void onComplete() { + if (index == parent.unique) { + done = true; + parent.drain(); + } + } + + public void cancel() { + DisposableHelper.dispose(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java new file mode 100755 index 0000000..2796357 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTake.java @@ -0,0 +1,102 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableTake extends AbstractObservableWithUpstream { + final long limit; + public ObservableTake(ObservableSource source, long limit) { + super(source); + this.limit = limit; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new TakeObserver(observer, limit)); + } + + static final class TakeObserver implements Observer, Disposable { + final Observer downstream; + + boolean done; + + Disposable upstream; + + long remaining; + TakeObserver(Observer actual, long limit) { + this.downstream = actual; + this.remaining = limit; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + upstream = d; + if (remaining == 0) { + done = true; + d.dispose(); + EmptyDisposable.complete(downstream); + } else { + downstream.onSubscribe(this); + } + } + } + + @Override + public void onNext(T t) { + if (!done && remaining-- > 0) { + boolean stop = remaining == 0; + downstream.onNext(t); + if (stop) { + onComplete(); + } + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + + done = true; + upstream.dispose(); + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + upstream.dispose(); + downstream.onComplete(); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLast.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLast.java new file mode 100755 index 0000000..92f0510 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLast.java @@ -0,0 +1,102 @@ +/** + * 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.internal.operators.observable; + +import java.util.ArrayDeque; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class ObservableTakeLast extends AbstractObservableWithUpstream { + final int count; + + public ObservableTakeLast(ObservableSource source, int count) { + super(source); + this.count = count; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new TakeLastObserver(t, count)); + } + + static final class TakeLastObserver extends ArrayDeque implements Observer, Disposable { + + private static final long serialVersionUID = 7240042530241604978L; + final Observer downstream; + final int count; + + Disposable upstream; + + volatile boolean cancelled; + + TakeLastObserver(Observer actual, int count) { + this.downstream = actual; + this.count = count; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (count == size()) { + poll(); + } + offer(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + Observer a = downstream; + for (;;) { + if (cancelled) { + return; + } + T v = poll(); + if (v == null) { + if (!cancelled) { + a.onComplete(); + } + return; + } + a.onNext(v); + } + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + upstream.dispose(); + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastOne.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastOne.java new file mode 100755 index 0000000..353f51f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastOne.java @@ -0,0 +1,85 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class ObservableTakeLastOne extends AbstractObservableWithUpstream { + + public ObservableTakeLastOne(ObservableSource source) { + super(source); + } + + @Override + public void subscribeActual(Observer observer) { + source.subscribe(new TakeLastOneObserver(observer)); + } + + static final class TakeLastOneObserver implements Observer, Disposable { + final Observer downstream; + + Disposable upstream; + + T value; + + TakeLastOneObserver(Observer downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + value = t; + } + + @Override + public void onError(Throwable t) { + value = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + emit(); + } + + void emit() { + T v = value; + if (v != null) { + value = null; + downstream.onNext(v); + } + downstream.onComplete(); + } + + @Override + public void dispose() { + value = null; + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java new file mode 100755 index 0000000..2135880 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java @@ -0,0 +1,183 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; + +public final class ObservableTakeLastTimed extends AbstractObservableWithUpstream { + final long count; + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final int bufferSize; + final boolean delayError; + + public ObservableTakeLastTimed(ObservableSource source, + long count, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { + super(source); + this.count = count; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSize = bufferSize; + this.delayError = delayError; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new TakeLastTimedObserver(t, count, time, unit, scheduler, bufferSize, delayError)); + } + + static final class TakeLastTimedObserver + extends AtomicBoolean implements Observer, Disposable { + + private static final long serialVersionUID = -5677354903406201275L; + final Observer downstream; + final long count; + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final SpscLinkedArrayQueue queue; + final boolean delayError; + + Disposable upstream; + + volatile boolean cancelled; + + Throwable error; + + TakeLastTimedObserver(Observer actual, long count, long time, TimeUnit unit, Scheduler scheduler, int bufferSize, boolean delayError) { + this.downstream = actual; + this.count = count; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.queue = new SpscLinkedArrayQueue(bufferSize); + this.delayError = delayError; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + final SpscLinkedArrayQueue q = queue; + + long now = scheduler.now(unit); + long time = this.time; + long c = count; + boolean unbounded = c == Long.MAX_VALUE; + + q.offer(now, t); + + while (!q.isEmpty()) { + long ts = (Long)q.peek(); + if (ts <= now - time || (!unbounded && (q.size() >> 1) > c)) { + q.poll(); + q.poll(); + } else { + break; + } + } + } + + @Override + public void onError(Throwable t) { + error = t; + drain(); + } + + @Override + public void onComplete() { + drain(); + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + upstream.dispose(); + + if (compareAndSet(false, true)) { + queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void drain() { + if (!compareAndSet(false, true)) { + return; + } + + final Observer a = downstream; + final SpscLinkedArrayQueue q = queue; + final boolean delayError = this.delayError; + final long timestampLimit = scheduler.now(unit) - time; + + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + if (!delayError) { + Throwable ex = error; + if (ex != null) { + q.clear(); + a.onError(ex); + return; + } + } + + Object ts = q.poll(); // the timestamp long + boolean empty = ts == null; + + if (empty) { + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + + @SuppressWarnings("unchecked") + T o = (T)q.poll(); + + if ((Long)ts < timestampLimit) { + continue; + } + + a.onNext(o); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java new file mode 100755 index 0000000..3b831b2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntil.java @@ -0,0 +1,133 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.*; + +public final class ObservableTakeUntil extends AbstractObservableWithUpstream { + + final ObservableSource other; + + public ObservableTakeUntil(ObservableSource source, ObservableSource other) { + super(source); + this.other = other; + } + + @Override + public void subscribeActual(Observer child) { + TakeUntilMainObserver parent = new TakeUntilMainObserver(child); + child.onSubscribe(parent); + + other.subscribe(parent.otherObserver); + source.subscribe(parent); + } + + static final class TakeUntilMainObserver extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = 1418547743690811973L; + + final Observer downstream; + + final AtomicReference upstream; + + final OtherObserver otherObserver; + + final AtomicThrowable error; + + TakeUntilMainObserver(Observer downstream) { + this.downstream = downstream; + this.upstream = new AtomicReference(); + this.otherObserver = new OtherObserver(); + this.error = new AtomicThrowable(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(otherObserver); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); + } + + @Override + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); + } + + @Override + public void onError(Throwable e) { + DisposableHelper.dispose(otherObserver); + HalfSerializer.onError(downstream, e, this, error); + } + + @Override + public void onComplete() { + DisposableHelper.dispose(otherObserver); + HalfSerializer.onComplete(downstream, this, error); + } + + void otherError(Throwable e) { + DisposableHelper.dispose(upstream); + HalfSerializer.onError(downstream, e, this, error); + } + + void otherComplete() { + DisposableHelper.dispose(upstream); + HalfSerializer.onComplete(downstream, this, error); + } + + final class OtherObserver extends AtomicReference + implements Observer { + + private static final long serialVersionUID = -8693423678067375039L; + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(U t) { + DisposableHelper.dispose(this); + otherComplete(); + } + + @Override + public void onError(Throwable e) { + otherError(e); + } + + @Override + public void onComplete() { + otherComplete(); + } + + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicate.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicate.java new file mode 100755 index 0000000..cd984f2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicate.java @@ -0,0 +1,102 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableTakeUntilPredicate extends AbstractObservableWithUpstream { + final Predicate predicate; + public ObservableTakeUntilPredicate(ObservableSource source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + public void subscribeActual(Observer observer) { + source.subscribe(new TakeUntilPredicateObserver(observer, predicate)); + } + + static final class TakeUntilPredicateObserver implements Observer, Disposable { + final Observer downstream; + final Predicate predicate; + Disposable upstream; + boolean done; + TakeUntilPredicateObserver(Observer downstream, Predicate predicate) { + this.downstream = downstream; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (!done) { + downstream.onNext(t); + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + if (b) { + done = true; + upstream.dispose(); + downstream.onComplete(); + } + } + } + + @Override + public void onError(Throwable t) { + if (!done) { + done = true; + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java new file mode 100755 index 0000000..c57e6df --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeWhile.java @@ -0,0 +1,110 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableTakeWhile extends AbstractObservableWithUpstream { + final Predicate predicate; + public ObservableTakeWhile(ObservableSource source, Predicate predicate) { + super(source); + this.predicate = predicate; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new TakeWhileObserver(t, predicate)); + } + + static final class TakeWhileObserver implements Observer, Disposable { + final Observer downstream; + final Predicate predicate; + + Disposable upstream; + + boolean done; + + TakeWhileObserver(Observer actual, Predicate predicate) { + this.downstream = actual; + this.predicate = predicate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + boolean b; + try { + b = predicate.test(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + upstream.dispose(); + onError(e); + return; + } + + if (!b) { + done = true; + upstream.dispose(); + downstream.onComplete(); + return; + } + + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java new file mode 100755 index 0000000..d711c36 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleFirstTimed.java @@ -0,0 +1,128 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.observers.SerializedObserver; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableThrottleFirstTimed extends AbstractObservableWithUpstream { + final long timeout; + final TimeUnit unit; + final Scheduler scheduler; + + public ObservableThrottleFirstTimed(ObservableSource source, + long timeout, TimeUnit unit, Scheduler scheduler) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new DebounceTimedObserver( + new SerializedObserver(t), + timeout, unit, scheduler.createWorker())); + } + + static final class DebounceTimedObserver + extends AtomicReference + implements Observer, Disposable, Runnable { + private static final long serialVersionUID = 786994795061867455L; + + final Observer downstream; + final long timeout; + final TimeUnit unit; + final Worker worker; + + Disposable upstream; + + volatile boolean gate; + + boolean done; + + DebounceTimedObserver(Observer actual, long timeout, TimeUnit unit, Worker worker) { + this.downstream = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!gate && !done) { + gate = true; + + downstream.onNext(t); + + Disposable d = get(); + if (d != null) { + d.dispose(); + } + DisposableHelper.replace(this, worker.schedule(this, timeout, unit)); + } + } + + @Override + public void run() { + gate = false; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + } else { + done = true; + downstream.onError(t); + worker.dispose(); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + worker.dispose(); + } + } + + @Override + public void dispose() { + upstream.dispose(); + worker.dispose(); + } + + @Override + public boolean isDisposed() { + return worker.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleLatest.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleLatest.java new file mode 100755 index 0000000..39e0f0b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableThrottleLatest.java @@ -0,0 +1,214 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Emits the next or latest item when the given time elapses. + *

+ * The operator emits the next item, then starts a timer. When the timer fires, + * it tries to emit the latest item from upstream. If there was no upstream item, + * in the meantime, the next upstream item is emitted immediately and the + * timed process repeats. + *

History: 2.1.14 - experimental + * @param the upstream and downstream value type + * @since 2.2 + */ +public final class ObservableThrottleLatest extends AbstractObservableWithUpstream { + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + final boolean emitLast; + + public ObservableThrottleLatest(Observable source, + long timeout, TimeUnit unit, Scheduler scheduler, + boolean emitLast) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.emitLast = emitLast; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new ThrottleLatestObserver(observer, timeout, unit, scheduler.createWorker(), emitLast)); + } + + static final class ThrottleLatestObserver + extends AtomicInteger + implements Observer, Disposable, Runnable { + + private static final long serialVersionUID = -8296689127439125014L; + + final Observer downstream; + + final long timeout; + + final TimeUnit unit; + + final Scheduler.Worker worker; + + final boolean emitLast; + + final AtomicReference latest; + + Disposable upstream; + + volatile boolean done; + Throwable error; + + volatile boolean cancelled; + + volatile boolean timerFired; + + boolean timerRunning; + + ThrottleLatestObserver(Observer downstream, + long timeout, TimeUnit unit, Scheduler.Worker worker, + boolean emitLast) { + this.downstream = downstream; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.emitLast = emitLast; + this.latest = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + latest.set(t); + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + worker.dispose(); + if (getAndIncrement() == 0) { + latest.lazySet(null); + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public void run() { + timerFired = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + + AtomicReference latest = this.latest; + Observer downstream = this.downstream; + + for (;;) { + + for (;;) { + if (cancelled) { + latest.lazySet(null); + return; + } + + boolean d = done; + + if (d && error != null) { + latest.lazySet(null); + downstream.onError(error); + worker.dispose(); + return; + } + + T v = latest.get(); + boolean empty = v == null; + + if (d) { + v = latest.getAndSet(null); + if (!empty && emitLast) { + downstream.onNext(v); + } + downstream.onComplete(); + worker.dispose(); + return; + } + + if (empty) { + if (timerFired) { + timerRunning = false; + timerFired = false; + } + break; + } + + if (!timerRunning || timerFired) { + v = latest.getAndSet(null); + downstream.onNext(v); + + timerFired = false; + timerRunning = true; + worker.schedule(this, timeout, unit); + } else { + break; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java new file mode 100755 index 0000000..7d06b18 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeInterval.java @@ -0,0 +1,91 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.schedulers.Timed; + +public final class ObservableTimeInterval extends AbstractObservableWithUpstream> { + final Scheduler scheduler; + final TimeUnit unit; + + public ObservableTimeInterval(ObservableSource source, TimeUnit unit, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + this.unit = unit; + } + + @Override + public void subscribeActual(Observer> t) { + source.subscribe(new TimeIntervalObserver(t, unit, scheduler)); + } + + static final class TimeIntervalObserver implements Observer, Disposable { + final Observer> downstream; + final TimeUnit unit; + final Scheduler scheduler; + + long lastTime; + + Disposable upstream; + + TimeIntervalObserver(Observer> actual, TimeUnit unit, Scheduler scheduler) { + this.downstream = actual; + this.scheduler = scheduler; + this.unit = unit; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + lastTime = scheduler.now(unit); + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + long now = scheduler.now(unit); + long last = lastTime; + lastTime = now; + long delta = now - last; + downstream.onNext(new Timed(t, delta, unit)); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeout.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeout.java new file mode 100755 index 0000000..2aeb905 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeout.java @@ -0,0 +1,378 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.observable.ObservableTimeoutTimed.TimeoutSupport; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableTimeout extends AbstractObservableWithUpstream { + final ObservableSource firstTimeoutIndicator; + final Function> itemTimeoutIndicator; + final ObservableSource other; + + public ObservableTimeout( + Observable source, + ObservableSource firstTimeoutIndicator, + Function> itemTimeoutIndicator, + ObservableSource other) { + super(source); + this.firstTimeoutIndicator = firstTimeoutIndicator; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.other = other; + } + + @Override + protected void subscribeActual(Observer observer) { + if (other == null) { + TimeoutObserver parent = new TimeoutObserver(observer, itemTimeoutIndicator); + observer.onSubscribe(parent); + parent.startFirstTimeout(firstTimeoutIndicator); + source.subscribe(parent); + } else { + TimeoutFallbackObserver parent = new TimeoutFallbackObserver(observer, itemTimeoutIndicator, other); + observer.onSubscribe(parent); + parent.startFirstTimeout(firstTimeoutIndicator); + source.subscribe(parent); + } + } + + interface TimeoutSelectorSupport extends TimeoutSupport { + void onTimeoutError(long idx, Throwable ex); + } + + static final class TimeoutObserver extends AtomicLong + implements Observer, Disposable, TimeoutSelectorSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Observer downstream; + + final Function> itemTimeoutIndicator; + + final SequentialDisposable task; + + final AtomicReference upstream; + + TimeoutObserver(Observer actual, Function> itemTimeoutIndicator) { + this.downstream = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); + } + + @Override + public void onNext(T t) { + long idx = get(); + if (idx == Long.MAX_VALUE || !compareAndSet(idx, idx + 1)) { + return; + } + + Disposable d = task.get(); + if (d != null) { + d.dispose(); + } + + downstream.onNext(t); + + ObservableSource itemTimeoutObservableSource; + + try { + itemTimeoutObservableSource = ObjectHelper.requireNonNull( + itemTimeoutIndicator.apply(t), + "The itemTimeoutIndicator returned a null ObservableSource."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().dispose(); + getAndSet(Long.MAX_VALUE); + downstream.onError(ex); + return; + } + + TimeoutConsumer consumer = new TimeoutConsumer(idx + 1, this); + if (task.replace(consumer)) { + itemTimeoutObservableSource.subscribe(consumer); + } + } + + void startFirstTimeout(ObservableSource firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer consumer = new TimeoutConsumer(0L, this); + if (task.replace(consumer)) { + firstTimeoutIndicator.subscribe(consumer); + } + } + } + + @Override + public void onError(Throwable t) { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + } + } + + @Override + public void onTimeout(long idx) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); + + downstream.onError(new TimeoutException()); + } + } + + @Override + public void onTimeoutError(long idx, Throwable ex) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); + + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + task.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } + } + + static final class TimeoutFallbackObserver + extends AtomicReference + implements Observer, Disposable, TimeoutSelectorSupport { + + private static final long serialVersionUID = -7508389464265974549L; + + final Observer downstream; + + final Function> itemTimeoutIndicator; + + final SequentialDisposable task; + + final AtomicLong index; + + final AtomicReference upstream; + + ObservableSource fallback; + + TimeoutFallbackObserver(Observer actual, + Function> itemTimeoutIndicator, + ObservableSource fallback) { + this.downstream = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.task = new SequentialDisposable(); + this.fallback = fallback; + this.index = new AtomicLong(); + this.upstream = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); + } + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + Disposable d = task.get(); + if (d != null) { + d.dispose(); + } + + downstream.onNext(t); + + ObservableSource itemTimeoutObservableSource; + + try { + itemTimeoutObservableSource = ObjectHelper.requireNonNull( + itemTimeoutIndicator.apply(t), + "The itemTimeoutIndicator returned a null ObservableSource."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().dispose(); + index.getAndSet(Long.MAX_VALUE); + downstream.onError(ex); + return; + } + + TimeoutConsumer consumer = new TimeoutConsumer(idx + 1, this); + if (task.replace(consumer)) { + itemTimeoutObservableSource.subscribe(consumer); + } + } + + void startFirstTimeout(ObservableSource firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer consumer = new TimeoutConsumer(0L, this); + if (task.replace(consumer)) { + firstTimeoutIndicator.subscribe(consumer); + } + } + } + + @Override + public void onError(Throwable t) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + task.dispose(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + task.dispose(); + } + } + + @Override + public void onTimeout(long idx) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); + + ObservableSource f = fallback; + fallback = null; + + f.subscribe(new ObservableTimeoutTimed.FallbackObserver(downstream, this)); + } + } + + @Override + public void onTimeoutError(long idx, Throwable ex) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(this); + + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(this); + task.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + + static final class TimeoutConsumer extends AtomicReference + implements Observer, Disposable { + + private static final long serialVersionUID = 8708641127342403073L; + + final TimeoutSelectorSupport parent; + + final long idx; + + TimeoutConsumer(long idx, TimeoutSelectorSupport parent) { + this.idx = idx; + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Object t) { + Disposable upstream = get(); + if (upstream != DisposableHelper.DISPOSED) { + upstream.dispose(); + lazySet(DisposableHelper.DISPOSED); + parent.onTimeout(idx); + } + } + + @Override + public void onError(Throwable t) { + if (get() != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + parent.onTimeoutError(idx, t); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (get() != DisposableHelper.DISPOSED) { + lazySet(DisposableHelper.DISPOSED); + parent.onTimeout(idx); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(this.get()); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java new file mode 100755 index 0000000..9e3cb8a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimeoutTimed.java @@ -0,0 +1,313 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + +public final class ObservableTimeoutTimed extends AbstractObservableWithUpstream { + final long timeout; + final TimeUnit unit; + final Scheduler scheduler; + final ObservableSource other; + + public ObservableTimeoutTimed(Observable source, + long timeout, TimeUnit unit, Scheduler scheduler, ObservableSource other) { + super(source); + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.other = other; + } + + @Override + protected void subscribeActual(Observer observer) { + if (other == null) { + TimeoutObserver parent = new TimeoutObserver(observer, timeout, unit, scheduler.createWorker()); + observer.onSubscribe(parent); + parent.startTimeout(0L); + source.subscribe(parent); + } else { + TimeoutFallbackObserver parent = new TimeoutFallbackObserver(observer, timeout, unit, scheduler.createWorker(), other); + observer.onSubscribe(parent); + parent.startTimeout(0L); + source.subscribe(parent); + } + } + + static final class TimeoutObserver extends AtomicLong + implements Observer, Disposable, TimeoutSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Observer downstream; + + final long timeout; + + final TimeUnit unit; + + final Scheduler.Worker worker; + + final SequentialDisposable task; + + final AtomicReference upstream; + + TimeoutObserver(Observer actual, long timeout, TimeUnit unit, Scheduler.Worker worker) { + this.downstream = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.task = new SequentialDisposable(); + this.upstream = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); + } + + @Override + public void onNext(T t) { + long idx = get(); + if (idx == Long.MAX_VALUE || !compareAndSet(idx, idx + 1)) { + return; + } + + task.get().dispose(); + + downstream.onNext(t); + + startTimeout(idx + 1); + } + + void startTimeout(long nextIndex) { + task.replace(worker.schedule(new TimeoutTask(nextIndex, this), timeout, unit)); + } + + @Override + public void onError(Throwable t) { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + worker.dispose(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + worker.dispose(); + } + } + + @Override + public void onTimeout(long idx) { + if (compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); + + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); + + worker.dispose(); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + worker.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } + } + + static final class TimeoutTask implements Runnable { + + final TimeoutSupport parent; + + final long idx; + + TimeoutTask(long idx, TimeoutSupport parent) { + this.idx = idx; + this.parent = parent; + } + + @Override + public void run() { + parent.onTimeout(idx); + } + } + + static final class TimeoutFallbackObserver extends AtomicReference + implements Observer, Disposable, TimeoutSupport { + + private static final long serialVersionUID = 3764492702657003550L; + + final Observer downstream; + + final long timeout; + + final TimeUnit unit; + + final Scheduler.Worker worker; + + final SequentialDisposable task; + + final AtomicLong index; + + final AtomicReference upstream; + + ObservableSource fallback; + + TimeoutFallbackObserver(Observer actual, long timeout, TimeUnit unit, + Scheduler.Worker worker, ObservableSource fallback) { + this.downstream = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.fallback = fallback; + this.task = new SequentialDisposable(); + this.index = new AtomicLong(); + this.upstream = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); + } + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + task.get().dispose(); + + downstream.onNext(t); + + startTimeout(idx + 1); + } + + void startTimeout(long nextIndex) { + task.replace(worker.schedule(new TimeoutTask(nextIndex, this), timeout, unit)); + } + + @Override + public void onError(Throwable t) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onError(t); + + worker.dispose(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.dispose(); + + downstream.onComplete(); + + worker.dispose(); + } + } + + @Override + public void onTimeout(long idx) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + DisposableHelper.dispose(upstream); + + ObservableSource f = fallback; + fallback = null; + + f.subscribe(new FallbackObserver(downstream, this)); + + worker.dispose(); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(this); + worker.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + + static final class FallbackObserver implements Observer { + + final Observer downstream; + + final AtomicReference arbiter; + + FallbackObserver(Observer actual, AtomicReference arbiter) { + this.downstream = actual; + this.arbiter = arbiter; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(arbiter, d); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } + + interface TimeoutSupport { + + void onTimeout(long idx); + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTimer.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimer.java new file mode 100755 index 0000000..c635ccb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTimer.java @@ -0,0 +1,77 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; + +public final class ObservableTimer extends Observable { + final Scheduler scheduler; + final long delay; + final TimeUnit unit; + public ObservableTimer(long delay, TimeUnit unit, Scheduler scheduler) { + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public void subscribeActual(Observer observer) { + TimerObserver ios = new TimerObserver(observer); + observer.onSubscribe(ios); + + Disposable d = scheduler.scheduleDirect(ios, delay, unit); + + ios.setResource(d); + } + + static final class TimerObserver extends AtomicReference + implements Disposable, Runnable { + + private static final long serialVersionUID = -2809475196591179431L; + + final Observer downstream; + + TimerObserver(Observer downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return get() == DisposableHelper.DISPOSED; + } + + @Override + public void run() { + if (!isDisposed()) { + downstream.onNext(0L); + lazySet(EmptyDisposable.INSTANCE); + downstream.onComplete(); + } + } + + public void setResource(Disposable d) { + DisposableHelper.trySet(this, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java new file mode 100755 index 0000000..afeee53 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableToList.java @@ -0,0 +1,103 @@ +/** + * 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.internal.operators.observable; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.*; + +public final class ObservableToList> +extends AbstractObservableWithUpstream { + + final Callable collectionSupplier; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ObservableToList(ObservableSource source, final int defaultCapacityHint) { + super(source); + this.collectionSupplier = (Callable)Functions.createArrayList(defaultCapacityHint); + } + + public ObservableToList(ObservableSource source, Callable collectionSupplier) { + super(source); + this.collectionSupplier = collectionSupplier; + } + + @Override + public void subscribeActual(Observer t) { + U coll; + try { + coll = ObjectHelper.requireNonNull(collectionSupplier.call(), "The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, t); + return; + } + source.subscribe(new ToListObserver(t, coll)); + } + + static final class ToListObserver> implements Observer, Disposable { + final Observer downstream; + + Disposable upstream; + + U collection; + + ToListObserver(Observer actual, U collection) { + this.downstream = actual; + this.collection = collection; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + collection.add(t); + } + + @Override + public void onError(Throwable t) { + collection = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + U c = collection; + collection = null; + downstream.onNext(c); + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java new file mode 100755 index 0000000..410af1e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableToListSingle.java @@ -0,0 +1,114 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.*; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.FuseToObservable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableToListSingle> +extends Single implements FuseToObservable { + + final ObservableSource source; + + final Callable collectionSupplier; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ObservableToListSingle(ObservableSource source, final int defaultCapacityHint) { + this.source = source; + this.collectionSupplier = (Callable)Functions.createArrayList(defaultCapacityHint); + } + + public ObservableToListSingle(ObservableSource source, Callable collectionSupplier) { + this.source = source; + this.collectionSupplier = collectionSupplier; + } + + @Override + public void subscribeActual(SingleObserver t) { + U coll; + try { + coll = ObjectHelper.requireNonNull(collectionSupplier.call(), "The collectionSupplier returned a null collection. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, t); + return; + } + source.subscribe(new ToListObserver(t, coll)); + } + + @Override + public Observable fuseToObservable() { + return RxJavaPlugins.onAssembly(new ObservableToList(source, collectionSupplier)); + } + + static final class ToListObserver> implements Observer, Disposable { + final SingleObserver downstream; + + U collection; + + Disposable upstream; + + ToListObserver(SingleObserver actual, U collection) { + this.downstream = actual; + this.collection = collection; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + collection.add(t); + } + + @Override + public void onError(Throwable t) { + collection = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + U c = collection; + collection = null; + downstream.onSuccess(c); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOn.java new file mode 100755 index 0000000..5f4ecad --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOn.java @@ -0,0 +1,99 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableUnsubscribeOn extends AbstractObservableWithUpstream { + final Scheduler scheduler; + public ObservableUnsubscribeOn(ObservableSource source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new UnsubscribeObserver(t, scheduler)); + } + + static final class UnsubscribeObserver extends AtomicBoolean implements Observer, Disposable { + + private static final long serialVersionUID = 1015244841293359600L; + + final Observer downstream; + final Scheduler scheduler; + + Disposable upstream; + + UnsubscribeObserver(Observer actual, Scheduler scheduler) { + this.downstream = actual; + this.scheduler = scheduler; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!get()) { + downstream.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + if (get()) { + RxJavaPlugins.onError(t); + return; + } + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!get()) { + downstream.onComplete(); + } + } + + @Override + public void dispose() { + if (compareAndSet(false, true)) { + scheduler.scheduleDirect(new DisposeTask()); + } + } + + @Override + public boolean isDisposed() { + return get(); + } + + final class DisposeTask implements Runnable { + @Override + public void run() { + upstream.dispose(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableUsing.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableUsing.java new file mode 100755 index 0000000..46806ae --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableUsing.java @@ -0,0 +1,173 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableUsing extends Observable { + final Callable resourceSupplier; + final Function> sourceSupplier; + final Consumer disposer; + final boolean eager; + + public ObservableUsing(Callable resourceSupplier, + Function> sourceSupplier, + Consumer disposer, + boolean eager) { + this.resourceSupplier = resourceSupplier; + this.sourceSupplier = sourceSupplier; + this.disposer = disposer; + this.eager = eager; + } + + @Override + public void subscribeActual(Observer observer) { + D resource; + + try { + resource = resourceSupplier.call(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + + ObservableSource source; + try { + source = ObjectHelper.requireNonNull(sourceSupplier.apply(resource), "The sourceSupplier returned a null ObservableSource"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + try { + disposer.accept(resource); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(new CompositeException(e, ex), observer); + return; + } + EmptyDisposable.error(e, observer); + return; + } + + UsingObserver us = new UsingObserver(observer, resource, disposer, eager); + + source.subscribe(us); + } + + static final class UsingObserver extends AtomicBoolean implements Observer, Disposable { + + private static final long serialVersionUID = 5904473792286235046L; + + final Observer downstream; + final D resource; + final Consumer disposer; + final boolean eager; + + Disposable upstream; + + UsingObserver(Observer actual, D resource, Consumer disposer, boolean eager) { + this.downstream = actual; + this.resource = resource; + this.disposer = disposer; + this.eager = eager; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (eager) { + if (compareAndSet(false, true)) { + try { + disposer.accept(resource); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + t = new CompositeException(t, e); + } + } + + upstream.dispose(); + downstream.onError(t); + } else { + downstream.onError(t); + upstream.dispose(); + disposeAfter(); + } + } + + @Override + public void onComplete() { + if (eager) { + if (compareAndSet(false, true)) { + try { + disposer.accept(resource); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + } + + upstream.dispose(); + downstream.onComplete(); + } else { + downstream.onComplete(); + upstream.dispose(); + disposeAfter(); + } + } + + @Override + public void dispose() { + disposeAfter(); + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return get(); + } + + void disposeAfter() { + if (compareAndSet(false, true)) { + try { + disposer.accept(resource); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call actual.onError unless it is serialized, which is expensive + RxJavaPlugins.onError(e); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindow.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindow.java new file mode 100755 index 0000000..0c5c50d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindow.java @@ -0,0 +1,247 @@ +/** + * 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.internal.operators.observable; + +import java.util.ArrayDeque; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.subjects.UnicastSubject; + +public final class ObservableWindow extends AbstractObservableWithUpstream> { + final long count; + final long skip; + final int capacityHint; + + public ObservableWindow(ObservableSource source, long count, long skip, int capacityHint) { + super(source); + this.count = count; + this.skip = skip; + this.capacityHint = capacityHint; + } + + @Override + public void subscribeActual(Observer> t) { + if (count == skip) { + source.subscribe(new WindowExactObserver(t, count, capacityHint)); + } else { + source.subscribe(new WindowSkipObserver(t, count, skip, capacityHint)); + } + } + + static final class WindowExactObserver + extends AtomicInteger + implements Observer, Disposable, Runnable { + + private static final long serialVersionUID = -7481782523886138128L; + final Observer> downstream; + final long count; + final int capacityHint; + + long size; + + Disposable upstream; + + UnicastSubject window; + + volatile boolean cancelled; + + WindowExactObserver(Observer> actual, long count, int capacityHint) { + this.downstream = actual; + this.count = count; + this.capacityHint = capacityHint; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + UnicastSubject w = window; + if (w == null && !cancelled) { + w = UnicastSubject.create(capacityHint, this); + window = w; + downstream.onNext(w); + } + + if (w != null) { + w.onNext(t); + if (++size >= count) { + size = 0; + window = null; + w.onComplete(); + if (cancelled) { + upstream.dispose(); + } + } + } + } + + @Override + public void onError(Throwable t) { + UnicastSubject w = window; + if (w != null) { + window = null; + w.onError(t); + } + downstream.onError(t); + } + + @Override + public void onComplete() { + UnicastSubject w = window; + if (w != null) { + window = null; + w.onComplete(); + } + downstream.onComplete(); + } + + @Override + public void dispose() { + cancelled = true; + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public void run() { + if (cancelled) { + upstream.dispose(); + } + } + } + + static final class WindowSkipObserver extends AtomicBoolean + implements Observer, Disposable, Runnable { + + private static final long serialVersionUID = 3366976432059579510L; + final Observer> downstream; + final long count; + final long skip; + final int capacityHint; + final ArrayDeque> windows; + + long index; + + volatile boolean cancelled; + + /** Counts how many elements were emitted to the very first window in windows. */ + long firstEmission; + + Disposable upstream; + + final AtomicInteger wip = new AtomicInteger(); + + WindowSkipObserver(Observer> actual, long count, long skip, int capacityHint) { + this.downstream = actual; + this.count = count; + this.skip = skip; + this.capacityHint = capacityHint; + this.windows = new ArrayDeque>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + final ArrayDeque> ws = windows; + + long i = index; + + long s = skip; + + if (i % s == 0 && !cancelled) { + wip.getAndIncrement(); + UnicastSubject w = UnicastSubject.create(capacityHint, this); + ws.offer(w); + downstream.onNext(w); + } + + long c = firstEmission + 1; + + for (UnicastSubject w : ws) { + w.onNext(t); + } + + if (c >= count) { + ws.poll().onComplete(); + if (ws.isEmpty() && cancelled) { + this.upstream.dispose(); + return; + } + firstEmission = c - s; + } else { + firstEmission = c; + } + + index = i + 1; + } + + @Override + public void onError(Throwable t) { + final ArrayDeque> ws = windows; + while (!ws.isEmpty()) { + ws.poll().onError(t); + } + downstream.onError(t); + } + + @Override + public void onComplete() { + final ArrayDeque> ws = windows; + while (!ws.isEmpty()) { + ws.poll().onComplete(); + } + downstream.onComplete(); + } + + @Override + public void dispose() { + cancelled = true; + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public void run() { + if (wip.decrementAndGet() == 0) { + if (cancelled) { + upstream.dispose(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundary.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundary.java new file mode 100755 index 0000000..a0fb1f9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundary.java @@ -0,0 +1,286 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.observers.DisposableObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.UnicastSubject; + +public final class ObservableWindowBoundary extends AbstractObservableWithUpstream> { + final ObservableSource other; + final int capacityHint; + + public ObservableWindowBoundary(ObservableSource source, ObservableSource other, int capacityHint) { + super(source); + this.other = other; + this.capacityHint = capacityHint; + } + + @Override + public void subscribeActual(Observer> observer) { + WindowBoundaryMainObserver parent = new WindowBoundaryMainObserver(observer, capacityHint); + + observer.onSubscribe(parent); + other.subscribe(parent.boundaryObserver); + + source.subscribe(parent); + } + + static final class WindowBoundaryMainObserver + extends AtomicInteger + implements Observer, Disposable, Runnable { + + private static final long serialVersionUID = 2233020065421370272L; + + final Observer> downstream; + + final int capacityHint; + + final WindowBoundaryInnerObserver boundaryObserver; + + final AtomicReference upstream; + + final AtomicInteger windows; + + final MpscLinkedQueue queue; + + final AtomicThrowable errors; + + final AtomicBoolean stopWindows; + + static final Object NEXT_WINDOW = new Object(); + + volatile boolean done; + + UnicastSubject window; + + WindowBoundaryMainObserver(Observer> downstream, int capacityHint) { + this.downstream = downstream; + this.capacityHint = capacityHint; + this.boundaryObserver = new WindowBoundaryInnerObserver(this); + this.upstream = new AtomicReference(); + this.windows = new AtomicInteger(1); + this.queue = new MpscLinkedQueue(); + this.errors = new AtomicThrowable(); + this.stopWindows = new AtomicBoolean(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(upstream, d)) { + + innerNext(); + } + } + + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable e) { + boundaryObserver.dispose(); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + boundaryObserver.dispose(); + done = true; + drain(); + } + + @Override + public void dispose() { + if (stopWindows.compareAndSet(false, true)) { + boundaryObserver.dispose(); + if (windows.decrementAndGet() == 0) { + DisposableHelper.dispose(upstream); + } + } + } + + @Override + public boolean isDisposed() { + return stopWindows.get(); + } + + @Override + public void run() { + if (windows.decrementAndGet() == 0) { + DisposableHelper.dispose(upstream); + } + } + + void innerNext() { + queue.offer(NEXT_WINDOW); + drain(); + } + + void innerError(Throwable e) { + DisposableHelper.dispose(upstream); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + void innerComplete() { + DisposableHelper.dispose(upstream); + done = true; + drain(); + } + + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer> downstream = this.downstream; + MpscLinkedQueue queue = this.queue; + AtomicThrowable errors = this.errors; + + for (;;) { + + for (;;) { + if (windows.get() == 0) { + queue.clear(); + window = null; + return; + } + + UnicastSubject w = window; + + boolean d = done; + + if (d && errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + return; + } + + Object v = queue.poll(); + + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + if (w != null) { + window = null; + w.onComplete(); + } + downstream.onComplete(); + } else { + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + if (v != NEXT_WINDOW) { + w.onNext((T)v); + continue; + } + + if (w != null) { + window = null; + w.onComplete(); + } + + if (!stopWindows.get()) { + w = UnicastSubject.create(capacityHint, this); + window = w; + windows.getAndIncrement(); + + downstream.onNext(w); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class WindowBoundaryInnerObserver extends DisposableObserver { + + final WindowBoundaryMainObserver parent; + + boolean done; + + WindowBoundaryInnerObserver(WindowBoundaryMainObserver parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + if (done) { + return; + } + parent.innerNext(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + parent.innerError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.innerComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java new file mode 100755 index 0000000..d8e745e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySelector.java @@ -0,0 +1,369 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.atomic.*; + +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.QueueDrainObserver; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.util.NotificationLite; +import io.reactivex.observers.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.UnicastSubject; + +public final class ObservableWindowBoundarySelector extends AbstractObservableWithUpstream> { + final ObservableSource open; + final Function> close; + final int bufferSize; + + public ObservableWindowBoundarySelector( + ObservableSource source, + ObservableSource open, Function> close, + int bufferSize) { + super(source); + this.open = open; + this.close = close; + this.bufferSize = bufferSize; + } + + @Override + public void subscribeActual(Observer> t) { + source.subscribe(new WindowBoundaryMainObserver( + new SerializedObserver>(t), + open, close, bufferSize)); + } + + static final class WindowBoundaryMainObserver + extends QueueDrainObserver> + implements Disposable { + final ObservableSource open; + final Function> close; + final int bufferSize; + final CompositeDisposable resources; + + Disposable upstream; + + final AtomicReference boundary = new AtomicReference(); + + final List> ws; + + final AtomicLong windows = new AtomicLong(); + + final AtomicBoolean stopWindows = new AtomicBoolean(); + + WindowBoundaryMainObserver(Observer> actual, + ObservableSource open, Function> close, int bufferSize) { + super(actual, new MpscLinkedQueue()); + this.open = open; + this.close = close; + this.bufferSize = bufferSize; + this.resources = new CompositeDisposable(); + this.ws = new ArrayList>(); + windows.lazySet(1); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + + if (stopWindows.get()) { + return; + } + + OperatorWindowBoundaryOpenObserver os = new OperatorWindowBoundaryOpenObserver(this); + + if (boundary.compareAndSet(null, os)) { + open.subscribe(os); + } + } + } + + @Override + public void onNext(T t) { + if (fastEnter()) { + for (UnicastSubject w : ws) { + w.onNext(t); + } + if (leave(-1) == 0) { + return; + } + } else { + queue.offer(NotificationLite.next(t)); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + + if (enter()) { + drainLoop(); + } + + if (windows.decrementAndGet() == 0) { + resources.dispose(); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + if (enter()) { + drainLoop(); + } + + if (windows.decrementAndGet() == 0) { + resources.dispose(); + } + + downstream.onComplete(); + } + + void error(Throwable t) { + upstream.dispose(); + resources.dispose(); + onError(t); + } + + @Override + public void dispose() { + if (stopWindows.compareAndSet(false, true)) { + DisposableHelper.dispose(boundary); + if (windows.decrementAndGet() == 0) { + upstream.dispose(); + } + } + } + + @Override + public boolean isDisposed() { + return stopWindows.get(); + } + + void disposeBoundary() { + resources.dispose(); + DisposableHelper.dispose(boundary); + } + + void drainLoop() { + final MpscLinkedQueue q = (MpscLinkedQueue)queue; + final Observer> a = downstream; + final List> ws = this.ws; + int missed = 1; + + for (;;) { + + for (;;) { + boolean d = done; + + Object o = q.poll(); + + boolean empty = o == null; + + if (d && empty) { + disposeBoundary(); + Throwable e = error; + if (e != null) { + for (UnicastSubject w : ws) { + w.onError(e); + } + } else { + for (UnicastSubject w : ws) { + w.onComplete(); + } + } + ws.clear(); + return; + } + + if (empty) { + break; + } + + if (o instanceof WindowOperation) { + @SuppressWarnings("unchecked") + WindowOperation wo = (WindowOperation) o; + + UnicastSubject w = wo.w; + if (w != null) { + if (ws.remove(wo.w)) { + wo.w.onComplete(); + + if (windows.decrementAndGet() == 0) { + disposeBoundary(); + return; + } + } + continue; + } + + if (stopWindows.get()) { + continue; + } + + w = UnicastSubject.create(bufferSize); + + ws.add(w); + a.onNext(w); + + ObservableSource p; + + try { + p = ObjectHelper.requireNonNull(close.apply(wo.open), "The ObservableSource supplied is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + stopWindows.set(true); + a.onError(e); + continue; + } + + OperatorWindowBoundaryCloseObserver cl = new OperatorWindowBoundaryCloseObserver(this, w); + + if (resources.add(cl)) { + windows.getAndIncrement(); + + p.subscribe(cl); + } + + continue; + } + + for (UnicastSubject w : ws) { + w.onNext(NotificationLite.getValue(o)); + } + } + + missed = leave(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void accept(Observer> a, Object v) { + } + + void open(B b) { + queue.offer(new WindowOperation(null, b)); + if (enter()) { + drainLoop(); + } + } + + void close(OperatorWindowBoundaryCloseObserver w) { + resources.delete(w); + queue.offer(new WindowOperation(w.w, null)); + if (enter()) { + drainLoop(); + } + } + } + + static final class WindowOperation { + final UnicastSubject w; + final B open; + WindowOperation(UnicastSubject w, B open) { + this.w = w; + this.open = open; + } + } + + static final class OperatorWindowBoundaryOpenObserver extends DisposableObserver { + final WindowBoundaryMainObserver parent; + + OperatorWindowBoundaryOpenObserver(WindowBoundaryMainObserver parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + parent.open(t); + } + + @Override + public void onError(Throwable t) { + parent.error(t); + } + + @Override + public void onComplete() { + parent.onComplete(); + } + } + + static final class OperatorWindowBoundaryCloseObserver extends DisposableObserver { + final WindowBoundaryMainObserver parent; + final UnicastSubject w; + + boolean done; + + OperatorWindowBoundaryCloseObserver(WindowBoundaryMainObserver parent, UnicastSubject w) { + this.parent = parent; + this.w = w; + } + + @Override + public void onNext(V t) { + dispose(); + onComplete(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + parent.error(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.close(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySupplier.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySupplier.java new file mode 100755 index 0000000..c2d3190 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowBoundarySupplier.java @@ -0,0 +1,321 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.util.AtomicThrowable; +import io.reactivex.observers.DisposableObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subjects.UnicastSubject; + +public final class ObservableWindowBoundarySupplier extends AbstractObservableWithUpstream> { + final Callable> other; + final int capacityHint; + + public ObservableWindowBoundarySupplier( + ObservableSource source, + Callable> other, int capacityHint) { + super(source); + this.other = other; + this.capacityHint = capacityHint; + } + + @Override + public void subscribeActual(Observer> observer) { + WindowBoundaryMainObserver parent = new WindowBoundaryMainObserver(observer, capacityHint, other); + + source.subscribe(parent); + } + + static final class WindowBoundaryMainObserver + extends AtomicInteger + implements Observer, Disposable, Runnable { + + private static final long serialVersionUID = 2233020065421370272L; + + final Observer> downstream; + + final int capacityHint; + + final AtomicReference> boundaryObserver; + + static final WindowBoundaryInnerObserver BOUNDARY_DISPOSED = new WindowBoundaryInnerObserver(null); + + final AtomicInteger windows; + + final MpscLinkedQueue queue; + + final AtomicThrowable errors; + + final AtomicBoolean stopWindows; + + final Callable> other; + + static final Object NEXT_WINDOW = new Object(); + + Disposable upstream; + + volatile boolean done; + + UnicastSubject window; + + WindowBoundaryMainObserver(Observer> downstream, int capacityHint, Callable> other) { + this.downstream = downstream; + this.capacityHint = capacityHint; + this.boundaryObserver = new AtomicReference>(); + this.windows = new AtomicInteger(1); + this.queue = new MpscLinkedQueue(); + this.errors = new AtomicThrowable(); + this.stopWindows = new AtomicBoolean(); + this.other = other; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + queue.offer(NEXT_WINDOW); + drain(); + } + } + + @Override + public void onNext(T t) { + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable e) { + disposeBoundary(); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + disposeBoundary(); + done = true; + drain(); + } + + @Override + public void dispose() { + if (stopWindows.compareAndSet(false, true)) { + disposeBoundary(); + if (windows.decrementAndGet() == 0) { + upstream.dispose(); + } + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + void disposeBoundary() { + Disposable d = boundaryObserver.getAndSet((WindowBoundaryInnerObserver)BOUNDARY_DISPOSED); + if (d != null && d != BOUNDARY_DISPOSED) { + d.dispose(); + } + } + + @Override + public boolean isDisposed() { + return stopWindows.get(); + } + + @Override + public void run() { + if (windows.decrementAndGet() == 0) { + upstream.dispose(); + } + } + + void innerNext(WindowBoundaryInnerObserver sender) { + boundaryObserver.compareAndSet(sender, null); + queue.offer(NEXT_WINDOW); + drain(); + } + + void innerError(Throwable e) { + upstream.dispose(); + if (errors.addThrowable(e)) { + done = true; + drain(); + } else { + RxJavaPlugins.onError(e); + } + } + + void innerComplete() { + upstream.dispose(); + done = true; + drain(); + } + + @SuppressWarnings("unchecked") + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Observer> downstream = this.downstream; + MpscLinkedQueue queue = this.queue; + AtomicThrowable errors = this.errors; + + for (;;) { + + for (;;) { + if (windows.get() == 0) { + queue.clear(); + window = null; + return; + } + + UnicastSubject w = window; + + boolean d = done; + + if (d && errors.get() != null) { + queue.clear(); + Throwable ex = errors.terminate(); + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + return; + } + + Object v = queue.poll(); + + boolean empty = v == null; + + if (d && empty) { + Throwable ex = errors.terminate(); + if (ex == null) { + if (w != null) { + window = null; + w.onComplete(); + } + downstream.onComplete(); + } else { + if (w != null) { + window = null; + w.onError(ex); + } + downstream.onError(ex); + } + return; + } + + if (empty) { + break; + } + + if (v != NEXT_WINDOW) { + w.onNext((T)v); + continue; + } + + if (w != null) { + window = null; + w.onComplete(); + } + + if (!stopWindows.get()) { + w = UnicastSubject.create(capacityHint, this); + window = w; + windows.getAndIncrement(); + + ObservableSource otherSource; + + try { + otherSource = ObjectHelper.requireNonNull(other.call(), "The other Callable returned a null ObservableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + errors.addThrowable(ex); + done = true; + continue; + } + + WindowBoundaryInnerObserver bo = new WindowBoundaryInnerObserver(this); + + if (boundaryObserver.compareAndSet(null, bo)) { + otherSource.subscribe(bo); + + downstream.onNext(w); + } + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class WindowBoundaryInnerObserver extends DisposableObserver { + final WindowBoundaryMainObserver parent; + + boolean done; + + WindowBoundaryInnerObserver(WindowBoundaryMainObserver parent) { + this.parent = parent; + } + + @Override + public void onNext(B t) { + if (done) { + return; + } + done = true; + dispose(); + parent.innerNext(this); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + parent.innerError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.innerComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java new file mode 100755 index 0000000..7fddbaf --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java @@ -0,0 +1,731 @@ +/** + * 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.internal.operators.observable; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.Scheduler.Worker; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.observers.QueueDrainObserver; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.util.NotificationLite; +import io.reactivex.observers.SerializedObserver; +import io.reactivex.subjects.UnicastSubject; + +public final class ObservableWindowTimed extends AbstractObservableWithUpstream> { + final long timespan; + final long timeskip; + final TimeUnit unit; + final Scheduler scheduler; + final long maxSize; + final int bufferSize; + final boolean restartTimerOnMaxSize; + + public ObservableWindowTimed( + ObservableSource source, + long timespan, long timeskip, TimeUnit unit, Scheduler scheduler, long maxSize, + int bufferSize, boolean restartTimerOnMaxSize) { + super(source); + this.timespan = timespan; + this.timeskip = timeskip; + this.unit = unit; + this.scheduler = scheduler; + this.maxSize = maxSize; + this.bufferSize = bufferSize; + this.restartTimerOnMaxSize = restartTimerOnMaxSize; + } + + @Override + public void subscribeActual(Observer> t) { + SerializedObserver> actual = new SerializedObserver>(t); + + if (timespan == timeskip) { + if (maxSize == Long.MAX_VALUE) { + source.subscribe(new WindowExactUnboundedObserver( + actual, + timespan, unit, scheduler, bufferSize)); + return; + } + source.subscribe(new WindowExactBoundedObserver( + actual, + timespan, unit, scheduler, + bufferSize, maxSize, restartTimerOnMaxSize)); + return; + } + source.subscribe(new WindowSkipObserver(actual, + timespan, timeskip, unit, scheduler.createWorker(), bufferSize)); + } + + static final class WindowExactUnboundedObserver + extends QueueDrainObserver> + implements Observer, Disposable, Runnable { + final long timespan; + final TimeUnit unit; + final Scheduler scheduler; + final int bufferSize; + + Disposable upstream; + + UnicastSubject window; + + final SequentialDisposable timer = new SequentialDisposable(); + + static final Object NEXT = new Object(); + + volatile boolean terminated; + + WindowExactUnboundedObserver(Observer> actual, long timespan, TimeUnit unit, + Scheduler scheduler, int bufferSize) { + super(actual, new MpscLinkedQueue()); + this.timespan = timespan; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSize = bufferSize; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + window = UnicastSubject.create(bufferSize); + + Observer> a = downstream; + a.onSubscribe(this); + + a.onNext(window); + + if (!cancelled) { + Disposable task = scheduler.schedulePeriodicallyDirect(this, timespan, timespan, unit); + timer.replace(task); + } + } + } + + @Override + public void onNext(T t) { + if (terminated) { + return; + } + if (fastEnter()) { + window.onNext(t); + if (leave(-1) == 0) { + return; + } + } else { + queue.offer(NotificationLite.next(t)); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onComplete(); + } + + @Override + public void dispose() { + cancelled = true; + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public void run() { + if (cancelled) { + terminated = true; + } + queue.offer(NEXT); + if (enter()) { + drainLoop(); + } + } + + void drainLoop() { + + final MpscLinkedQueue q = (MpscLinkedQueue)queue; + final Observer> a = downstream; + UnicastSubject w = window; + + int missed = 1; + for (;;) { + + for (;;) { + boolean term = terminated; // NOPMD + + boolean d = done; + + Object o = q.poll(); + + if (d && (o == null || o == NEXT)) { + window = null; + q.clear(); + Throwable err = error; + if (err != null) { + w.onError(err); + } else { + w.onComplete(); + } + timer.dispose(); + return; + } + + if (o == null) { + break; + } + + if (o == NEXT) { + w.onComplete(); + if (!term) { + w = UnicastSubject.create(bufferSize); + window = w; + + a.onNext(w); + } else { + upstream.dispose(); + } + continue; + } + + w.onNext(NotificationLite.getValue(o)); + } + + missed = leave(-missed); + if (missed == 0) { + break; + } + } + } + } + + static final class WindowExactBoundedObserver + extends QueueDrainObserver> + implements Disposable { + final long timespan; + final TimeUnit unit; + final Scheduler scheduler; + final int bufferSize; + final boolean restartTimerOnMaxSize; + final long maxSize; + + final Worker worker; + + long count; + + long producerIndex; + + Disposable upstream; + + UnicastSubject window; + + volatile boolean terminated; + + final SequentialDisposable timer = new SequentialDisposable(); + + WindowExactBoundedObserver( + Observer> actual, + long timespan, TimeUnit unit, Scheduler scheduler, + int bufferSize, long maxSize, boolean restartTimerOnMaxSize) { + super(actual, new MpscLinkedQueue()); + this.timespan = timespan; + this.unit = unit; + this.scheduler = scheduler; + this.bufferSize = bufferSize; + this.maxSize = maxSize; + this.restartTimerOnMaxSize = restartTimerOnMaxSize; + if (restartTimerOnMaxSize) { + worker = scheduler.createWorker(); + } else { + worker = null; + } + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + Observer> a = downstream; + + a.onSubscribe(this); + + if (cancelled) { + return; + } + + UnicastSubject w = UnicastSubject.create(bufferSize); + window = w; + + a.onNext(w); + + Disposable task; + ConsumerIndexHolder consumerIndexHolder = new ConsumerIndexHolder(producerIndex, this); + if (restartTimerOnMaxSize) { + task = worker.schedulePeriodically(consumerIndexHolder, timespan, timespan, unit); + } else { + task = scheduler.schedulePeriodicallyDirect(consumerIndexHolder, timespan, timespan, unit); + } + + timer.replace(task); + } + } + + @Override + public void onNext(T t) { + if (terminated) { + return; + } + + if (fastEnter()) { + UnicastSubject w = window; + w.onNext(t); + + long c = count + 1; + + if (c >= maxSize) { + producerIndex++; + count = 0; + + w.onComplete(); + + w = UnicastSubject.create(bufferSize); + window = w; + downstream.onNext(w); + if (restartTimerOnMaxSize) { + Disposable tm = timer.get(); + tm.dispose(); + Disposable task = worker.schedulePeriodically( + new ConsumerIndexHolder(producerIndex, this), timespan, timespan, unit); + + DisposableHelper.replace(timer, task); + } + } else { + count = c; + } + + if (leave(-1) == 0) { + return; + } + } else { + queue.offer(NotificationLite.next(t)); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onComplete(); + } + + @Override + public void dispose() { + cancelled = true; + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void disposeTimer() { + DisposableHelper.dispose(timer); + Worker w = worker; + if (w != null) { + w.dispose(); + } + } + + void drainLoop() { + final MpscLinkedQueue q = (MpscLinkedQueue)queue; + final Observer> a = downstream; + UnicastSubject w = window; + + int missed = 1; + for (;;) { + + for (;;) { + if (terminated) { + upstream.dispose(); + q.clear(); + disposeTimer(); + return; + } + + boolean d = done; + + Object o = q.poll(); + + boolean empty = o == null; + boolean isHolder = o instanceof ConsumerIndexHolder; + + if (d && (empty || isHolder)) { + window = null; + q.clear(); + Throwable err = error; + if (err != null) { + w.onError(err); + } else { + w.onComplete(); + } + disposeTimer(); + return; + } + + if (empty) { + break; + } + + if (isHolder) { + ConsumerIndexHolder consumerIndexHolder = (ConsumerIndexHolder) o; + if (!restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { + w.onComplete(); + count = 0; + w = UnicastSubject.create(bufferSize); + window = w; + + a.onNext(w); + } + continue; + } + + w.onNext(NotificationLite.getValue(o)); + long c = count + 1; + + if (c >= maxSize) { + producerIndex++; + count = 0; + + w.onComplete(); + + w = UnicastSubject.create(bufferSize); + window = w; + downstream.onNext(w); + + if (restartTimerOnMaxSize) { + Disposable tm = timer.get(); + tm.dispose(); + + Disposable task = worker.schedulePeriodically( + new ConsumerIndexHolder(producerIndex, this), timespan, timespan, unit); + if (!timer.compareAndSet(tm, task)) { + task.dispose(); + } + } + + } else { + count = c; + } + } + + missed = leave(-missed); + if (missed == 0) { + break; + } + } + } + + static final class ConsumerIndexHolder implements Runnable { + final long index; + final WindowExactBoundedObserver parent; + ConsumerIndexHolder(long index, WindowExactBoundedObserver parent) { + this.index = index; + this.parent = parent; + } + + @Override + public void run() { + WindowExactBoundedObserver p = parent; + + if (!p.cancelled) { + p.queue.offer(this); + } else { + p.terminated = true; + } + if (p.enter()) { + p.drainLoop(); + } + } + } + } + + static final class WindowSkipObserver + extends QueueDrainObserver> + implements Disposable, Runnable { + final long timespan; + final long timeskip; + final TimeUnit unit; + final Worker worker; + final int bufferSize; + + final List> windows; + + Disposable upstream; + + volatile boolean terminated; + + WindowSkipObserver(Observer> actual, + long timespan, long timeskip, TimeUnit unit, + Worker worker, int bufferSize) { + super(actual, new MpscLinkedQueue()); + this.timespan = timespan; + this.timeskip = timeskip; + this.unit = unit; + this.worker = worker; + this.bufferSize = bufferSize; + this.windows = new LinkedList>(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + + if (cancelled) { + return; + } + + final UnicastSubject w = UnicastSubject.create(bufferSize); + windows.add(w); + + downstream.onNext(w); + worker.schedule(new CompletionTask(w), timespan, unit); + + worker.schedulePeriodically(this, timeskip, timeskip, unit); + } + + } + + @Override + public void onNext(T t) { + if (fastEnter()) { + for (UnicastSubject w : windows) { + w.onNext(t); + } + if (leave(-1) == 0) { + return; + } + } else { + queue.offer(t); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onError(t); + } + + @Override + public void onComplete() { + done = true; + if (enter()) { + drainLoop(); + } + + downstream.onComplete(); + } + + @Override + public void dispose() { + cancelled = true; + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void complete(UnicastSubject w) { + queue.offer(new SubjectWork(w, false)); + if (enter()) { + drainLoop(); + } + } + + @SuppressWarnings("unchecked") + void drainLoop() { + final MpscLinkedQueue q = (MpscLinkedQueue)queue; + final Observer> a = downstream; + final List> ws = windows; + + int missed = 1; + + for (;;) { + + for (;;) { + if (terminated) { + upstream.dispose(); + q.clear(); + ws.clear(); + worker.dispose(); + return; + } + + boolean d = done; + + Object v = q.poll(); + + boolean empty = v == null; + boolean sw = v instanceof SubjectWork; + + if (d && (empty || sw)) { + q.clear(); + Throwable e = error; + if (e != null) { + for (UnicastSubject w : ws) { + w.onError(e); + } + } else { + for (UnicastSubject w : ws) { + w.onComplete(); + } + } + ws.clear(); + worker.dispose(); + return; + } + + if (empty) { + break; + } + + if (sw) { + SubjectWork work = (SubjectWork)v; + + if (work.open) { + if (cancelled) { + continue; + } + + final UnicastSubject w = UnicastSubject.create(bufferSize); + ws.add(w); + a.onNext(w); + + worker.schedule(new CompletionTask(w), timespan, unit); + } else { + ws.remove(work.w); + work.w.onComplete(); + if (ws.isEmpty() && cancelled) { + terminated = true; + } + } + } else { + for (UnicastSubject w : ws) { + w.onNext((T)v); + } + } + } + + missed = leave(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void run() { + + UnicastSubject w = UnicastSubject.create(bufferSize); + + SubjectWork sw = new SubjectWork(w, true); + if (!cancelled) { + queue.offer(sw); + } + if (enter()) { + drainLoop(); + } + } + + static final class SubjectWork { + final UnicastSubject w; + final boolean open; + SubjectWork(UnicastSubject w, boolean open) { + this.w = w; + this.open = open; + } + } + + final class CompletionTask implements Runnable { + private final UnicastSubject w; + + CompletionTask(UnicastSubject w) { + this.w = w; + } + + @Override + public void run() { + complete(w); + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java new file mode 100755 index 0000000..83369a7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFrom.java @@ -0,0 +1,147 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.observers.SerializedObserver; + +public final class ObservableWithLatestFrom extends AbstractObservableWithUpstream { + final BiFunction combiner; + final ObservableSource other; + public ObservableWithLatestFrom(ObservableSource source, + BiFunction combiner, ObservableSource other) { + super(source); + this.combiner = combiner; + this.other = other; + } + + @Override + public void subscribeActual(Observer t) { + final SerializedObserver serial = new SerializedObserver(t); + final WithLatestFromObserver wlf = new WithLatestFromObserver(serial, combiner); + + serial.onSubscribe(wlf); + + other.subscribe(new WithLatestFromOtherObserver(wlf)); + + source.subscribe(wlf); + } + + static final class WithLatestFromObserver extends AtomicReference implements Observer, Disposable { + + private static final long serialVersionUID = -312246233408980075L; + + final Observer downstream; + + final BiFunction combiner; + + final AtomicReference upstream = new AtomicReference(); + + final AtomicReference other = new AtomicReference(); + + WithLatestFromObserver(Observer actual, BiFunction combiner) { + this.downstream = actual; + this.combiner = combiner; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); + } + + @Override + public void onNext(T t) { + U u = get(); + if (u != null) { + R r; + try { + r = ObjectHelper.requireNonNull(combiner.apply(t, u), "The combiner returned a null value"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + dispose(); + downstream.onError(e); + return; + } + downstream.onNext(r); + } + } + + @Override + public void onError(Throwable t) { + DisposableHelper.dispose(other); + downstream.onError(t); + } + + @Override + public void onComplete() { + DisposableHelper.dispose(other); + downstream.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + DisposableHelper.dispose(other); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } + + public boolean setOther(Disposable o) { + return DisposableHelper.setOnce(other, o); + } + + public void otherError(Throwable e) { + DisposableHelper.dispose(upstream); + downstream.onError(e); + } + } + + final class WithLatestFromOtherObserver implements Observer { + private final WithLatestFromObserver parent; + + WithLatestFromOtherObserver(WithLatestFromObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Disposable d) { + parent.setOther(d); + } + + @Override + public void onNext(U t) { + parent.lazySet(t); + } + + @Override + public void onError(Throwable t) { + parent.otherError(t); + } + + @Override + public void onComplete() { + // nothing to do, the wlf will complete on its own pace + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromMany.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromMany.java new file mode 100755 index 0000000..194d33c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWithLatestFromMany.java @@ -0,0 +1,292 @@ +/** + * 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.internal.operators.observable; + +import java.util.Arrays; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.annotations.NonNull; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Combines a main sequence of values with the latest from multiple other sequences via + * a selector function. + * + * @param the main sequence's type + * @param the output type + */ +public final class ObservableWithLatestFromMany extends AbstractObservableWithUpstream { + + @Nullable + final ObservableSource[] otherArray; + + @Nullable + final Iterable> otherIterable; + + @NonNull + final Function combiner; + + public ObservableWithLatestFromMany(@NonNull ObservableSource source, @NonNull ObservableSource[] otherArray, @NonNull Function combiner) { + super(source); + this.otherArray = otherArray; + this.otherIterable = null; + this.combiner = combiner; + } + + public ObservableWithLatestFromMany(@NonNull ObservableSource source, @NonNull Iterable> otherIterable, @NonNull Function combiner) { + super(source); + this.otherArray = null; + this.otherIterable = otherIterable; + this.combiner = combiner; + } + + @Override + protected void subscribeActual(Observer observer) { + ObservableSource[] others = otherArray; + int n = 0; + if (others == null) { + others = new ObservableSource[8]; + + try { + for (ObservableSource p : otherIterable) { + if (n == others.length) { + others = Arrays.copyOf(others, n + (n >> 1)); + } + others[n++] = p; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + } else { + n = others.length; + } + + if (n == 0) { + new ObservableMap(source, new SingletonArrayFunc()).subscribeActual(observer); + return; + } + + WithLatestFromObserver parent = new WithLatestFromObserver(observer, combiner, n); + observer.onSubscribe(parent); + parent.subscribe(others, n); + + source.subscribe(parent); + } + + static final class WithLatestFromObserver + extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = 1577321883966341961L; + + final Observer downstream; + + final Function combiner; + + final WithLatestInnerObserver[] observers; + + final AtomicReferenceArray values; + + final AtomicReference upstream; + + final AtomicThrowable error; + + volatile boolean done; + + WithLatestFromObserver(Observer actual, Function combiner, int n) { + this.downstream = actual; + this.combiner = combiner; + WithLatestInnerObserver[] s = new WithLatestInnerObserver[n]; + for (int i = 0; i < n; i++) { + s[i] = new WithLatestInnerObserver(this, i); + } + this.observers = s; + this.values = new AtomicReferenceArray(n); + this.upstream = new AtomicReference(); + this.error = new AtomicThrowable(); + } + + void subscribe(ObservableSource[] others, int n) { + WithLatestInnerObserver[] observers = this.observers; + AtomicReference upstream = this.upstream; + for (int i = 0; i < n; i++) { + if (DisposableHelper.isDisposed(upstream.get()) || done) { + return; + } + others[i].subscribe(observers[i]); + } + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + AtomicReferenceArray ara = values; + int n = ara.length(); + Object[] objects = new Object[n + 1]; + objects[0] = t; + + for (int i = 0; i < n; i++) { + Object o = ara.get(i); + if (o == null) { + // no latest, skip this value + return; + } + objects[i + 1] = o; + } + + R v; + + try { + v = ObjectHelper.requireNonNull(combiner.apply(objects), "combiner returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + dispose(); + onError(ex); + return; + } + + HalfSerializer.onNext(downstream, v, this, error); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + cancelAllBut(-1); + HalfSerializer.onError(downstream, t, this, error); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + cancelAllBut(-1); + HalfSerializer.onComplete(downstream, this, error); + } + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + for (WithLatestInnerObserver observer : observers) { + observer.dispose(); + } + } + + void innerNext(int index, Object o) { + values.set(index, o); + } + + void innerError(int index, Throwable t) { + done = true; + DisposableHelper.dispose(upstream); + cancelAllBut(index); + HalfSerializer.onError(downstream, t, this, error); + } + + void innerComplete(int index, boolean nonEmpty) { + if (!nonEmpty) { + done = true; + cancelAllBut(index); + HalfSerializer.onComplete(downstream, this, error); + } + } + + void cancelAllBut(int index) { + WithLatestInnerObserver[] observers = this.observers; + for (int i = 0; i < observers.length; i++) { + if (i != index) { + observers[i].dispose(); + } + } + } + } + + static final class WithLatestInnerObserver + extends AtomicReference + implements Observer { + + private static final long serialVersionUID = 3256684027868224024L; + + final WithLatestFromObserver parent; + + final int index; + + boolean hasValue; + + WithLatestInnerObserver(WithLatestFromObserver parent, int index) { + this.parent = parent; + this.index = index; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onNext(Object t) { + if (!hasValue) { + hasValue = true; + } + parent.innerNext(index, t); + } + + @Override + public void onError(Throwable t) { + parent.innerError(index, t); + } + + @Override + public void onComplete() { + parent.innerComplete(index, hasValue); + } + + public void dispose() { + DisposableHelper.dispose(this); + } + } + + final class SingletonArrayFunc implements Function { + @Override + public R apply(T t) throws Exception { + return ObjectHelper.requireNonNull(combiner.apply(new Object[] { t }), "The combiner returned a null value"); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java new file mode 100755 index 0000000..7f00383 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java @@ -0,0 +1,301 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.Arrays; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; + +public final class ObservableZip extends Observable { + + final ObservableSource[] sources; + final Iterable> sourcesIterable; + final Function zipper; + final int bufferSize; + final boolean delayError; + + public ObservableZip(ObservableSource[] sources, + Iterable> sourcesIterable, + Function zipper, + int bufferSize, + boolean delayError) { + this.sources = sources; + this.sourcesIterable = sourcesIterable; + this.zipper = zipper; + this.bufferSize = bufferSize; + this.delayError = delayError; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribeActual(Observer observer) { + ObservableSource[] sources = this.sources; + int count = 0; + if (sources == null) { + sources = new ObservableSource[8]; + for (ObservableSource p : sourcesIterable) { + if (count == sources.length) { + ObservableSource[] b = new ObservableSource[count + (count >> 2)]; + System.arraycopy(sources, 0, b, 0, count); + sources = b; + } + sources[count++] = p; + } + } else { + count = sources.length; + } + + if (count == 0) { + EmptyDisposable.complete(observer); + return; + } + + ZipCoordinator zc = new ZipCoordinator(observer, zipper, count, delayError); + zc.subscribe(sources, bufferSize); + } + + static final class ZipCoordinator extends AtomicInteger implements Disposable { + + private static final long serialVersionUID = 2983708048395377667L; + final Observer downstream; + final Function zipper; + final ZipObserver[] observers; + final T[] row; + final boolean delayError; + + volatile boolean cancelled; + + @SuppressWarnings("unchecked") + ZipCoordinator(Observer actual, + Function zipper, + int count, boolean delayError) { + this.downstream = actual; + this.zipper = zipper; + this.observers = new ZipObserver[count]; + this.row = (T[])new Object[count]; + this.delayError = delayError; + } + + public void subscribe(ObservableSource[] sources, int bufferSize) { + ZipObserver[] s = observers; + int len = s.length; + for (int i = 0; i < len; i++) { + s[i] = new ZipObserver(this, bufferSize); + } + // this makes sure the contents of the observers array is visible + this.lazySet(0); + downstream.onSubscribe(this); + for (int i = 0; i < len; i++) { + if (cancelled) { + return; + } + sources[i].subscribe(s[i]); + } + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + cancelSources(); + if (getAndIncrement() == 0) { + clear(); + } + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void cancel() { + clear(); + cancelSources(); + } + + void cancelSources() { + for (ZipObserver zs : observers) { + zs.dispose(); + } + } + + void clear() { + for (ZipObserver zs : observers) { + zs.queue.clear(); + } + } + + public void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missing = 1; + + final ZipObserver[] zs = observers; + final Observer a = downstream; + final T[] os = row; + final boolean delayError = this.delayError; + + for (;;) { + + for (;;) { + int i = 0; + int emptyCount = 0; + for (ZipObserver z : zs) { + if (os[i] == null) { + boolean d = z.done; + T v = z.queue.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, delayError, z)) { + return; + } + if (!empty) { + os[i] = v; + } else { + emptyCount++; + } + } else { + if (z.done && !delayError) { + Throwable ex = z.error; + if (ex != null) { + cancelled = true; + cancel(); + a.onError(ex); + return; + } + } + } + i++; + } + + if (emptyCount != 0) { + break; + } + + R v; + try { + v = ObjectHelper.requireNonNull(zipper.apply(os.clone()), "The zipper returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(); + a.onError(ex); + return; + } + + a.onNext(v); + + Arrays.fill(os, null); + } + + missing = addAndGet(-missing); + if (missing == 0) { + return; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Observer a, boolean delayError, ZipObserver source) { + if (cancelled) { + cancel(); + return true; + } + + if (d) { + if (delayError) { + if (empty) { + Throwable e = source.error; + cancelled = true; + cancel(); + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + return true; + } + } else { + Throwable e = source.error; + if (e != null) { + cancelled = true; + cancel(); + a.onError(e); + return true; + } else + if (empty) { + cancelled = true; + cancel(); + a.onComplete(); + return true; + } + } + } + + return false; + } + } + + static final class ZipObserver implements Observer { + + final ZipCoordinator parent; + final SpscLinkedArrayQueue queue; + + volatile boolean done; + Throwable error; + + final AtomicReference upstream = new AtomicReference(); + + ZipObserver(ZipCoordinator parent, int bufferSize) { + this.parent = parent; + this.queue = new SpscLinkedArrayQueue(bufferSize); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this.upstream, d); + } + + @Override + public void onNext(T t) { + queue.offer(t); + parent.drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + parent.drain(); + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + + public void dispose() { + DisposableHelper.dispose(upstream); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java new file mode 100755 index 0000000..22b7b95 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableZipIterable.java @@ -0,0 +1,173 @@ +/** + * 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.internal.operators.observable; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.Iterator; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ObservableZipIterable extends Observable { + final Observable source; + final Iterable other; + final BiFunction zipper; + + public ObservableZipIterable( + Observable source, + Iterable other, BiFunction zipper) { + this.source = source; + this.other = other; + this.zipper = zipper; + } + + @Override + public void subscribeActual(Observer t) { + Iterator it; + + try { + it = ObjectHelper.requireNonNull(other.iterator(), "The iterator returned by other is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, t); + return; + } + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, t); + return; + } + + if (!b) { + EmptyDisposable.complete(t); + return; + } + + source.subscribe(new ZipIterableObserver(t, it, zipper)); + } + + static final class ZipIterableObserver implements Observer, Disposable { + final Observer downstream; + final Iterator iterator; + final BiFunction zipper; + + Disposable upstream; + + boolean done; + + ZipIterableObserver(Observer actual, Iterator iterator, + BiFunction zipper) { + this.downstream = actual; + this.iterator = iterator; + this.zipper = zipper; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + U u; + + try { + u = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null value"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + error(e); + return; + } + + V v; + try { + v = ObjectHelper.requireNonNull(zipper.apply(t, u), "The zipper function returned a null value"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + error(e); + return; + } + + downstream.onNext(v); + + boolean b; + + try { + b = iterator.hasNext(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + error(e); + return; + } + + if (!b) { + done = true; + upstream.dispose(); + downstream.onComplete(); + } + } + + void error(Throwable e) { + done = true; + upstream.dispose(); + downstream.onError(e); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObserverResourceWrapper.java b/src/main/java/io/reactivex/internal/operators/observable/ObserverResourceWrapper.java new file mode 100755 index 0000000..8003492 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObserverResourceWrapper.java @@ -0,0 +1,73 @@ +/** + * 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.internal.operators.observable; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; + +public final class ObserverResourceWrapper extends AtomicReference implements Observer, Disposable { + + private static final long serialVersionUID = -8612022020200669122L; + + final Observer downstream; + + final AtomicReference upstream = new AtomicReference(); + + public ObserverResourceWrapper(Observer downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(upstream, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + dispose(); + downstream.onError(t); + } + + @Override + public void onComplete() { + dispose(); + downstream.onComplete(); + } + + @Override + public void dispose() { + DisposableHelper.dispose(upstream); + + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return upstream.get() == DisposableHelper.DISPOSED; + } + + public void setResource(Disposable resource) { + DisposableHelper.set(this, resource); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java new file mode 100755 index 0000000..b1052b2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelCollect.java @@ -0,0 +1,159 @@ +/** + * 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.internal.operators.parallel; + +import java.util.concurrent.Callable; + +import org.reactivestreams.*; + +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiConsumer; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscribers.DeferredScalarSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Reduce the sequence of values in each 'rail' to a single value. + * + * @param the input value type + * @param the collection type + */ +public final class ParallelCollect extends ParallelFlowable { + + final ParallelFlowable source; + + final Callable initialCollection; + + final BiConsumer collector; + + public ParallelCollect(ParallelFlowable source, + Callable initialCollection, BiConsumer collector) { + this.source = source; + this.initialCollection = initialCollection; + this.collector = collector; + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") + Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + + C initialValue; + + try { + initialValue = ObjectHelper.requireNonNull(initialCollection.call(), "The initialSupplier returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + reportError(subscribers, ex); + return; + } + + parents[i] = new ParallelCollectSubscriber(subscribers[i], initialValue, collector); + } + + source.subscribe(parents); + } + + void reportError(Subscriber[] subscribers, Throwable ex) { + for (Subscriber s : subscribers) { + EmptySubscription.error(ex, s); + } + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + static final class ParallelCollectSubscriber extends DeferredScalarSubscriber { + + private static final long serialVersionUID = -4767392946044436228L; + + final BiConsumer collector; + + C collection; + + boolean done; + + ParallelCollectSubscriber(Subscriber subscriber, + C initialValue, BiConsumer collector) { + super(subscriber); + this.collection = initialValue; + this.collector = collector; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + try { + collector.accept(collection, t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(); + onError(ex); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + collection = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + C c = collection; + collection = null; + complete(c); + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelConcatMap.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelConcatMap.java new file mode 100755 index 0000000..7b75bfd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelConcatMap.java @@ -0,0 +1,72 @@ +/** + * 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.internal.operators.parallel; + +import org.reactivestreams.*; + +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.flowable.FlowableConcatMap; +import io.reactivex.internal.util.ErrorMode; +import io.reactivex.parallel.ParallelFlowable; + +/** + * Concatenates the generated Publishers on each rail. + * + * @param the input value type + * @param the output value type + */ +public final class ParallelConcatMap extends ParallelFlowable { + + final ParallelFlowable source; + + final Function> mapper; + + final int prefetch; + + final ErrorMode errorMode; + + public ParallelConcatMap( + ParallelFlowable source, + Function> mapper, + int prefetch, ErrorMode errorMode) { + this.source = source; + this.mapper = ObjectHelper.requireNonNull(mapper, "mapper"); + this.prefetch = prefetch; + this.errorMode = ObjectHelper.requireNonNull(errorMode, "errorMode"); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + @SuppressWarnings("unchecked") + final Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + parents[i] = FlowableConcatMap.subscribe(subscribers[i], mapper, prefetch, errorMode); + } + + source.subscribe(parents); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelDoOnNextTry.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelDoOnNextTry.java new file mode 100755 index 0000000..23fae57 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelDoOnNextTry.java @@ -0,0 +1,295 @@ +/** + * 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.internal.operators.parallel; + +import org.reactivestreams.*; + +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.parallel.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Calls a Consumer for each upstream value passing by + * and handles any failure with a handler function. + *

History: 2.0.8 - experimental + * @param the input value type + * @since 2.2 + */ +public final class ParallelDoOnNextTry extends ParallelFlowable { + + final ParallelFlowable source; + + final Consumer onNext; + + final BiFunction errorHandler; + + public ParallelDoOnNextTry(ParallelFlowable source, Consumer onNext, + BiFunction errorHandler) { + this.source = source; + this.onNext = onNext; + this.errorHandler = errorHandler; + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") + Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + Subscriber a = subscribers[i]; + if (a instanceof ConditionalSubscriber) { + parents[i] = new ParallelDoOnNextConditionalSubscriber((ConditionalSubscriber)a, onNext, errorHandler); + } else { + parents[i] = new ParallelDoOnNextSubscriber(a, onNext, errorHandler); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + static final class ParallelDoOnNextSubscriber implements ConditionalSubscriber, Subscription { + + final Subscriber downstream; + + final Consumer onNext; + + final BiFunction errorHandler; + + Subscription upstream; + + boolean done; + + ParallelDoOnNextSubscriber(Subscriber actual, Consumer onNext, + BiFunction errorHandler) { + this.downstream = actual; + this.onNext = onNext; + this.errorHandler = errorHandler; + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t)) { + upstream.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + long retries = 0; + + for (;;) { + try { + onNext.accept(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + ParallelFailureHandling h; + + try { + h = ObjectHelper.requireNonNull(errorHandler.apply(++retries, ex), "The errorHandler returned a null item"); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancel(); + onError(new CompositeException(ex, exc)); + return false; + } + + switch (h) { + case RETRY: + continue; + case SKIP: + return false; + case STOP: + cancel(); + onComplete(); + return false; + default: + cancel(); + onError(ex); + return false; + } + } + + downstream.onNext(t); + return true; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + } + static final class ParallelDoOnNextConditionalSubscriber implements ConditionalSubscriber, Subscription { + + final ConditionalSubscriber downstream; + + final Consumer onNext; + + final BiFunction errorHandler; + + Subscription upstream; + + boolean done; + + ParallelDoOnNextConditionalSubscriber(ConditionalSubscriber actual, + Consumer onNext, + BiFunction errorHandler) { + this.downstream = actual; + this.onNext = onNext; + this.errorHandler = errorHandler; + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t) && !done) { + upstream.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + long retries = 0; + + for (;;) { + try { + onNext.accept(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + ParallelFailureHandling h; + + try { + h = ObjectHelper.requireNonNull(errorHandler.apply(++retries, ex), "The errorHandler returned a null item"); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancel(); + onError(new CompositeException(ex, exc)); + return false; + } + + switch (h) { + case RETRY: + continue; + case SKIP: + return false; + case STOP: + cancel(); + onComplete(); + return false; + default: + cancel(); + onError(ex); + return false; + } + } + + return downstream.tryOnNext(t); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilter.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilter.java new file mode 100755 index 0000000..1a775d5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilter.java @@ -0,0 +1,212 @@ +/** + * 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.internal.operators.parallel; + +import org.reactivestreams.*; + +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Filters each 'rail' of the source ParallelFlowable with a predicate function. + * + * @param the input value type + */ +public final class ParallelFilter extends ParallelFlowable { + + final ParallelFlowable source; + + final Predicate predicate; + + public ParallelFilter(ParallelFlowable source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") + Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + Subscriber a = subscribers[i]; + if (a instanceof ConditionalSubscriber) { + parents[i] = new ParallelFilterConditionalSubscriber((ConditionalSubscriber)a, predicate); + } else { + parents[i] = new ParallelFilterSubscriber(a, predicate); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + abstract static class BaseFilterSubscriber implements ConditionalSubscriber, Subscription { + final Predicate predicate; + + Subscription upstream; + + boolean done; + + BaseFilterSubscriber(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public final void request(long n) { + upstream.request(n); + } + + @Override + public final void cancel() { + upstream.cancel(); + } + + @Override + public final void onNext(T t) { + if (!tryOnNext(t) && !done) { + upstream.request(1); + } + } + } + + static final class ParallelFilterSubscriber extends BaseFilterSubscriber { + + final Subscriber downstream; + + ParallelFilterSubscriber(Subscriber actual, Predicate predicate) { + super(predicate); + this.downstream = actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public boolean tryOnNext(T t) { + if (!done) { + boolean b; + + try { + b = predicate.test(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(); + onError(ex); + return false; + } + + if (b) { + downstream.onNext(t); + return true; + } + } + return false; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + } + } + } + + static final class ParallelFilterConditionalSubscriber extends BaseFilterSubscriber { + + final ConditionalSubscriber downstream; + + ParallelFilterConditionalSubscriber(ConditionalSubscriber actual, Predicate predicate) { + super(predicate); + this.downstream = actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public boolean tryOnNext(T t) { + if (!done) { + boolean b; + + try { + b = predicate.test(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(); + onError(ex); + return false; + } + + if (b) { + return downstream.tryOnNext(t); + } + } + return false; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + } + } + }} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilterTry.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilterTry.java new file mode 100755 index 0000000..bd0923f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFilterTry.java @@ -0,0 +1,275 @@ +/** + * 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.internal.operators.parallel; + +import org.reactivestreams.*; + +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.parallel.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Filters each 'rail' of the source ParallelFlowable with a predicate function. + * + * @param the input value type + */ +public final class ParallelFilterTry extends ParallelFlowable { + + final ParallelFlowable source; + + final Predicate predicate; + + final BiFunction errorHandler; + + public ParallelFilterTry(ParallelFlowable source, Predicate predicate, + BiFunction errorHandler) { + this.source = source; + this.predicate = predicate; + this.errorHandler = errorHandler; + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") + Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + Subscriber a = subscribers[i]; + if (a instanceof ConditionalSubscriber) { + parents[i] = new ParallelFilterConditionalSubscriber((ConditionalSubscriber)a, predicate, errorHandler); + } else { + parents[i] = new ParallelFilterSubscriber(a, predicate, errorHandler); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + abstract static class BaseFilterSubscriber implements ConditionalSubscriber, Subscription { + final Predicate predicate; + + final BiFunction errorHandler; + + Subscription upstream; + + boolean done; + + BaseFilterSubscriber(Predicate predicate, BiFunction errorHandler) { + this.predicate = predicate; + this.errorHandler = errorHandler; + } + + @Override + public final void request(long n) { + upstream.request(n); + } + + @Override + public final void cancel() { + upstream.cancel(); + } + + @Override + public final void onNext(T t) { + if (!tryOnNext(t) && !done) { + upstream.request(1); + } + } + } + + static final class ParallelFilterSubscriber extends BaseFilterSubscriber { + + final Subscriber downstream; + + ParallelFilterSubscriber(Subscriber actual, Predicate predicate, BiFunction errorHandler) { + super(predicate, errorHandler); + this.downstream = actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public boolean tryOnNext(T t) { + if (!done) { + long retries = 0L; + + for (;;) { + boolean b; + + try { + b = predicate.test(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + ParallelFailureHandling h; + + try { + h = ObjectHelper.requireNonNull(errorHandler.apply(++retries, ex), "The errorHandler returned a null item"); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancel(); + onError(new CompositeException(ex, exc)); + return false; + } + + switch (h) { + case RETRY: + continue; + case SKIP: + return false; + case STOP: + cancel(); + onComplete(); + return false; + default: + cancel(); + onError(ex); + return false; + } + } + + if (b) { + downstream.onNext(t); + return true; + } + return false; + } + } + return false; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + } + } + } + + static final class ParallelFilterConditionalSubscriber extends BaseFilterSubscriber { + + final ConditionalSubscriber downstream; + + ParallelFilterConditionalSubscriber(ConditionalSubscriber actual, + Predicate predicate, + BiFunction errorHandler) { + super(predicate, errorHandler); + this.downstream = actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public boolean tryOnNext(T t) { + if (!done) { + long retries = 0L; + + for (;;) { + boolean b; + + try { + b = predicate.test(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + ParallelFailureHandling h; + + try { + h = ObjectHelper.requireNonNull(errorHandler.apply(++retries, ex), "The errorHandler returned a null item"); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancel(); + onError(new CompositeException(ex, exc)); + return false; + } + + switch (h) { + case RETRY: + continue; + case SKIP: + return false; + case STOP: + cancel(); + onComplete(); + return false; + default: + cancel(); + onError(ex); + return false; + } + } + + return b && downstream.tryOnNext(t); + } + } + return false; + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + } + } + }} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFlatMap.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFlatMap.java new file mode 100755 index 0000000..0c2e47a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFlatMap.java @@ -0,0 +1,75 @@ +/** + * 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.internal.operators.parallel; + +import org.reactivestreams.*; + +import io.reactivex.functions.Function; +import io.reactivex.internal.operators.flowable.FlowableFlatMap; +import io.reactivex.parallel.ParallelFlowable; + +/** + * Flattens the generated Publishers on each rail. + * + * @param the input value type + * @param the output value type + */ +public final class ParallelFlatMap extends ParallelFlowable { + + final ParallelFlowable source; + + final Function> mapper; + + final boolean delayError; + + final int maxConcurrency; + + final int prefetch; + + public ParallelFlatMap( + ParallelFlowable source, + Function> mapper, + boolean delayError, + int maxConcurrency, + int prefetch) { + this.source = source; + this.mapper = mapper; + this.delayError = delayError; + this.maxConcurrency = maxConcurrency; + this.prefetch = prefetch; + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + @SuppressWarnings("unchecked") + final Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + parents[i] = FlowableFlatMap.subscribe(subscribers[i], mapper, delayError, maxConcurrency, prefetch); + } + + source.subscribe(parents); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromArray.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromArray.java new file mode 100755 index 0000000..9831380 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromArray.java @@ -0,0 +1,50 @@ +/** + * 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.internal.operators.parallel; + +import org.reactivestreams.*; + +import io.reactivex.parallel.ParallelFlowable; + +/** + * Wraps multiple Publishers into a ParallelFlowable which runs them + * in parallel. + * + * @param the value type + */ +public final class ParallelFromArray extends ParallelFlowable { + final Publisher[] sources; + + public ParallelFromArray(Publisher[] sources) { + this.sources = sources; + } + + @Override + public int parallelism() { + return sources.length; + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + for (int i = 0; i < n; i++) { + sources[i].subscribe(subscribers[i]); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java new file mode 100755 index 0000000..33d83ca --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelFromPublisher.java @@ -0,0 +1,440 @@ +/** + * 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.internal.operators.parallel; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.exceptions.*; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.parallel.ParallelFlowable; + +/** + * Dispatches the values from upstream in a round robin fashion to subscribers which are + * ready to consume elements. A value from upstream is sent to only one of the subscribers. + * + * @param the value type + */ +public final class ParallelFromPublisher extends ParallelFlowable { + final Publisher source; + + final int parallelism; + + final int prefetch; + + public ParallelFromPublisher(Publisher source, int parallelism, int prefetch) { + this.source = source; + this.parallelism = parallelism; + this.prefetch = prefetch; + } + + @Override + public int parallelism() { + return parallelism; + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + source.subscribe(new ParallelDispatcher(subscribers, prefetch)); + } + + static final class ParallelDispatcher + extends AtomicInteger + implements FlowableSubscriber { + + private static final long serialVersionUID = -4470634016609963609L; + + final Subscriber[] subscribers; + + final AtomicLongArray requests; + + final long[] emissions; + + final int prefetch; + + final int limit; + + Subscription upstream; + + SimpleQueue queue; + + Throwable error; + + volatile boolean done; + + int index; + + volatile boolean cancelled; + + /** + * Counts how many subscribers were setup to delay triggering the + * drain of upstream until all of them have been setup. + */ + final AtomicInteger subscriberCount = new AtomicInteger(); + + int produced; + + int sourceMode; + + ParallelDispatcher(Subscriber[] subscribers, int prefetch) { + this.subscribers = subscribers; + this.prefetch = prefetch; + this.limit = prefetch - (prefetch >> 2); + int m = subscribers.length; + this.requests = new AtomicLongArray(m + m + 1); + this.requests.lazySet(m + m, m); + this.emissions = new long[m]; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); + + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = qs; + done = true; + setupSubscribers(); + drain(); + return; + } else + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = qs; + + setupSubscribers(); + + s.request(prefetch); + + return; + } + } + + queue = new SpscArrayQueue(prefetch); + + setupSubscribers(); + + s.request(prefetch); + } + } + + void setupSubscribers() { + Subscriber[] subs = subscribers; + final int m = subs.length; + + for (int i = 0; i < m; i++) { + if (cancelled) { + return; + } + + subscriberCount.lazySet(i + 1); + + subs[i].onSubscribe(new RailSubscription(i, m)); + } + } + + final class RailSubscription implements Subscription { + + final int j; + + final int m; + + RailSubscription(int j, int m) { + this.j = j; + this.m = m; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + AtomicLongArray ra = requests; + for (;;) { + long r = ra.get(j); + if (r == Long.MAX_VALUE) { + return; + } + long u = BackpressureHelper.addCap(r, n); + if (ra.compareAndSet(j, r, u)) { + break; + } + } + if (subscriberCount.get() == m) { + drain(); + } + } + } + + @Override + public void cancel() { + if (requests.compareAndSet(m + j, 0L, 1L)) { + ParallelDispatcher.this.cancel(m + m); + } + } + } + + @Override + public void onNext(T t) { + if (sourceMode == QueueSubscription.NONE) { + if (!queue.offer(t)) { + upstream.cancel(); + onError(new MissingBackpressureException("Queue is full?")); + return; + } + } + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + void cancel(int m) { + if (requests.decrementAndGet(m) == 0L) { + cancelled = true; + this.upstream.cancel(); + + if (getAndIncrement() == 0) { + queue.clear(); + } + } + } + + void drainAsync() { + int missed = 1; + + SimpleQueue q = queue; + Subscriber[] a = this.subscribers; + AtomicLongArray r = this.requests; + long[] e = this.emissions; + int n = e.length; + int idx = index; + int consumed = produced; + + for (;;) { + + int notReady = 0; + + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + if (d) { + Throwable ex = error; + if (ex != null) { + q.clear(); + for (Subscriber s : a) { + s.onError(ex); + } + return; + } + } + + boolean empty = q.isEmpty(); + + if (d && empty) { + for (Subscriber s : a) { + s.onComplete(); + } + return; + } + + if (empty) { + break; + } + + long requestAtIndex = r.get(idx); + long emissionAtIndex = e[idx]; + if (requestAtIndex != emissionAtIndex && r.get(n + idx) == 0) { + + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + for (Subscriber s : a) { + s.onError(ex); + } + return; + } + + if (v == null) { + break; + } + + a[idx].onNext(v); + + e[idx] = emissionAtIndex + 1; + + int c = ++consumed; + if (c == limit) { + consumed = 0; + upstream.request(c); + } + notReady = 0; + } else { + notReady++; + } + + idx++; + if (idx == n) { + idx = 0; + } + + if (notReady == n) { + break; + } + } + + int w = get(); + if (w == missed) { + index = idx; + produced = consumed; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + + void drainSync() { + int missed = 1; + + SimpleQueue q = queue; + Subscriber[] a = this.subscribers; + AtomicLongArray r = this.requests; + long[] e = this.emissions; + int n = e.length; + int idx = index; + + for (;;) { + + int notReady = 0; + + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + boolean empty = q.isEmpty(); + + if (empty) { + for (Subscriber s : a) { + s.onComplete(); + } + return; + } + + long requestAtIndex = r.get(idx); + long emissionAtIndex = e[idx]; + if (requestAtIndex != emissionAtIndex && r.get(n + idx) == 0) { + + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.cancel(); + for (Subscriber s : a) { + s.onError(ex); + } + return; + } + + if (v == null) { + for (Subscriber s : a) { + s.onComplete(); + } + return; + } + + a[idx].onNext(v); + + e[idx] = emissionAtIndex + 1; + + notReady = 0; + } else { + notReady++; + } + + idx++; + if (idx == n) { + idx = 0; + } + + if (notReady == n) { + break; + } + } + + int w = get(); + if (w == missed) { + index = idx; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + if (sourceMode == QueueSubscription.SYNC) { + drainSync(); + } else { + drainAsync(); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java new file mode 100755 index 0000000..975c151 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelJoin.java @@ -0,0 +1,568 @@ +/** + * 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.internal.operators.parallel; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Merges the individual 'rails' of the source ParallelFlowable, unordered, + * into a single regular Publisher sequence (exposed as Flowable). + * + * @param the value type + */ +public final class ParallelJoin extends Flowable { + + final ParallelFlowable source; + + final int prefetch; + + final boolean delayErrors; + + public ParallelJoin(ParallelFlowable source, int prefetch, boolean delayErrors) { + this.source = source; + this.prefetch = prefetch; + this.delayErrors = delayErrors; + } + + @Override + protected void subscribeActual(Subscriber s) { + JoinSubscriptionBase parent; + if (delayErrors) { + parent = new JoinSubscriptionDelayError(s, source.parallelism(), prefetch); + } else { + parent = new JoinSubscription(s, source.parallelism(), prefetch); + } + s.onSubscribe(parent); + source.subscribe(parent.subscribers); + } + + abstract static class JoinSubscriptionBase extends AtomicInteger + implements Subscription { + + private static final long serialVersionUID = 3100232009247827843L; + + final Subscriber downstream; + + final JoinInnerSubscriber[] subscribers; + + final AtomicThrowable errors = new AtomicThrowable(); + + final AtomicLong requested = new AtomicLong(); + + volatile boolean cancelled; + + final AtomicInteger done = new AtomicInteger(); + + JoinSubscriptionBase(Subscriber actual, int n, int prefetch) { + this.downstream = actual; + @SuppressWarnings("unchecked") + JoinInnerSubscriber[] a = new JoinInnerSubscriber[n]; + + for (int i = 0; i < n; i++) { + a[i] = new JoinInnerSubscriber(this, prefetch); + } + + this.subscribers = a; + done.lazySet(n); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + cancelAll(); + + if (getAndIncrement() == 0) { + cleanup(); + } + } + } + + void cancelAll() { + for (JoinInnerSubscriber s : subscribers) { + s.cancel(); + } + } + + void cleanup() { + for (JoinInnerSubscriber s : subscribers) { + s.queue = null; + } + } + + abstract void onNext(JoinInnerSubscriber inner, T value); + + abstract void onError(Throwable e); + + abstract void onComplete(); + + abstract void drain(); + } + + static final class JoinSubscription extends JoinSubscriptionBase { + + private static final long serialVersionUID = 6312374661811000451L; + + JoinSubscription(Subscriber actual, int n, int prefetch) { + super(actual, n, prefetch); + } + + @Override + public void onNext(JoinInnerSubscriber inner, T value) { + if (get() == 0 && compareAndSet(0, 1)) { + if (requested.get() != 0) { + downstream.onNext(value); + if (requested.get() != Long.MAX_VALUE) { + requested.decrementAndGet(); + } + inner.request(1); + } else { + SimplePlainQueue q = inner.getQueue(); + + if (!q.offer(value)) { + cancelAll(); + Throwable mbe = new MissingBackpressureException("Queue full?!"); + if (errors.compareAndSet(null, mbe)) { + downstream.onError(mbe); + } else { + RxJavaPlugins.onError(mbe); + } + return; + } + } + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue q = inner.getQueue(); + + if (!q.offer(value)) { + cancelAll(); + onError(new MissingBackpressureException("Queue full?!")); + return; + } + + if (getAndIncrement() != 0) { + return; + } + } + + drainLoop(); + } + + @Override + public void onError(Throwable e) { + if (errors.compareAndSet(null, e)) { + cancelAll(); + drain(); + } else { + if (e != errors.get()) { + RxJavaPlugins.onError(e); + } + } + } + + @Override + public void onComplete() { + done.decrementAndGet(); + drain(); + } + + @Override + void drain() { + if (getAndIncrement() != 0) { + return; + } + + drainLoop(); + } + + void drainLoop() { + int missed = 1; + + JoinInnerSubscriber[] s = this.subscribers; + int n = s.length; + Subscriber a = this.downstream; + + for (;;) { + + long r = requested.get(); + long e = 0; + + middle: + while (e != r) { + if (cancelled) { + cleanup(); + return; + } + + Throwable ex = errors.get(); + if (ex != null) { + cleanup(); + a.onError(ex); + return; + } + + boolean d = done.get() == 0; + + boolean empty = true; + + for (int i = 0; i < s.length; i++) { + JoinInnerSubscriber inner = s[i]; + SimplePlainQueue q = inner.queue; + if (q != null) { + T v = q.poll(); + + if (v != null) { + empty = false; + a.onNext(v); + inner.requestOne(); + if (++e == r) { + break middle; + } + } + } + } + + if (d && empty) { + a.onComplete(); + return; + } + + if (empty) { + break; + } + } + + if (e == r) { + if (cancelled) { + cleanup(); + return; + } + + Throwable ex = errors.get(); + if (ex != null) { + cleanup(); + a.onError(ex); + return; + } + + boolean d = done.get() == 0; + + boolean empty = true; + + for (int i = 0; i < n; i++) { + JoinInnerSubscriber inner = s[i]; + + SimpleQueue q = inner.queue; + if (q != null && !q.isEmpty()) { + empty = false; + break; + } + } + + if (d && empty) { + a.onComplete(); + return; + } + } + + if (e != 0 && r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + + int w = get(); + if (w == missed) { + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + } + + static final class JoinSubscriptionDelayError extends JoinSubscriptionBase { + + private static final long serialVersionUID = -5737965195918321883L; + + JoinSubscriptionDelayError(Subscriber actual, int n, int prefetch) { + super(actual, n, prefetch); + } + + @Override + void onNext(JoinInnerSubscriber inner, T value) { + if (get() == 0 && compareAndSet(0, 1)) { + if (requested.get() != 0) { + downstream.onNext(value); + if (requested.get() != Long.MAX_VALUE) { + requested.decrementAndGet(); + } + inner.request(1); + } else { + SimplePlainQueue q = inner.getQueue(); + + if (!q.offer(value)) { + inner.cancel(); + errors.addThrowable(new MissingBackpressureException("Queue full?!")); + done.decrementAndGet(); + drainLoop(); + return; + } + } + if (decrementAndGet() == 0) { + return; + } + } else { + SimplePlainQueue q = inner.getQueue(); + + if (!q.offer(value)) { + if (inner.cancel()) { + errors.addThrowable(new MissingBackpressureException("Queue full?!")); + done.decrementAndGet(); + } + } + + if (getAndIncrement() != 0) { + return; + } + } + + drainLoop(); + } + + @Override + void onError(Throwable e) { + errors.addThrowable(e); + done.decrementAndGet(); + drain(); + } + + @Override + void onComplete() { + done.decrementAndGet(); + drain(); + } + + @Override + void drain() { + if (getAndIncrement() != 0) { + return; + } + + drainLoop(); + } + + void drainLoop() { + int missed = 1; + + JoinInnerSubscriber[] s = this.subscribers; + int n = s.length; + Subscriber a = this.downstream; + + for (;;) { + + long r = requested.get(); + long e = 0; + + middle: + while (e != r) { + if (cancelled) { + cleanup(); + return; + } + + boolean d = done.get() == 0; + + boolean empty = true; + + for (int i = 0; i < n; i++) { + JoinInnerSubscriber inner = s[i]; + + SimplePlainQueue q = inner.queue; + if (q != null) { + T v = q.poll(); + + if (v != null) { + empty = false; + a.onNext(v); + inner.requestOne(); + if (++e == r) { + break middle; + } + } + } + } + + if (d && empty) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + } else { + a.onComplete(); + } + return; + } + + if (empty) { + break; + } + } + + if (e == r) { + if (cancelled) { + cleanup(); + return; + } + + boolean d = done.get() == 0; + + boolean empty = true; + + for (int i = 0; i < n; i++) { + JoinInnerSubscriber inner = s[i]; + + SimpleQueue q = inner.queue; + if (q != null && !q.isEmpty()) { + empty = false; + break; + } + } + + if (d && empty) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(errors.terminate()); + } else { + a.onComplete(); + } + return; + } + } + + if (e != 0 && r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + + int w = get(); + if (w == missed) { + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + } + + static final class JoinInnerSubscriber + extends AtomicReference + implements FlowableSubscriber { + + private static final long serialVersionUID = 8410034718427740355L; + + final JoinSubscriptionBase parent; + + final int prefetch; + + final int limit; + + long produced; + + volatile SimplePlainQueue queue; + + JoinInnerSubscriber(JoinSubscriptionBase parent, int prefetch) { + this.parent = parent; + this.prefetch = prefetch ; + this.limit = prefetch - (prefetch >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, prefetch); + } + + @Override + public void onNext(T t) { + parent.onNext(this, t); + } + + @Override + public void onError(Throwable t) { + parent.onError(t); + } + + @Override + public void onComplete() { + parent.onComplete(); + } + + public void requestOne() { + long p = produced + 1; + if (p == limit) { + produced = 0; + get().request(p); + } else { + produced = p; + } + } + + public void request(long n) { + long p = produced + n; + if (p >= limit) { + produced = 0; + get().request(p); + } else { + produced = p; + } + } + + public boolean cancel() { + return SubscriptionHelper.cancel(this); + } + + SimplePlainQueue getQueue() { + SimplePlainQueue q = queue; + if (q == null) { + q = new SpscArrayQueue(prefetch); + this.queue = q; + } + return q; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelMap.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelMap.java new file mode 100755 index 0000000..18a627d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelMap.java @@ -0,0 +1,236 @@ +/** + * 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.internal.operators.parallel; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each 'rail' of the source ParallelFlowable with a mapper function. + * + * @param the input value type + * @param the output value type + */ +public final class ParallelMap extends ParallelFlowable { + + final ParallelFlowable source; + + final Function mapper; + + public ParallelMap(ParallelFlowable source, Function mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") + Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + Subscriber a = subscribers[i]; + if (a instanceof ConditionalSubscriber) { + parents[i] = new ParallelMapConditionalSubscriber((ConditionalSubscriber)a, mapper); + } else { + parents[i] = new ParallelMapSubscriber(a, mapper); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + static final class ParallelMapSubscriber implements FlowableSubscriber, Subscription { + + final Subscriber downstream; + + final Function mapper; + + Subscription upstream; + + boolean done; + + ParallelMapSubscriber(Subscriber actual, Function mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + R v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(); + onError(ex); + return; + } + + downstream.onNext(v); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + } + static final class ParallelMapConditionalSubscriber implements ConditionalSubscriber, Subscription { + + final ConditionalSubscriber downstream; + + final Function mapper; + + Subscription upstream; + + boolean done; + + ParallelMapConditionalSubscriber(ConditionalSubscriber actual, Function mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + R v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(); + onError(ex); + return; + } + + downstream.onNext(v); + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + R v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(); + onError(ex); + return false; + } + + return downstream.tryOnNext(v); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelMapTry.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelMapTry.java new file mode 100755 index 0000000..73c1973 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelMapTry.java @@ -0,0 +1,300 @@ +/** + * 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.internal.operators.parallel; + +import org.reactivestreams.*; + +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.parallel.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Maps each 'rail' of the source ParallelFlowable with a mapper function + * and handle any failure based on a handler function. + *

History: 2.0.8 - experimental + * @param the input value type + * @param the output value type + * @since 2.2 + */ +public final class ParallelMapTry extends ParallelFlowable { + + final ParallelFlowable source; + + final Function mapper; + + final BiFunction errorHandler; + + public ParallelMapTry(ParallelFlowable source, Function mapper, + BiFunction errorHandler) { + this.source = source; + this.mapper = mapper; + this.errorHandler = errorHandler; + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") + Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + Subscriber a = subscribers[i]; + if (a instanceof ConditionalSubscriber) { + parents[i] = new ParallelMapTryConditionalSubscriber((ConditionalSubscriber)a, mapper, errorHandler); + } else { + parents[i] = new ParallelMapTrySubscriber(a, mapper, errorHandler); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + static final class ParallelMapTrySubscriber implements ConditionalSubscriber, Subscription { + + final Subscriber downstream; + + final Function mapper; + + final BiFunction errorHandler; + + Subscription upstream; + + boolean done; + + ParallelMapTrySubscriber(Subscriber actual, Function mapper, + BiFunction errorHandler) { + this.downstream = actual; + this.mapper = mapper; + this.errorHandler = errorHandler; + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t) && !done) { + upstream.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + long retries = 0; + + for (;;) { + R v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + ParallelFailureHandling h; + + try { + h = ObjectHelper.requireNonNull(errorHandler.apply(++retries, ex), "The errorHandler returned a null item"); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancel(); + onError(new CompositeException(ex, exc)); + return false; + } + + switch (h) { + case RETRY: + continue; + case SKIP: + return false; + case STOP: + cancel(); + onComplete(); + return false; + default: + cancel(); + onError(ex); + return false; + } + } + + downstream.onNext(v); + return true; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + } + static final class ParallelMapTryConditionalSubscriber implements ConditionalSubscriber, Subscription { + + final ConditionalSubscriber downstream; + + final Function mapper; + + final BiFunction errorHandler; + + Subscription upstream; + + boolean done; + + ParallelMapTryConditionalSubscriber(ConditionalSubscriber actual, + Function mapper, + BiFunction errorHandler) { + this.downstream = actual; + this.mapper = mapper; + this.errorHandler = errorHandler; + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t) && !done) { + upstream.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + return false; + } + long retries = 0; + + for (;;) { + R v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + ParallelFailureHandling h; + + try { + h = ObjectHelper.requireNonNull(errorHandler.apply(++retries, ex), "The errorHandler returned a null item"); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancel(); + onError(new CompositeException(ex, exc)); + return false; + } + + switch (h) { + case RETRY: + continue; + case SKIP: + return false; + case STOP: + cancel(); + onComplete(); + return false; + default: + cancel(); + onError(ex); + return false; + } + } + + return downstream.tryOnNext(v); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelPeek.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelPeek.java new file mode 100755 index 0000000..3e7914c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelPeek.java @@ -0,0 +1,212 @@ +/** + * 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.internal.operators.parallel; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Execute a Consumer in each 'rail' for the current element passing through. + * + * @param the value type + */ +public final class ParallelPeek extends ParallelFlowable { + + final ParallelFlowable source; + + final Consumer onNext; + final Consumer onAfterNext; + final Consumer onError; + final Action onComplete; + final Action onAfterTerminated; + final Consumer onSubscribe; + final LongConsumer onRequest; + final Action onCancel; + + public ParallelPeek(ParallelFlowable source, + Consumer onNext, + Consumer onAfterNext, + Consumer onError, + Action onComplete, + Action onAfterTerminated, + Consumer onSubscribe, + LongConsumer onRequest, + Action onCancel + ) { + this.source = source; + + this.onNext = ObjectHelper.requireNonNull(onNext, "onNext is null"); + this.onAfterNext = ObjectHelper.requireNonNull(onAfterNext, "onAfterNext is null"); + this.onError = ObjectHelper.requireNonNull(onError, "onError is null"); + this.onComplete = ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + this.onAfterTerminated = ObjectHelper.requireNonNull(onAfterTerminated, "onAfterTerminated is null"); + this.onSubscribe = ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + this.onRequest = ObjectHelper.requireNonNull(onRequest, "onRequest is null"); + this.onCancel = ObjectHelper.requireNonNull(onCancel, "onCancel is null"); + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") + Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + parents[i] = new ParallelPeekSubscriber(subscribers[i], this); + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + static final class ParallelPeekSubscriber implements FlowableSubscriber, Subscription { + + final Subscriber downstream; + + final ParallelPeek parent; + + Subscription upstream; + + boolean done; + + ParallelPeekSubscriber(Subscriber actual, ParallelPeek parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void request(long n) { + try { + parent.onRequest.accept(n); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + upstream.request(n); + } + + @Override + public void cancel() { + try { + parent.onCancel.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + upstream.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + try { + parent.onSubscribe.accept(s); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + s.cancel(); + downstream.onSubscribe(EmptySubscription.INSTANCE); + onError(ex); + return; + } + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!done) { + try { + parent.onNext.accept(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onError(ex); + return; + } + + downstream.onNext(t); + + try { + parent.onAfterNext.accept(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onError(ex); + } + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + + try { + parent.onError.accept(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + t = new CompositeException(t, ex); + } + downstream.onError(t); + + try { + parent.onAfterTerminated.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + try { + parent.onComplete.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + downstream.onComplete(); + + try { + parent.onAfterTerminated.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java new file mode 100755 index 0000000..0c3bcfb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduce.java @@ -0,0 +1,160 @@ +/** + * 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.internal.operators.parallel; + +import java.util.concurrent.Callable; + +import org.reactivestreams.*; + +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscribers.DeferredScalarSubscriber; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Reduce the sequence of values in each 'rail' to a single value. + * + * @param the input value type + * @param the result value type + */ +public final class ParallelReduce extends ParallelFlowable { + + final ParallelFlowable source; + + final Callable initialSupplier; + + final BiFunction reducer; + + public ParallelReduce(ParallelFlowable source, Callable initialSupplier, BiFunction reducer) { + this.source = source; + this.initialSupplier = initialSupplier; + this.reducer = reducer; + } + + @Override + public void subscribe(Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") + Subscriber[] parents = new Subscriber[n]; + + for (int i = 0; i < n; i++) { + + R initialValue; + + try { + initialValue = ObjectHelper.requireNonNull(initialSupplier.call(), "The initialSupplier returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + reportError(subscribers, ex); + return; + } + + parents[i] = new ParallelReduceSubscriber(subscribers[i], initialValue, reducer); + } + + source.subscribe(parents); + } + + void reportError(Subscriber[] subscribers, Throwable ex) { + for (Subscriber s : subscribers) { + EmptySubscription.error(ex, s); + } + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + static final class ParallelReduceSubscriber extends DeferredScalarSubscriber { + + private static final long serialVersionUID = 8200530050639449080L; + + final BiFunction reducer; + + R accumulator; + + boolean done; + + ParallelReduceSubscriber(Subscriber subscriber, R initialValue, BiFunction reducer) { + super(subscriber); + this.accumulator = initialValue; + this.reducer = reducer; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (!done) { + R v; + + try { + v = ObjectHelper.requireNonNull(reducer.apply(accumulator, t), "The reducer returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(); + onError(ex); + return; + } + + accumulator = v; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + accumulator = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + + R a = accumulator; + accumulator = null; + complete(a); + } + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java new file mode 100755 index 0000000..8757752 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelReduceFull.java @@ -0,0 +1,258 @@ +/** + * 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.internal.operators.parallel; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiFunction; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Reduces all 'rails' into a single value which then gets reduced into a single + * Publisher sequence. + * + * @param the value type + */ +public final class ParallelReduceFull extends Flowable { + + final ParallelFlowable source; + + final BiFunction reducer; + + public ParallelReduceFull(ParallelFlowable source, BiFunction reducer) { + this.source = source; + this.reducer = reducer; + } + + @Override + protected void subscribeActual(Subscriber s) { + ParallelReduceFullMainSubscriber parent = new ParallelReduceFullMainSubscriber(s, source.parallelism(), reducer); + s.onSubscribe(parent); + + source.subscribe(parent.subscribers); + } + + static final class ParallelReduceFullMainSubscriber extends DeferredScalarSubscription { + + private static final long serialVersionUID = -5370107872170712765L; + + final ParallelReduceFullInnerSubscriber[] subscribers; + + final BiFunction reducer; + + final AtomicReference> current = new AtomicReference>(); + + final AtomicInteger remaining = new AtomicInteger(); + + final AtomicReference error = new AtomicReference(); + + ParallelReduceFullMainSubscriber(Subscriber subscriber, int n, BiFunction reducer) { + super(subscriber); + @SuppressWarnings("unchecked") + ParallelReduceFullInnerSubscriber[] a = new ParallelReduceFullInnerSubscriber[n]; + for (int i = 0; i < n; i++) { + a[i] = new ParallelReduceFullInnerSubscriber(this, reducer); + } + this.subscribers = a; + this.reducer = reducer; + remaining.lazySet(n); + } + + SlotPair addValue(T value) { + for (;;) { + SlotPair curr = current.get(); + + if (curr == null) { + curr = new SlotPair(); + if (!current.compareAndSet(null, curr)) { + continue; + } + } + + int c = curr.tryAcquireSlot(); + if (c < 0) { + current.compareAndSet(curr, null); + continue; + } + if (c == 0) { + curr.first = value; + } else { + curr.second = value; + } + + if (curr.releaseSlot()) { + current.compareAndSet(curr, null); + return curr; + } + return null; + } + } + + @Override + public void cancel() { + for (ParallelReduceFullInnerSubscriber inner : subscribers) { + inner.cancel(); + } + } + + void innerError(Throwable ex) { + if (error.compareAndSet(null, ex)) { + cancel(); + downstream.onError(ex); + } else { + if (ex != error.get()) { + RxJavaPlugins.onError(ex); + } + } + } + + void innerComplete(T value) { + if (value != null) { + for (;;) { + SlotPair sp = addValue(value); + + if (sp != null) { + + try { + value = ObjectHelper.requireNonNull(reducer.apply(sp.first, sp.second), "The reducer returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + innerError(ex); + return; + } + + } else { + break; + } + } + } + + if (remaining.decrementAndGet() == 0) { + SlotPair sp = current.get(); + current.lazySet(null); + + if (sp != null) { + complete(sp.first); + } else { + downstream.onComplete(); + } + } + } + } + + static final class ParallelReduceFullInnerSubscriber + extends AtomicReference + implements FlowableSubscriber { + + private static final long serialVersionUID = -7954444275102466525L; + + final ParallelReduceFullMainSubscriber parent; + + final BiFunction reducer; + + T value; + + boolean done; + + ParallelReduceFullInnerSubscriber(ParallelReduceFullMainSubscriber parent, BiFunction reducer) { + this.parent = parent; + this.reducer = reducer; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + if (!done) { + T v = value; + + if (v == null) { + value = t; + } else { + + try { + v = ObjectHelper.requireNonNull(reducer.apply(v, t), "The reducer returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + get().cancel(); + onError(ex); + return; + } + + value = v; + } + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + parent.innerError(t); + } + + @Override + public void onComplete() { + if (!done) { + done = true; + parent.innerComplete(value); + } + } + + void cancel() { + SubscriptionHelper.cancel(this); + } + } + + static final class SlotPair extends AtomicInteger { + + private static final long serialVersionUID = 473971317683868662L; + + T first; + + T second; + + final AtomicInteger releaseIndex = new AtomicInteger(); + + int tryAcquireSlot() { + for (;;) { + int acquired = get(); + if (acquired >= 2) { + return -1; + } + + if (compareAndSet(acquired, acquired + 1)) { + return acquired; + } + } + } + + boolean releaseSlot() { + return releaseIndex.incrementAndGet() == 2; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelRunOn.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelRunOn.java new file mode 100755 index 0000000..e1b5886 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelRunOn.java @@ -0,0 +1,450 @@ +/** + * 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.internal.operators.parallel; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.Scheduler.Worker; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.schedulers.SchedulerMultiWorkerSupport; +import io.reactivex.internal.schedulers.SchedulerMultiWorkerSupport.WorkerCallback; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Ensures each 'rail' from upstream runs on a Worker from a Scheduler. + * + * @param the value type + */ +public final class ParallelRunOn extends ParallelFlowable { + final ParallelFlowable source; + + final Scheduler scheduler; + + final int prefetch; + + public ParallelRunOn(ParallelFlowable parent, + Scheduler scheduler, int prefetch) { + this.source = parent; + this.scheduler = scheduler; + this.prefetch = prefetch; + } + + @Override + public void subscribe(final Subscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + @SuppressWarnings("unchecked") + final Subscriber[] parents = new Subscriber[n]; + + if (scheduler instanceof SchedulerMultiWorkerSupport) { + SchedulerMultiWorkerSupport multiworker = (SchedulerMultiWorkerSupport) scheduler; + multiworker.createWorkers(n, new MultiWorkerCallback(subscribers, parents)); + } else { + for (int i = 0; i < n; i++) { + createSubscriber(i, subscribers, parents, scheduler.createWorker()); + } + } + source.subscribe(parents); + } + + void createSubscriber(int i, Subscriber[] subscribers, + Subscriber[] parents, Worker worker) { + + Subscriber a = subscribers[i]; + + SpscArrayQueue q = new SpscArrayQueue(prefetch); + + if (a instanceof ConditionalSubscriber) { + parents[i] = new RunOnConditionalSubscriber((ConditionalSubscriber)a, prefetch, q, worker); + } else { + parents[i] = new RunOnSubscriber(a, prefetch, q, worker); + } + } + + final class MultiWorkerCallback implements WorkerCallback { + + final Subscriber[] subscribers; + + final Subscriber[] parents; + + MultiWorkerCallback(Subscriber[] subscribers, + Subscriber[] parents) { + this.subscribers = subscribers; + this.parents = parents; + } + + @Override + public void onWorker(int i, Worker w) { + createSubscriber(i, subscribers, parents, w); + } + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + abstract static class BaseRunOnSubscriber extends AtomicInteger + implements FlowableSubscriber, Subscription, Runnable { + + private static final long serialVersionUID = 9222303586456402150L; + + final int prefetch; + + final int limit; + + final SpscArrayQueue queue; + + final Worker worker; + + Subscription upstream; + + volatile boolean done; + + Throwable error; + + final AtomicLong requested = new AtomicLong(); + + volatile boolean cancelled; + + int consumed; + + BaseRunOnSubscriber(int prefetch, SpscArrayQueue queue, Worker worker) { + this.prefetch = prefetch; + this.queue = queue; + this.limit = prefetch - (prefetch >> 2); + this.worker = worker; + } + + @Override + public final void onNext(T t) { + if (done) { + return; + } + if (!queue.offer(t)) { + upstream.cancel(); + onError(new MissingBackpressureException("Queue is full?!")); + return; + } + schedule(); + } + + @Override + public final void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + schedule(); + } + + @Override + public final void onComplete() { + if (done) { + return; + } + done = true; + schedule(); + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + schedule(); + } + } + + @Override + public final void cancel() { + if (!cancelled) { + cancelled = true; + upstream.cancel(); + worker.dispose(); + + if (getAndIncrement() == 0) { + queue.clear(); + } + } + } + + final void schedule() { + if (getAndIncrement() == 0) { + worker.schedule(this); + } + } + } + + static final class RunOnSubscriber extends BaseRunOnSubscriber { + + private static final long serialVersionUID = 1075119423897941642L; + + final Subscriber downstream; + + RunOnSubscriber(Subscriber actual, int prefetch, SpscArrayQueue queue, Worker worker) { + super(prefetch, queue, worker); + this.downstream = actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(prefetch); + } + } + + @Override + public void run() { + int missed = 1; + int c = consumed; + SpscArrayQueue q = queue; + Subscriber a = downstream; + int lim = limit; + + for (;;) { + + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + + if (d) { + Throwable ex = error; + if (ex != null) { + q.clear(); + + a.onError(ex); + + worker.dispose(); + return; + } + } + + T v = q.poll(); + + boolean empty = v == null; + + if (d && empty) { + a.onComplete(); + + worker.dispose(); + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + + int p = ++c; + if (p == lim) { + c = 0; + upstream.request(p); + } + } + + if (e == r) { + if (cancelled) { + q.clear(); + return; + } + + if (done) { + Throwable ex = error; + if (ex != null) { + q.clear(); + + a.onError(ex); + + worker.dispose(); + return; + } + if (q.isEmpty()) { + a.onComplete(); + + worker.dispose(); + return; + } + } + } + + if (e != 0L && r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + + int w = get(); + if (w == missed) { + consumed = c; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + } + + static final class RunOnConditionalSubscriber extends BaseRunOnSubscriber { + + private static final long serialVersionUID = 1075119423897941642L; + + final ConditionalSubscriber downstream; + + RunOnConditionalSubscriber(ConditionalSubscriber actual, int prefetch, SpscArrayQueue queue, Worker worker) { + super(prefetch, queue, worker); + this.downstream = actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(prefetch); + } + } + + @Override + public void run() { + int missed = 1; + int c = consumed; + SpscArrayQueue q = queue; + ConditionalSubscriber a = downstream; + int lim = limit; + + for (;;) { + + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + + if (d) { + Throwable ex = error; + if (ex != null) { + q.clear(); + + a.onError(ex); + + worker.dispose(); + return; + } + } + + T v = q.poll(); + + boolean empty = v == null; + + if (d && empty) { + a.onComplete(); + + worker.dispose(); + return; + } + + if (empty) { + break; + } + + if (a.tryOnNext(v)) { + e++; + } + + int p = ++c; + if (p == lim) { + c = 0; + upstream.request(p); + } + } + + if (e == r) { + if (cancelled) { + q.clear(); + return; + } + + if (done) { + Throwable ex = error; + if (ex != null) { + q.clear(); + + a.onError(ex); + + worker.dispose(); + return; + } + if (q.isEmpty()) { + a.onComplete(); + + worker.dispose(); + return; + } + } + } + + if (e != 0L && r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + + int w = get(); + if (w == missed) { + consumed = c; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java b/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java new file mode 100755 index 0000000..a7c4947 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/parallel/ParallelSortedJoin.java @@ -0,0 +1,304 @@ +/** + * 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.internal.operators.parallel; + +import java.util.*; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Given sorted rail sequences (according to the provided comparator) as List + * emit the smallest item from these parallel Lists to the Subscriber. + *

+ * It expects the source to emit exactly one list (which could be empty). + * + * @param the value type + */ +public final class ParallelSortedJoin extends Flowable { + + final ParallelFlowable> source; + + final Comparator comparator; + + public ParallelSortedJoin(ParallelFlowable> source, Comparator comparator) { + this.source = source; + this.comparator = comparator; + } + + @Override + protected void subscribeActual(Subscriber s) { + SortedJoinSubscription parent = new SortedJoinSubscription(s, source.parallelism(), comparator); + s.onSubscribe(parent); + + source.subscribe(parent.subscribers); + } + + static final class SortedJoinSubscription + extends AtomicInteger + implements Subscription { + + private static final long serialVersionUID = 3481980673745556697L; + + final Subscriber downstream; + + final SortedJoinInnerSubscriber[] subscribers; + + final List[] lists; + + final int[] indexes; + + final Comparator comparator; + + final AtomicLong requested = new AtomicLong(); + + volatile boolean cancelled; + + final AtomicInteger remaining = new AtomicInteger(); + + final AtomicReference error = new AtomicReference(); + + @SuppressWarnings("unchecked") + SortedJoinSubscription(Subscriber actual, int n, Comparator comparator) { + this.downstream = actual; + this.comparator = comparator; + + SortedJoinInnerSubscriber[] s = new SortedJoinInnerSubscriber[n]; + + for (int i = 0; i < n; i++) { + s[i] = new SortedJoinInnerSubscriber(this, i); + } + this.subscribers = s; + this.lists = new List[n]; + this.indexes = new int[n]; + remaining.lazySet(n); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + if (remaining.get() == 0) { + drain(); + } + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + cancelAll(); + if (getAndIncrement() == 0) { + Arrays.fill(lists, null); + } + } + } + + void cancelAll() { + for (SortedJoinInnerSubscriber s : subscribers) { + s.cancel(); + } + } + + void innerNext(List value, int index) { + lists[index] = value; + if (remaining.decrementAndGet() == 0) { + drain(); + } + } + + void innerError(Throwable e) { + if (error.compareAndSet(null, e)) { + drain(); + } else { + if (e != error.get()) { + RxJavaPlugins.onError(e); + } + } + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber a = downstream; + List[] lists = this.lists; + int[] indexes = this.indexes; + int n = indexes.length; + + for (;;) { + + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + Arrays.fill(lists, null); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + cancelAll(); + Arrays.fill(lists, null); + a.onError(ex); + return; + } + + T min = null; + int minIndex = -1; + + for (int i = 0; i < n; i++) { + List list = lists[i]; + int index = indexes[i]; + + if (list.size() != index) { + if (min == null) { + min = list.get(index); + minIndex = i; + } else { + T b = list.get(index); + + boolean smaller; + + try { + smaller = comparator.compare(min, b) > 0; + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + cancelAll(); + Arrays.fill(lists, null); + if (!error.compareAndSet(null, exc)) { + RxJavaPlugins.onError(exc); + } + a.onError(error.get()); + return; + } + if (smaller) { + min = b; + minIndex = i; + } + } + } + } + + if (min == null) { + Arrays.fill(lists, null); + a.onComplete(); + return; + } + + a.onNext(min); + + indexes[minIndex]++; + + e++; + } + + if (e == r) { + if (cancelled) { + Arrays.fill(lists, null); + return; + } + + Throwable ex = error.get(); + if (ex != null) { + cancelAll(); + Arrays.fill(lists, null); + a.onError(ex); + return; + } + + boolean empty = true; + + for (int i = 0; i < n; i++) { + if (indexes[i] != lists[i].size()) { + empty = false; + break; + } + } + + if (empty) { + Arrays.fill(lists, null); + a.onComplete(); + return; + } + } + + if (e != 0 && r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + + int w = get(); + if (w == missed) { + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + } + + static final class SortedJoinInnerSubscriber + extends AtomicReference + implements FlowableSubscriber> { + + private static final long serialVersionUID = 6751017204873808094L; + + final SortedJoinSubscription parent; + + final int index; + + SortedJoinInnerSubscriber(SortedJoinSubscription parent, int index) { + this.parent = parent; + this.index = index; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(List t) { + parent.innerNext(t, index); + } + + @Override + public void onError(Throwable t) { + parent.innerError(t); + } + + @Override + public void onComplete() { + // ignored + } + + void cancel() { + SubscriptionHelper.cancel(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java b/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java new file mode 100755 index 0000000..2584506 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleAmb.java @@ -0,0 +1,131 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleAmb extends Single { + private final SingleSource[] sources; + private final Iterable> sourcesIterable; + + public SingleAmb(SingleSource[] sources, Iterable> sourcesIterable) { + this.sources = sources; + this.sourcesIterable = sourcesIterable; + } + + @Override + @SuppressWarnings("unchecked") + protected void subscribeActual(final SingleObserver observer) { + SingleSource[] sources = this.sources; + int count = 0; + if (sources == null) { + sources = new SingleSource[8]; + try { + for (SingleSource element : sourcesIterable) { + if (element == null) { + EmptyDisposable.error(new NullPointerException("One of the sources is null"), observer); + return; + } + if (count == sources.length) { + SingleSource[] b = new SingleSource[count + (count >> 2)]; + System.arraycopy(sources, 0, b, 0, count); + sources = b; + } + sources[count++] = element; + } + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + } else { + count = sources.length; + } + + final AtomicBoolean winner = new AtomicBoolean(); + final CompositeDisposable set = new CompositeDisposable(); + + observer.onSubscribe(set); + + for (int i = 0; i < count; i++) { + SingleSource s1 = sources[i]; + if (set.isDisposed()) { + return; + } + + if (s1 == null) { + set.dispose(); + Throwable e = new NullPointerException("One of the sources is null"); + if (winner.compareAndSet(false, true)) { + observer.onError(e); + } else { + RxJavaPlugins.onError(e); + } + return; + } + + s1.subscribe(new AmbSingleObserver(observer, set, winner)); + } + } + + static final class AmbSingleObserver implements SingleObserver { + + final CompositeDisposable set; + + final SingleObserver downstream; + + final AtomicBoolean winner; + + Disposable upstream; + + AmbSingleObserver(SingleObserver observer, CompositeDisposable set, AtomicBoolean winner) { + this.downstream = observer; + this.set = set; + this.winner = winner; + } + + @Override + public void onSubscribe(Disposable d) { + this.upstream = d; + set.add(d); + } + + @Override + public void onSuccess(T value) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); + set.dispose(); + downstream.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + if (winner.compareAndSet(false, true)) { + set.delete(upstream); + set.dispose(); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleCache.java b/src/main/java/io/reactivex/internal/operators/single/SingleCache.java new file mode 100755 index 0000000..1752a28 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleCache.java @@ -0,0 +1,178 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; + +public final class SingleCache extends Single implements SingleObserver { + + @SuppressWarnings("rawtypes") + static final CacheDisposable[] EMPTY = new CacheDisposable[0]; + @SuppressWarnings("rawtypes") + static final CacheDisposable[] TERMINATED = new CacheDisposable[0]; + + final SingleSource source; + + final AtomicInteger wip; + + final AtomicReference[]> observers; + + T value; + + Throwable error; + + @SuppressWarnings("unchecked") + public SingleCache(SingleSource source) { + this.source = source; + this.wip = new AtomicInteger(); + this.observers = new AtomicReference[]>(EMPTY); + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + CacheDisposable d = new CacheDisposable(observer, this); + observer.onSubscribe(d); + + if (add(d)) { + if (d.isDisposed()) { + remove(d); + } + } else { + Throwable ex = error; + if (ex != null) { + observer.onError(ex); + } else { + observer.onSuccess(value); + } + return; + } + + if (wip.getAndIncrement() == 0) { + source.subscribe(this); + } + } + + boolean add(CacheDisposable observer) { + for (;;) { + CacheDisposable[] a = observers.get(); + if (a == TERMINATED) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + CacheDisposable[] b = new CacheDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = observer; + if (observers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(CacheDisposable observer) { + for (;;) { + CacheDisposable[] a = observers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == observer) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + CacheDisposable[] b; + + if (n == 1) { + b = EMPTY; + } else { + b = new CacheDisposable[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (observers.compareAndSet(a, b)) { + return; + } + } + } + + @Override + public void onSubscribe(Disposable d) { + // not supported by this operator + } + + @SuppressWarnings("unchecked") + @Override + public void onSuccess(T value) { + this.value = value; + + for (CacheDisposable d : observers.getAndSet(TERMINATED)) { + if (!d.isDisposed()) { + d.downstream.onSuccess(value); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable e) { + this.error = e; + + for (CacheDisposable d : observers.getAndSet(TERMINATED)) { + if (!d.isDisposed()) { + d.downstream.onError(e); + } + } + } + + static final class CacheDisposable + extends AtomicBoolean + implements Disposable { + + private static final long serialVersionUID = 7514387411091976596L; + + final SingleObserver downstream; + + final SingleCache parent; + + CacheDisposable(SingleObserver actual, SingleCache parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public boolean isDisposed() { + return get(); + } + + @Override + public void dispose() { + if (compareAndSet(false, true)) { + parent.remove(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleContains.java b/src/main/java/io/reactivex/internal/operators/single/SingleContains.java new file mode 100755 index 0000000..c8fb355 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleContains.java @@ -0,0 +1,74 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiPredicate; + +public final class SingleContains extends Single { + + final SingleSource source; + + final Object value; + + final BiPredicate comparer; + + public SingleContains(SingleSource source, Object value, BiPredicate comparer) { + this.source = source; + this.value = value; + this.comparer = comparer; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + source.subscribe(new ContainsSingleObserver(observer)); + } + + final class ContainsSingleObserver implements SingleObserver { + + private final SingleObserver downstream; + + ContainsSingleObserver(SingleObserver observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T v) { + boolean b; + + try { + b = comparer.test(v, value); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + downstream.onSuccess(b); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleCreate.java b/src/main/java/io/reactivex/internal/operators/single/SingleCreate.java new file mode 100755 index 0000000..2b0dcfa --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleCreate.java @@ -0,0 +1,131 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Cancellable; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleCreate extends Single { + + final SingleOnSubscribe source; + + public SingleCreate(SingleOnSubscribe source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + Emitter parent = new Emitter(observer); + observer.onSubscribe(parent); + + try { + source.subscribe(parent); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + parent.onError(ex); + } + } + + static final class Emitter + extends AtomicReference + implements SingleEmitter, Disposable { + + private static final long serialVersionUID = -2467358622224974244L; + + final SingleObserver downstream; + + Emitter(SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSuccess(T value) { + if (get() != DisposableHelper.DISPOSED) { + Disposable d = getAndSet(DisposableHelper.DISPOSED); + if (d != DisposableHelper.DISPOSED) { + try { + if (value == null) { + downstream.onError(new NullPointerException("onSuccess called with null. Null values are generally not allowed in 2.x operators and sources.")); + } else { + downstream.onSuccess(value); + } + } finally { + if (d != null) { + d.dispose(); + } + } + } + } + } + + @Override + public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + if (get() != DisposableHelper.DISPOSED) { + Disposable d = getAndSet(DisposableHelper.DISPOSED); + if (d != DisposableHelper.DISPOSED) { + try { + downstream.onError(t); + } finally { + if (d != null) { + d.dispose(); + } + } + return true; + } + } + return false; + } + + @Override + public void setDisposable(Disposable d) { + DisposableHelper.set(this, d); + } + + @Override + public void setCancellable(Cancellable c) { + setDisposable(new CancellableDisposable(c)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public String toString() { + return String.format("%s{%s}", getClass().getSimpleName(), super.toString()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDefer.java b/src/main/java/io/reactivex/internal/operators/single/SingleDefer.java new file mode 100755 index 0000000..0f7a66d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDefer.java @@ -0,0 +1,46 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; + +public final class SingleDefer extends Single { + + final Callable> singleSupplier; + + public SingleDefer(Callable> singleSupplier) { + this.singleSupplier = singleSupplier; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + SingleSource next; + + try { + next = ObjectHelper.requireNonNull(singleSupplier.call(), "The singleSupplier returned a null SingleSource"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + EmptyDisposable.error(e, observer); + return; + } + + next.subscribe(observer); + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelay.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelay.java new file mode 100755 index 0000000..3ea0104 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelay.java @@ -0,0 +1,96 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.SequentialDisposable; + +public final class SingleDelay extends Single { + + final SingleSource source; + final long time; + final TimeUnit unit; + final Scheduler scheduler; + final boolean delayError; + + public SingleDelay(SingleSource source, long time, TimeUnit unit, Scheduler scheduler, boolean delayError) { + this.source = source; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + this.delayError = delayError; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + final SequentialDisposable sd = new SequentialDisposable(); + observer.onSubscribe(sd); + source.subscribe(new Delay(sd, observer)); + } + + final class Delay implements SingleObserver { + private final SequentialDisposable sd; + final SingleObserver downstream; + + Delay(SequentialDisposable sd, SingleObserver observer) { + this.sd = sd; + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + sd.replace(d); + } + + @Override + public void onSuccess(final T value) { + sd.replace(scheduler.scheduleDirect(new OnSuccess(value), time, unit)); + } + + @Override + public void onError(final Throwable e) { + sd.replace(scheduler.scheduleDirect(new OnError(e), delayError ? time : 0, unit)); + } + + final class OnSuccess implements Runnable { + private final T value; + + OnSuccess(T value) { + this.value = value; + } + + @Override + public void run() { + downstream.onSuccess(value); + } + } + + final class OnError implements Runnable { + private final Throwable e; + + OnError(Throwable e) { + this.e = e; + } + + @Override + public void run() { + downstream.onError(e); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java new file mode 100755 index 0000000..86dbd4d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithCompletable.java @@ -0,0 +1,82 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.observers.ResumeSingleObserver; + +public final class SingleDelayWithCompletable extends Single { + + final SingleSource source; + + final CompletableSource other; + + public SingleDelayWithCompletable(SingleSource source, CompletableSource other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + other.subscribe(new OtherObserver(observer, source)); + } + + static final class OtherObserver + extends AtomicReference + implements CompletableObserver, Disposable { + + private static final long serialVersionUID = -8565274649390031272L; + + final SingleObserver downstream; + + final SingleSource source; + + OtherObserver(SingleObserver actual, SingleSource source) { + this.downstream = actual; + this.source = source; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + + downstream.onSubscribe(this); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + source.subscribe(new ResumeSingleObserver(this, downstream)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java new file mode 100755 index 0000000..c905bba --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithObservable.java @@ -0,0 +1,100 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.observers.ResumeSingleObserver; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleDelayWithObservable extends Single { + + final SingleSource source; + + final ObservableSource other; + + public SingleDelayWithObservable(SingleSource source, ObservableSource other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + other.subscribe(new OtherSubscriber(observer, source)); + } + + static final class OtherSubscriber + extends AtomicReference + implements Observer, Disposable { + + private static final long serialVersionUID = -8565274649390031272L; + + final SingleObserver downstream; + + final SingleSource source; + + boolean done; + + OtherSubscriber(SingleObserver actual, SingleSource source) { + this.downstream = actual; + this.source = source; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.set(this, d)) { + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(U value) { + get().dispose(); + onComplete(); + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaPlugins.onError(e); + return; + } + done = true; + downstream.onError(e); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + source.subscribe(new ResumeSingleObserver(this, downstream)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java new file mode 100755 index 0000000..ee93007 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithPublisher.java @@ -0,0 +1,109 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.observers.ResumeSingleObserver; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleDelayWithPublisher extends Single { + + final SingleSource source; + + final Publisher other; + + public SingleDelayWithPublisher(SingleSource source, Publisher other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + other.subscribe(new OtherSubscriber(observer, source)); + } + + static final class OtherSubscriber + extends AtomicReference + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = -8565274649390031272L; + + final SingleObserver downstream; + + final SingleSource source; + + boolean done; + + Subscription upstream; + + OtherSubscriber(SingleObserver actual, SingleSource source) { + this.downstream = actual; + this.source = source; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(U value) { + upstream.cancel(); + onComplete(); + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaPlugins.onError(e); + return; + } + done = true; + downstream.onError(e); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + source.subscribe(new ResumeSingleObserver(this, downstream)); + } + + @Override + public void dispose() { + upstream.cancel(); + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java new file mode 100755 index 0000000..83255b9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDelayWithSingle.java @@ -0,0 +1,82 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.observers.ResumeSingleObserver; + +public final class SingleDelayWithSingle extends Single { + + final SingleSource source; + + final SingleSource other; + + public SingleDelayWithSingle(SingleSource source, SingleSource other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + other.subscribe(new OtherObserver(observer, source)); + } + + static final class OtherObserver + extends AtomicReference + implements SingleObserver, Disposable { + + private static final long serialVersionUID = -8565274649390031272L; + + final SingleObserver downstream; + + final SingleSource source; + + OtherObserver(SingleObserver actual, SingleSource source) { + this.downstream = actual; + this.source = source; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(U value) { + source.subscribe(new ResumeSingleObserver(this, downstream)); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDematerialize.java b/src/main/java/io/reactivex/internal/operators/single/SingleDematerialize.java new file mode 100755 index 0000000..2e402b0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDematerialize.java @@ -0,0 +1,105 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of the source to a Notification, then + * maps it back to the corresponding signal type. + * @param the element type of the source + * @param the element type of the Notification and result + * @since 2.2.4 - experimental + */ +@Experimental +public final class SingleDematerialize extends Maybe { + + final Single source; + + final Function> selector; + + public SingleDematerialize(Single source, Function> selector) { + this.source = source; + this.selector = selector; + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + source.subscribe(new DematerializeObserver(observer, selector)); + } + + static final class DematerializeObserver implements SingleObserver, Disposable { + + final MaybeObserver downstream; + + final Function> selector; + + Disposable upstream; + + DematerializeObserver(MaybeObserver downstream, + Function> selector) { + this.downstream = downstream; + this.selector = selector; + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(upstream, d)) { + upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + Notification notification; + + try { + notification = ObjectHelper.requireNonNull(selector.apply(t), "The selector returned a null Notification"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + if (notification.isOnNext()) { + downstream.onSuccess(notification.getValue()); + } else if (notification.isOnComplete()) { + downstream.onComplete(); + } else { + downstream.onError(notification.getError()); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDetach.java b/src/main/java/io/reactivex/internal/operators/single/SingleDetach.java new file mode 100755 index 0000000..ad9883a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDetach.java @@ -0,0 +1,90 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Breaks the references between the upstream and downstream when the Maybe terminates. + *

History: 2.1.5 - experimental + * @param the value type + * @since 2.2 + */ +public final class SingleDetach extends Single { + + final SingleSource source; + + public SingleDetach(SingleSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new DetachSingleObserver(observer)); + } + + static final class DetachSingleObserver implements SingleObserver, Disposable { + + SingleObserver downstream; + + Disposable upstream; + + DetachSingleObserver(SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + downstream = null; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + SingleObserver a = downstream; + if (a != null) { + downstream = null; + a.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + SingleObserver a = downstream; + if (a != null) { + downstream = null; + a.onError(e); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterSuccess.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterSuccess.java new file mode 100755 index 0000000..208bb0e --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterSuccess.java @@ -0,0 +1,95 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Calls a consumer after pushing the current item to the downstream. + *

History: 2.0.1 - experimental + * @param the value type + * @since 2.1 + */ +public final class SingleDoAfterSuccess extends Single { + + final SingleSource source; + + final Consumer onAfterSuccess; + + public SingleDoAfterSuccess(SingleSource source, Consumer onAfterSuccess) { + this.source = source; + this.onAfterSuccess = onAfterSuccess; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new DoAfterObserver(observer, onAfterSuccess)); + } + + static final class DoAfterObserver implements SingleObserver, Disposable { + + final SingleObserver downstream; + + final Consumer onAfterSuccess; + + Disposable upstream; + + DoAfterObserver(SingleObserver actual, Consumer onAfterSuccess) { + this.downstream = actual; + this.onAfterSuccess = onAfterSuccess; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + downstream.onSuccess(t); + + try { + onAfterSuccess.accept(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + // remember, onSuccess is a terminal event and we can't call onError + RxJavaPlugins.onError(ex); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterTerminate.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterTerminate.java new file mode 100755 index 0000000..eed7993 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoAfterTerminate.java @@ -0,0 +1,102 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Calls an action after pushing the current item or an error to the downstream. + *

History: 2.0.6 - experimental + * @param the value type + * @since 2.1 + */ +public final class SingleDoAfterTerminate extends Single { + + final SingleSource source; + + final Action onAfterTerminate; + + public SingleDoAfterTerminate(SingleSource source, Action onAfterTerminate) { + this.source = source; + this.onAfterTerminate = onAfterTerminate; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new DoAfterTerminateObserver(observer, onAfterTerminate)); + } + + static final class DoAfterTerminateObserver implements SingleObserver, Disposable { + + final SingleObserver downstream; + + final Action onAfterTerminate; + + Disposable upstream; + + DoAfterTerminateObserver(SingleObserver actual, Action onAfterTerminate) { + this.downstream = actual; + this.onAfterTerminate = onAfterTerminate; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + downstream.onSuccess(t); + + onAfterTerminate(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + + onAfterTerminate(); + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + private void onAfterTerminate() { + try { + onAfterTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoFinally.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoFinally.java new file mode 100755 index 0000000..7a6bc67 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoFinally.java @@ -0,0 +1,105 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Execute an action after an onSuccess, onError or a dispose event. + *

History: 2.0.1 - experimental + * @param the value type + * @since 2.1 + */ +public final class SingleDoFinally extends Single { + + final SingleSource source; + + final Action onFinally; + + public SingleDoFinally(SingleSource source, Action onFinally) { + this.source = source; + this.onFinally = onFinally; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new DoFinallyObserver(observer, onFinally)); + } + + static final class DoFinallyObserver extends AtomicInteger implements SingleObserver, Disposable { + + private static final long serialVersionUID = 4109457741734051389L; + + final SingleObserver downstream; + + final Action onFinally; + + Disposable upstream; + + DoFinallyObserver(SingleObserver actual, Action onFinally) { + this.downstream = actual; + this.onFinally = onFinally; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T t) { + downstream.onSuccess(t); + runFinally(); + } + + @Override + public void onError(Throwable t) { + downstream.onError(t); + runFinally(); + } + + @Override + public void dispose() { + upstream.dispose(); + runFinally(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + void runFinally() { + if (compareAndSet(0, 1)) { + try { + onFinally.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnDispose.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnDispose.java new file mode 100755 index 0000000..29cd86d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnDispose.java @@ -0,0 +1,93 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleDoOnDispose extends Single { + final SingleSource source; + + final Action onDispose; + + public SingleDoOnDispose(SingleSource source, Action onDispose) { + this.source = source; + this.onDispose = onDispose; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + source.subscribe(new DoOnDisposeObserver(observer, onDispose)); + } + + static final class DoOnDisposeObserver + extends AtomicReference + implements SingleObserver, Disposable { + private static final long serialVersionUID = -8583764624474935784L; + + final SingleObserver downstream; + + Disposable upstream; + + DoOnDisposeObserver(SingleObserver actual, Action onDispose) { + this.downstream = actual; + this.lazySet(onDispose); + } + + @Override + public void dispose() { + Action a = getAndSet(null); + if (a != null) { + try { + a.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + upstream.dispose(); + } + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnError.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnError.java new file mode 100755 index 0000000..c20eb7f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnError.java @@ -0,0 +1,67 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Consumer; + +public final class SingleDoOnError extends Single { + + final SingleSource source; + + final Consumer onError; + + public SingleDoOnError(SingleSource source, Consumer onError) { + this.source = source; + this.onError = onError; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + source.subscribe(new DoOnError(observer)); + } + + final class DoOnError implements SingleObserver { + private final SingleObserver downstream; + + DoOnError(SingleObserver observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + try { + onError.accept(e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + downstream.onError(e); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnEvent.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnEvent.java new file mode 100755 index 0000000..e057642 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnEvent.java @@ -0,0 +1,76 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.BiConsumer; + +public final class SingleDoOnEvent extends Single { + final SingleSource source; + + final BiConsumer onEvent; + + public SingleDoOnEvent(SingleSource source, BiConsumer onEvent) { + this.source = source; + this.onEvent = onEvent; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + source.subscribe(new DoOnEvent(observer)); + } + + final class DoOnEvent implements SingleObserver { + private final SingleObserver downstream; + + DoOnEvent(SingleObserver observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + try { + onEvent.accept(value, null); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + try { + onEvent.accept(null, e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSubscribe.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSubscribe.java new file mode 100755 index 0000000..4103ad6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSubscribe.java @@ -0,0 +1,90 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Calls a callback when the upstream calls onSubscribe with a disposable. + * + * @param the value type + */ +public final class SingleDoOnSubscribe extends Single { + + final SingleSource source; + + final Consumer onSubscribe; + + public SingleDoOnSubscribe(SingleSource source, Consumer onSubscribe) { + this.source = source; + this.onSubscribe = onSubscribe; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + source.subscribe(new DoOnSubscribeSingleObserver(observer, onSubscribe)); + } + + static final class DoOnSubscribeSingleObserver implements SingleObserver { + + final SingleObserver downstream; + + final Consumer onSubscribe; + + boolean done; + + DoOnSubscribeSingleObserver(SingleObserver actual, Consumer onSubscribe) { + this.downstream = actual; + this.onSubscribe = onSubscribe; + } + + @Override + public void onSubscribe(Disposable d) { + try { + onSubscribe.accept(d); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + done = true; + d.dispose(); + EmptyDisposable.error(ex, downstream); + return; + } + + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + if (done) { + return; + } + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + if (done) { + RxJavaPlugins.onError(e); + return; + } + downstream.onError(e); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSuccess.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSuccess.java new file mode 100755 index 0000000..f915c98 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnSuccess.java @@ -0,0 +1,69 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; + +public final class SingleDoOnSuccess extends Single { + + final SingleSource source; + + final Consumer onSuccess; + + public SingleDoOnSuccess(SingleSource source, Consumer onSuccess) { + this.source = source; + this.onSuccess = onSuccess; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + source.subscribe(new DoOnSuccess(observer)); + } + + final class DoOnSuccess implements SingleObserver { + + final SingleObserver downstream; + + DoOnSuccess(SingleObserver observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + try { + onSuccess.accept(value); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleDoOnTerminate.java b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnTerminate.java new file mode 100755 index 0000000..7497aff --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleDoOnTerminate.java @@ -0,0 +1,78 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; + +public final class SingleDoOnTerminate extends Single { + + final SingleSource source; + + final Action onTerminate; + + public SingleDoOnTerminate(SingleSource source, Action onTerminate) { + this.source = source; + this.onTerminate = onTerminate; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + source.subscribe(new DoOnTerminate(observer)); + } + + final class DoOnTerminate implements SingleObserver { + + final SingleObserver downstream; + + DoOnTerminate(SingleObserver observer) { + this.downstream = observer; + } + + @Override + public void onSubscribe(Disposable d) { + downstream.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + try { + onTerminate.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java b/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java new file mode 100755 index 0000000..07d3f36 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleEquals.java @@ -0,0 +1,93 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleEquals extends Single { + + final SingleSource first; + final SingleSource second; + + public SingleEquals(SingleSource first, SingleSource second) { + this.first = first; + this.second = second; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + final AtomicInteger count = new AtomicInteger(); + final Object[] values = { null, null }; + + final CompositeDisposable set = new CompositeDisposable(); + observer.onSubscribe(set); + + first.subscribe(new InnerObserver(0, set, values, observer, count)); + second.subscribe(new InnerObserver(1, set, values, observer, count)); + } + + static class InnerObserver implements SingleObserver { + final int index; + final CompositeDisposable set; + final Object[] values; + final SingleObserver downstream; + final AtomicInteger count; + + InnerObserver(int index, CompositeDisposable set, Object[] values, SingleObserver observer, AtomicInteger count) { + this.index = index; + this.set = set; + this.values = values; + this.downstream = observer; + this.count = count; + } + + @Override + public void onSubscribe(Disposable d) { + set.add(d); + } + + @Override + public void onSuccess(T value) { + values[index] = value; + + if (count.incrementAndGet() == 2) { + downstream.onSuccess(ObjectHelper.equals(values[0], values[1])); + } + } + + @Override + public void onError(Throwable e) { + for (;;) { + int state = count.get(); + if (state >= 2) { + RxJavaPlugins.onError(e); + return; + } + if (count.compareAndSet(state, 2)) { + set.dispose(); + downstream.onError(e); + return; + } + } + } + + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleError.java b/src/main/java/io/reactivex/internal/operators/single/SingleError.java new file mode 100755 index 0000000..6a6e1ae --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleError.java @@ -0,0 +1,45 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; + +public final class SingleError extends Single { + + final Callable errorSupplier; + + public SingleError(Callable errorSupplier) { + this.errorSupplier = errorSupplier; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + Throwable error; + + try { + error = ObjectHelper.requireNonNull(errorSupplier.call(), "Callable returned null throwable. Null values are generally not allowed in 2.x operators and sources."); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + error = e; + } + + EmptyDisposable.error(error, observer); + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMap.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMap.java new file mode 100755 index 0000000..2913ff7 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMap.java @@ -0,0 +1,120 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.atomic.AtomicReference; + +public final class SingleFlatMap extends Single { + final SingleSource source; + + final Function> mapper; + + public SingleFlatMap(SingleSource source, Function> mapper) { + this.mapper = mapper; + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver downstream) { + source.subscribe(new SingleFlatMapCallback(downstream, mapper)); + } + + static final class SingleFlatMapCallback + extends AtomicReference + implements SingleObserver, Disposable { + private static final long serialVersionUID = 3258103020495908596L; + + final SingleObserver downstream; + + final Function> mapper; + + SingleFlatMapCallback(SingleObserver actual, + Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + SingleSource o; + + try { + o = ObjectHelper.requireNonNull(mapper.apply(value), "The single returned by the mapper is null"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + + if (!isDisposed()) { + o.subscribe(new FlatMapSingleObserver(this, downstream)); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + static final class FlatMapSingleObserver implements SingleObserver { + + final AtomicReference parent; + + final SingleObserver downstream; + + FlatMapSingleObserver(AtomicReference parent, SingleObserver downstream) { + this.parent = parent; + this.downstream = downstream; + } + + @Override + public void onSubscribe(final Disposable d) { + DisposableHelper.replace(parent, d); + } + + @Override + public void onSuccess(final R value) { + downstream.onSuccess(value); + } + + @Override + public void onError(final Throwable e) { + downstream.onError(e); + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapCompletable.java new file mode 100755 index 0000000..31b9ac2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapCompletable.java @@ -0,0 +1,105 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Maps the success value of the source SingleSource into a Completable. + * @param the value type of the source SingleSource + */ +public final class SingleFlatMapCompletable extends Completable { + + final SingleSource source; + + final Function mapper; + + public SingleFlatMapCompletable(SingleSource source, Function mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + FlatMapCompletableObserver parent = new FlatMapCompletableObserver(observer, mapper); + observer.onSubscribe(parent); + source.subscribe(parent); + } + + static final class FlatMapCompletableObserver + extends AtomicReference + implements SingleObserver, CompletableObserver, Disposable { + + private static final long serialVersionUID = -2177128922851101253L; + + final CompletableObserver downstream; + + final Function mapper; + + FlatMapCompletableObserver(CompletableObserver actual, + Function mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.replace(this, d); + } + + @Override + public void onSuccess(T value) { + CompletableSource cs; + + try { + cs = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null CompletableSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onError(ex); + return; + } + + if (!isDisposed()) { + cs.subscribe(this); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java new file mode 100755 index 0000000..f6f4c79 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableFlowable.java @@ -0,0 +1,290 @@ +/** + * 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.internal.operators.single; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicLong; + +import io.reactivex.annotations.Nullable; +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; + +/** + * Maps a success value into an Iterable and streams it back as a Flowable. + * + * @param the source value type + * @param the element type of the Iterable + */ +public final class SingleFlatMapIterableFlowable extends Flowable { + + final SingleSource source; + + final Function> mapper; + + public SingleFlatMapIterableFlowable(SingleSource source, + Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Subscriber s) { + source.subscribe(new FlatMapIterableObserver(s, mapper)); + } + + static final class FlatMapIterableObserver + extends BasicIntQueueSubscription + implements SingleObserver { + + private static final long serialVersionUID = -8938804753851907758L; + + final Subscriber downstream; + + final Function> mapper; + + final AtomicLong requested; + + Disposable upstream; + + volatile Iterator it; + + volatile boolean cancelled; + + boolean outputFused; + + FlatMapIterableObserver(Subscriber actual, + Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + this.requested = new AtomicLong(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + Iterator iterator; + boolean has; + try { + iterator = mapper.apply(value).iterator(); + + has = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + if (!has) { + downstream.onComplete(); + return; + } + + this.it = iterator; + drain(); + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + cancelled = true; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + Subscriber a = downstream; + Iterator iterator = this.it; + + if (outputFused && iterator != null) { + a.onNext(null); + a.onComplete(); + return; + } + + int missed = 1; + + for (;;) { + + if (iterator != null) { + long r = requested.get(); + long e = 0L; + + if (r == Long.MAX_VALUE) { + slowPath(a, iterator); + return; + } + + while (e != r) { + if (cancelled) { + return; + } + + R v; + + try { + v = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + a.onNext(v); + + if (cancelled) { + return; + } + + e++; + + boolean b; + + try { + b = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!b) { + a.onComplete(); + return; + } + } + + if (e != 0L) { + BackpressureHelper.produced(requested, e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + + if (iterator == null) { + iterator = it; + } + } + } + + void slowPath(Subscriber a, Iterator iterator) { + for (;;) { + if (cancelled) { + return; + } + + R v; + + try { + v = iterator.next(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + a.onNext(v); + + if (cancelled) { + return; + } + + boolean b; + + try { + b = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!b) { + a.onComplete(); + return; + } + } + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Override + public void clear() { + it = null; + } + + @Override + public boolean isEmpty() { + return it == null; + } + + @Nullable + @Override + public R poll() throws Exception { + Iterator iterator = it; + + if (iterator != null) { + R v = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null value"); + if (!iterator.hasNext()) { + it = null; + } + return v; + } + return null; + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java new file mode 100755 index 0000000..b3f822c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapIterableObservable.java @@ -0,0 +1,200 @@ +/** + * 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.internal.operators.single; + +import java.util.Iterator; + +import io.reactivex.*; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.BasicIntQueueDisposable; + +/** + * Maps a success value into an Iterable and streams it back as an Observable. + * + * @param the source value type + * @param the element type of the Iterable + */ +public final class SingleFlatMapIterableObservable extends Observable { + + final SingleSource source; + + final Function> mapper; + + public SingleFlatMapIterableObservable(SingleSource source, + Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new FlatMapIterableObserver(observer, mapper)); + } + + static final class FlatMapIterableObserver + extends BasicIntQueueDisposable + implements SingleObserver { + + private static final long serialVersionUID = -8938804753851907758L; + + final Observer downstream; + + final Function> mapper; + + Disposable upstream; + + volatile Iterator it; + + volatile boolean cancelled; + + boolean outputFused; + + FlatMapIterableObserver(Observer actual, + Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + Observer a = downstream; + Iterator iterator; + boolean has; + try { + iterator = mapper.apply(value).iterator(); + + has = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + if (!has) { + a.onComplete(); + return; + } + + if (outputFused) { + it = iterator; + a.onNext(null); + a.onComplete(); + } else { + for (;;) { + if (cancelled) { + return; + } + + R v; + + try { + v = iterator.next(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + a.onNext(v); + + if (cancelled) { + return; + } + + boolean b; + + try { + b = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + a.onError(ex); + return; + } + + if (!b) { + a.onComplete(); + return; + } + } + } + } + + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + downstream.onError(e); + } + + @Override + public void dispose() { + cancelled = true; + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Override + public void clear() { + it = null; + } + + @Override + public boolean isEmpty() { + return it == null; + } + + @Nullable + @Override + public R poll() throws Exception { + Iterator iterator = it; + + if (iterator != null) { + R v = ObjectHelper.requireNonNull(iterator.next(), "The iterator returned a null value"); + if (!iterator.hasNext()) { + it = null; + } + return v; + } + return null; + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapMaybe.java new file mode 100755 index 0000000..dfe6b60 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapMaybe.java @@ -0,0 +1,130 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.MaybeSource; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import java.util.concurrent.atomic.AtomicReference; + +public final class SingleFlatMapMaybe extends Maybe { + + final SingleSource source; + + final Function> mapper; + + public SingleFlatMapMaybe(SingleSource source, Function> mapper) { + this.mapper = mapper; + this.source = source; + } + + @Override + protected void subscribeActual(MaybeObserver downstream) { + source.subscribe(new FlatMapSingleObserver(downstream, mapper)); + } + + static final class FlatMapSingleObserver + extends AtomicReference + implements SingleObserver, Disposable { + + private static final long serialVersionUID = -5843758257109742742L; + + final MaybeObserver downstream; + + final Function> mapper; + + FlatMapSingleObserver(MaybeObserver actual, Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + MaybeSource ms; + + try { + ms = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper returned a null MaybeSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + onError(ex); + return; + } + + if (!isDisposed()) { + ms.subscribe(new FlatMapMaybeObserver(this, downstream)); + } + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } + + static final class FlatMapMaybeObserver implements MaybeObserver { + + final AtomicReference parent; + + final MaybeObserver downstream; + + FlatMapMaybeObserver(AtomicReference parent, MaybeObserver downstream) { + this.parent = parent; + this.downstream = downstream; + } + + @Override + public void onSubscribe(final Disposable d) { + DisposableHelper.replace(parent, d); + } + + @Override + public void onSuccess(final R value) { + downstream.onSuccess(value); + } + + @Override + public void onError(final Throwable e) { + downstream.onError(e); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapPublisher.java b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapPublisher.java new file mode 100755 index 0000000..be090bd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFlatMapPublisher.java @@ -0,0 +1,137 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import io.reactivex.Flowable; +import io.reactivex.FlowableSubscriber; +import io.reactivex.Scheduler; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +/** + * A Flowable that emits items based on applying a specified function to the item emitted by the + * source Single, where that function returns a Publisher. + *

+ * + *

+ *
Backpressure:
+ *
The returned {@code Flowable} honors the backpressure of the downstream consumer + * and the {@code Publisher} returned by the mapper function is expected to honor it as well.
+ *
Scheduler:
+ *
{@code flatMapPublisher} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the source value type + * @param the result value type + * + * @see ReactiveX operators documentation: FlatMap + * @since 2.1.15 + */ +public final class SingleFlatMapPublisher extends Flowable { + + final SingleSource source; + final Function> mapper; + + public SingleFlatMapPublisher(SingleSource source, + Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Subscriber downstream) { + source.subscribe(new SingleFlatMapPublisherObserver(downstream, mapper)); + } + + static final class SingleFlatMapPublisherObserver extends AtomicLong + implements SingleObserver, FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 7759721921468635667L; + + final Subscriber downstream; + final Function> mapper; + final AtomicReference parent; + Disposable disposable; + + SingleFlatMapPublisherObserver(Subscriber actual, + Function> mapper) { + this.downstream = actual; + this.mapper = mapper; + this.parent = new AtomicReference(); + } + + @Override + public void onSubscribe(Disposable d) { + this.disposable = d; + downstream.onSubscribe(this); + } + + @Override + public void onSuccess(S value) { + Publisher f; + try { + f = ObjectHelper.requireNonNull(mapper.apply(value), "the mapper returned a null Publisher"); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + downstream.onError(e); + return; + } + f.subscribe(this); + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.deferredSetOnce(parent, this, s); + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onComplete() { + downstream.onComplete(); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(parent, this, n); + } + + @Override + public void cancel() { + disposable.dispose(); + SubscriptionHelper.cancel(parent); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFromCallable.java b/src/main/java/io/reactivex/internal/operators/single/SingleFromCallable.java new file mode 100755 index 0000000..2b5ff36 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFromCallable.java @@ -0,0 +1,59 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.Disposables; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleFromCallable extends Single { + + final Callable callable; + + public SingleFromCallable(Callable callable) { + this.callable = callable; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + Disposable d = Disposables.empty(); + observer.onSubscribe(d); + + if (d.isDisposed()) { + return; + } + T value; + + try { + value = ObjectHelper.requireNonNull(callable.call(), "The callable returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (!d.isDisposed()) { + observer.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + return; + } + + if (!d.isDisposed()) { + observer.onSuccess(value); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFromPublisher.java b/src/main/java/io/reactivex/internal/operators/single/SingleFromPublisher.java new file mode 100755 index 0000000..5709f3c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFromPublisher.java @@ -0,0 +1,116 @@ +/** + * 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.internal.operators.single; + +import java.util.NoSuchElementException; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleFromPublisher extends Single { + + final Publisher publisher; + + public SingleFromPublisher(Publisher publisher) { + this.publisher = publisher; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + publisher.subscribe(new ToSingleObserver(observer)); + } + + static final class ToSingleObserver implements FlowableSubscriber, Disposable { + final SingleObserver downstream; + + Subscription upstream; + + T value; + + boolean done; + + volatile boolean disposed; + + ToSingleObserver(SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (value != null) { + upstream.cancel(); + done = true; + this.value = null; + downstream.onError(new IndexOutOfBoundsException("Too many elements in the Publisher")); + } else { + value = t; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + this.value = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + T v = this.value; + this.value = null; + if (v == null) { + downstream.onError(new NoSuchElementException("The source Publisher is empty")); + } else { + downstream.onSuccess(v); + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void dispose() { + disposed = true; + upstream.cancel(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleFromUnsafeSource.java b/src/main/java/io/reactivex/internal/operators/single/SingleFromUnsafeSource.java new file mode 100755 index 0000000..c8ce55d --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleFromUnsafeSource.java @@ -0,0 +1,29 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; + +public final class SingleFromUnsafeSource extends Single { + final SingleSource source; + + public SingleFromUnsafeSource(SingleSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(observer); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleHide.java b/src/main/java/io/reactivex/internal/operators/single/SingleHide.java new file mode 100755 index 0000000..7ec7390 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleHide.java @@ -0,0 +1,72 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class SingleHide extends Single { + + final SingleSource source; + + public SingleHide(SingleSource source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new HideSingleObserver(observer)); + } + + static final class HideSingleObserver implements SingleObserver, Disposable { + + final SingleObserver downstream; + + Disposable upstream; + + HideSingleObserver(SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java b/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java new file mode 100755 index 0000000..7770fd3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleInternalHelper.java @@ -0,0 +1,118 @@ +/** + * 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.internal.operators.single; + +import java.util.*; +import java.util.concurrent.Callable; + +import org.reactivestreams.Publisher; + +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.functions.Function; + +/** + * Helper utility class to support Single with inner classes. + */ +public final class SingleInternalHelper { + + /** Utility class. */ + private SingleInternalHelper() { + throw new IllegalStateException("No instances!"); + } + + enum NoSuchElementCallable implements Callable { + INSTANCE; + + @Override + public NoSuchElementException call() throws Exception { + return new NoSuchElementException(); + } + } + + public static Callable emptyThrower() { + return NoSuchElementCallable.INSTANCE; + } + + @SuppressWarnings("rawtypes") + enum ToFlowable implements Function { + INSTANCE; + @SuppressWarnings("unchecked") + @Override + public Publisher apply(SingleSource v) { + return new SingleToFlowable(v); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Function, Publisher> toFlowable() { + return (Function)ToFlowable.INSTANCE; + } + + static final class ToFlowableIterator implements Iterator> { + private final Iterator> sit; + + ToFlowableIterator(Iterator> sit) { + this.sit = sit; + } + + @Override + public boolean hasNext() { + return sit.hasNext(); + } + + @Override + public Flowable next() { + return new SingleToFlowable(sit.next()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + static final class ToFlowableIterable implements Iterable> { + + private final Iterable> sources; + + ToFlowableIterable(Iterable> sources) { + this.sources = sources; + } + + @Override + public Iterator> iterator() { + return new ToFlowableIterator(sources.iterator()); + } + } + + public static Iterable> iterableToFlowable(final Iterable> sources) { + return new ToFlowableIterable(sources); + } + + @SuppressWarnings("rawtypes") + enum ToObservable implements Function { + INSTANCE; + @SuppressWarnings("unchecked") + @Override + public Observable apply(SingleSource v) { + return new SingleToObservable(v); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Function, Observable> toObservable() { + return (Function)ToObservable.INSTANCE; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleJust.java b/src/main/java/io/reactivex/internal/operators/single/SingleJust.java new file mode 100755 index 0000000..5e3dfdc --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleJust.java @@ -0,0 +1,33 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposables; + +public final class SingleJust extends Single { + + final T value; + + public SingleJust(T value) { + this.value = value; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + observer.onSubscribe(Disposables.disposed()); + observer.onSuccess(value); + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleLift.java b/src/main/java/io/reactivex/internal/operators/single/SingleLift.java new file mode 100755 index 0000000..3d55453 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleLift.java @@ -0,0 +1,47 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; + +public final class SingleLift extends Single { + + final SingleSource source; + + final SingleOperator onLift; + + public SingleLift(SingleSource source, SingleOperator onLift) { + this.source = source; + this.onLift = onLift; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + SingleObserver sr; + + try { + sr = ObjectHelper.requireNonNull(onLift.apply(observer), "The onLift returned a null SingleObserver"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + source.subscribe(sr); + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleMap.java b/src/main/java/io/reactivex/internal/operators/single/SingleMap.java new file mode 100755 index 0000000..25ddcdf --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleMap.java @@ -0,0 +1,72 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.ObjectHelper; + +public final class SingleMap extends Single { + final SingleSource source; + + final Function mapper; + + public SingleMap(SingleSource source, Function mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(final SingleObserver t) { + source.subscribe(new MapSingleObserver(t, mapper)); + } + + static final class MapSingleObserver implements SingleObserver { + + final SingleObserver t; + + final Function mapper; + + MapSingleObserver(SingleObserver t, Function mapper) { + this.t = t; + this.mapper = mapper; + } + + @Override + public void onSubscribe(Disposable d) { + t.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + R v; + try { + v = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper function returned a null value."); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + onError(e); + return; + } + + t.onSuccess(v); + } + + @Override + public void onError(Throwable e) { + t.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleMaterialize.java b/src/main/java/io/reactivex/internal/operators/single/SingleMaterialize.java new file mode 100755 index 0000000..e22b648 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleMaterialize.java @@ -0,0 +1,40 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.internal.operators.mixed.MaterializeSingleObserver; + +/** + * Turn the signal types of a Single source into a single Notification of + * equal kind. + * + * @param the element type of the source + * @since 2.2.4 - experimental + */ +@Experimental +public final class SingleMaterialize extends Single> { + + final Single source; + + public SingleMaterialize(Single source) { + this.source = source; + } + + @Override + protected void subscribeActual(SingleObserver> observer) { + source.subscribe(new MaterializeSingleObserver(observer)); + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleNever.java b/src/main/java/io/reactivex/internal/operators/single/SingleNever.java new file mode 100755 index 0000000..a3c46eb --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleNever.java @@ -0,0 +1,30 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.internal.disposables.EmptyDisposable; + +public final class SingleNever extends Single { + public static final Single INSTANCE = new SingleNever(); + + private SingleNever() { + } + + @Override + protected void subscribeActual(SingleObserver observer) { + observer.onSubscribe(EmptyDisposable.NEVER); + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleObserveOn.java b/src/main/java/io/reactivex/internal/operators/single/SingleObserveOn.java new file mode 100755 index 0000000..7c8a6c1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleObserveOn.java @@ -0,0 +1,95 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +public final class SingleObserveOn extends Single { + + final SingleSource source; + + final Scheduler scheduler; + + public SingleObserveOn(SingleSource source, Scheduler scheduler) { + this.source = source; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + source.subscribe(new ObserveOnSingleObserver(observer, scheduler)); + } + + static final class ObserveOnSingleObserver extends AtomicReference + implements SingleObserver, Disposable, Runnable { + private static final long serialVersionUID = 3528003840217436037L; + + final SingleObserver downstream; + + final Scheduler scheduler; + + T value; + Throwable error; + + ObserveOnSingleObserver(SingleObserver actual, Scheduler scheduler) { + this.downstream = actual; + this.scheduler = scheduler; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + this.value = value; + Disposable d = scheduler.scheduleDirect(this); + DisposableHelper.replace(this, d); + } + + @Override + public void onError(Throwable e) { + this.error = e; + Disposable d = scheduler.scheduleDirect(this); + DisposableHelper.replace(this, d); + } + + @Override + public void run() { + Throwable ex = error; + if (ex != null) { + downstream.onError(ex); + } else { + downstream.onSuccess(value); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java b/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java new file mode 100755 index 0000000..0ae695a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleOnErrorReturn.java @@ -0,0 +1,86 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; + +public final class SingleOnErrorReturn extends Single { + final SingleSource source; + + final Function valueSupplier; + + final T value; + + public SingleOnErrorReturn(SingleSource source, + Function valueSupplier, T value) { + this.source = source; + this.valueSupplier = valueSupplier; + this.value = value; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + source.subscribe(new OnErrorReturn(observer)); + } + + final class OnErrorReturn implements SingleObserver { + + private final SingleObserver observer; + + OnErrorReturn(SingleObserver observer) { + this.observer = observer; + } + + @Override + public void onError(Throwable e) { + T v; + + if (valueSupplier != null) { + try { + v = valueSupplier.apply(e); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + observer.onError(new CompositeException(e, ex)); + return; + } + } else { + v = value; + } + + if (v == null) { + NullPointerException npe = new NullPointerException("Value supplied was null"); + npe.initCause(e); + observer.onError(npe); + return; + } + + observer.onSuccess(v); + } + + @Override + public void onSubscribe(Disposable d) { + observer.onSubscribe(d); + } + + @Override + public void onSuccess(T value) { + observer.onSuccess(value); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleResumeNext.java b/src/main/java/io/reactivex/internal/operators/single/SingleResumeNext.java new file mode 100755 index 0000000..325365f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleResumeNext.java @@ -0,0 +1,93 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.ResumeSingleObserver; + +public final class SingleResumeNext extends Single { + final SingleSource source; + + final Function> nextFunction; + + public SingleResumeNext(SingleSource source, + Function> nextFunction) { + this.source = source; + this.nextFunction = nextFunction; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + source.subscribe(new ResumeMainSingleObserver(observer, nextFunction)); + } + + static final class ResumeMainSingleObserver extends AtomicReference + implements SingleObserver, Disposable { + private static final long serialVersionUID = -5314538511045349925L; + + final SingleObserver downstream; + + final Function> nextFunction; + + ResumeMainSingleObserver(SingleObserver actual, + Function> nextFunction) { + this.downstream = actual; + this.nextFunction = nextFunction; + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + SingleSource source; + + try { + source = ObjectHelper.requireNonNull(nextFunction.apply(e), "The nextFunction returned a null SingleSource."); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(new CompositeException(e, ex)); + return; + } + + source.subscribe(new ResumeSingleObserver(this, downstream)); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleSubscribeOn.java b/src/main/java/io/reactivex/internal/operators/single/SingleSubscribeOn.java new file mode 100755 index 0000000..9b29e62 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleSubscribeOn.java @@ -0,0 +1,93 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; + +public final class SingleSubscribeOn extends Single { + final SingleSource source; + + final Scheduler scheduler; + + public SingleSubscribeOn(SingleSource source, Scheduler scheduler) { + this.source = source; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + final SubscribeOnObserver parent = new SubscribeOnObserver(observer, source); + observer.onSubscribe(parent); + + Disposable f = scheduler.scheduleDirect(parent); + + parent.task.replace(f); + + } + + static final class SubscribeOnObserver + extends AtomicReference + implements SingleObserver, Disposable, Runnable { + + private static final long serialVersionUID = 7000911171163930287L; + + final SingleObserver downstream; + + final SequentialDisposable task; + + final SingleSource source; + + SubscribeOnObserver(SingleObserver actual, SingleSource source) { + this.downstream = actual; + this.source = source; + this.task = new SequentialDisposable(); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + task.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void run() { + source.subscribe(this); + } + } + +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java b/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java new file mode 100755 index 0000000..8db5e40 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleTakeUntil.java @@ -0,0 +1,167 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Signals the events of the source Single or signals a CancellationException if the + * other Publisher signalled first. + * @param the main value type + * @param the other value type + */ +public final class SingleTakeUntil extends Single { + + final SingleSource source; + + final Publisher other; + + public SingleTakeUntil(SingleSource source, Publisher other) { + this.source = source; + this.other = other; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + TakeUntilMainObserver parent = new TakeUntilMainObserver(observer); + observer.onSubscribe(parent); + + other.subscribe(parent.other); + + source.subscribe(parent); + } + + static final class TakeUntilMainObserver + extends AtomicReference + implements SingleObserver, Disposable { + + private static final long serialVersionUID = -622603812305745221L; + + final SingleObserver downstream; + + final TakeUntilOtherSubscriber other; + + TakeUntilMainObserver(SingleObserver downstream) { + this.downstream = downstream; + this.other = new TakeUntilOtherSubscriber(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + other.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + other.dispose(); + + Disposable a = getAndSet(DisposableHelper.DISPOSED); + if (a != DisposableHelper.DISPOSED) { + downstream.onSuccess(value); + } + } + + @Override + public void onError(Throwable e) { + other.dispose(); + + Disposable a = get(); + if (a != DisposableHelper.DISPOSED) { + a = getAndSet(DisposableHelper.DISPOSED); + if (a != DisposableHelper.DISPOSED) { + downstream.onError(e); + return; + } + } + RxJavaPlugins.onError(e); + } + + void otherError(Throwable e) { + Disposable a = get(); + if (a != DisposableHelper.DISPOSED) { + a = getAndSet(DisposableHelper.DISPOSED); + if (a != DisposableHelper.DISPOSED) { + if (a != null) { + a.dispose(); + } + downstream.onError(e); + return; + } + } + RxJavaPlugins.onError(e); + } + } + + static final class TakeUntilOtherSubscriber + extends AtomicReference + implements FlowableSubscriber { + + private static final long serialVersionUID = 5170026210238877381L; + + final TakeUntilMainObserver parent; + + TakeUntilOtherSubscriber(TakeUntilMainObserver parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(Object t) { + if (SubscriptionHelper.cancel(this)) { + parent.otherError(new CancellationException()); + } + } + + @Override + public void onError(Throwable t) { + parent.otherError(t); + } + + @Override + public void onComplete() { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + parent.otherError(new CancellationException()); + } + } + + public void dispose() { + SubscriptionHelper.cancel(this); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java b/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java new file mode 100755 index 0000000..ea4212f --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleTimeout.java @@ -0,0 +1,170 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.plugins.RxJavaPlugins; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + +public final class SingleTimeout extends Single { + + final SingleSource source; + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + final SingleSource other; + + public SingleTimeout(SingleSource source, long timeout, TimeUnit unit, Scheduler scheduler, + SingleSource other) { + this.source = source; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.other = other; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + TimeoutMainObserver parent = new TimeoutMainObserver(observer, other, timeout, unit); + observer.onSubscribe(parent); + + DisposableHelper.replace(parent.task, scheduler.scheduleDirect(parent, timeout, unit)); + + source.subscribe(parent); + } + + static final class TimeoutMainObserver extends AtomicReference + implements SingleObserver, Runnable, Disposable { + + private static final long serialVersionUID = 37497744973048446L; + + final SingleObserver downstream; + + final AtomicReference task; + + final TimeoutFallbackObserver fallback; + + SingleSource other; + + final long timeout; + + final TimeUnit unit; + + static final class TimeoutFallbackObserver extends AtomicReference + implements SingleObserver { + + private static final long serialVersionUID = 2071387740092105509L; + final SingleObserver downstream; + + TimeoutFallbackObserver(SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + downstream.onSuccess(t); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } + + TimeoutMainObserver(SingleObserver actual, SingleSource other, long timeout, TimeUnit unit) { + this.downstream = actual; + this.other = other; + this.timeout = timeout; + this.unit = unit; + this.task = new AtomicReference(); + if (other != null) { + this.fallback = new TimeoutFallbackObserver(actual); + } else { + this.fallback = null; + } + } + + @Override + public void run() { + Disposable d = get(); + if (d != DisposableHelper.DISPOSED && compareAndSet(d, DisposableHelper.DISPOSED)) { + if (d != null) { + d.dispose(); + } + SingleSource other = this.other; + if (other == null) { + downstream.onError(new TimeoutException(timeoutMessage(timeout, unit))); + } else { + this.other = null; + other.subscribe(fallback); + } + } + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T t) { + Disposable d = get(); + if (d != DisposableHelper.DISPOSED && compareAndSet(d, DisposableHelper.DISPOSED)) { + DisposableHelper.dispose(task); + downstream.onSuccess(t); + } + } + + @Override + public void onError(Throwable e) { + Disposable d = get(); + if (d != DisposableHelper.DISPOSED && compareAndSet(d, DisposableHelper.DISPOSED)) { + DisposableHelper.dispose(task); + downstream.onError(e); + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + DisposableHelper.dispose(task); + if (fallback != null) { + DisposableHelper.dispose(fallback); + } + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleTimer.java b/src/main/java/io/reactivex/internal/operators/single/SingleTimer.java new file mode 100755 index 0000000..68b4c9c --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleTimer.java @@ -0,0 +1,73 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Signals a {@code 0L} after the specified delay. + */ +public final class SingleTimer extends Single { + + final long delay; + final TimeUnit unit; + final Scheduler scheduler; + + public SingleTimer(long delay, TimeUnit unit, Scheduler scheduler) { + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + TimerDisposable parent = new TimerDisposable(observer); + observer.onSubscribe(parent); + parent.setFuture(scheduler.scheduleDirect(parent, delay, unit)); + } + + static final class TimerDisposable extends AtomicReference implements Disposable, Runnable { + + private static final long serialVersionUID = 8465401857522493082L; + final SingleObserver downstream; + + TimerDisposable(final SingleObserver downstream) { + this.downstream = downstream; + } + + @Override + public void run() { + downstream.onSuccess(0L); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + void setFuture(Disposable d) { + DisposableHelper.replace(this, d); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java b/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java new file mode 100755 index 0000000..de2b8a9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleToFlowable.java @@ -0,0 +1,76 @@ +/** + * 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.internal.operators.single; + +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.DeferredScalarSubscription; + +/** + * Wraps a Single and exposes it as a Flowable. + * + * @param the value type + */ +public final class SingleToFlowable extends Flowable { + + final SingleSource source; + + public SingleToFlowable(SingleSource source) { + this.source = source; + } + + @Override + public void subscribeActual(final Subscriber s) { + source.subscribe(new SingleToFlowableObserver(s)); + } + + static final class SingleToFlowableObserver extends DeferredScalarSubscription + implements SingleObserver { + + private static final long serialVersionUID = 187782011903685568L; + + Disposable upstream; + + SingleToFlowableObserver(Subscriber downstream) { + super(downstream); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + complete(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + + @Override + public void cancel() { + super.cancel(); + upstream.dispose(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java b/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java new file mode 100755 index 0000000..2c4126a --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java @@ -0,0 +1,87 @@ +/** + * 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.internal.operators.single; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.observers.DeferredScalarDisposable; + +/** + * Wraps a Single and exposes it as an Observable. + * + * @param the value type + */ +public final class SingleToObservable extends Observable { + + final SingleSource source; + + public SingleToObservable(SingleSource source) { + this.source = source; + } + + @Override + public void subscribeActual(final Observer observer) { + source.subscribe(create(observer)); + } + + /** + * Creates a {@link SingleObserver} wrapper around a {@link Observer}. + *

History: 2.0.1 - experimental + * @param the value type + * @param downstream the downstream {@code Observer} to talk to + * @return the new SingleObserver instance + * @since 2.2 + */ + public static SingleObserver create(Observer downstream) { + return new SingleToObservableObserver(downstream); + } + + static final class SingleToObservableObserver + extends DeferredScalarDisposable + implements SingleObserver { + + private static final long serialVersionUID = 3786543492451018833L; + Disposable upstream; + + SingleToObservableObserver(Observer downstream) { + super(downstream); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + complete(value); + } + + @Override + public void onError(Throwable e) { + error(e); + } + + @Override + public void dispose() { + super.dispose(); + upstream.dispose(); + } + + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleUnsubscribeOn.java b/src/main/java/io/reactivex/internal/operators/single/SingleUnsubscribeOn.java new file mode 100755 index 0000000..dc7962b --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleUnsubscribeOn.java @@ -0,0 +1,95 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * Makes sure a dispose() call from downstream happens on the specified scheduler. + * + * @param the value type + */ +public final class SingleUnsubscribeOn extends Single { + + final SingleSource source; + + final Scheduler scheduler; + + public SingleUnsubscribeOn(SingleSource source, Scheduler scheduler) { + this.source = source; + this.scheduler = scheduler; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + source.subscribe(new UnsubscribeOnSingleObserver(observer, scheduler)); + } + + static final class UnsubscribeOnSingleObserver extends AtomicReference + implements SingleObserver, Disposable, Runnable { + + private static final long serialVersionUID = 3256698449646456986L; + + final SingleObserver downstream; + + final Scheduler scheduler; + + Disposable ds; + + UnsubscribeOnSingleObserver(SingleObserver actual, Scheduler scheduler) { + this.downstream = actual; + this.scheduler = scheduler; + } + + @Override + public void dispose() { + Disposable d = getAndSet(DisposableHelper.DISPOSED); + if (d != DisposableHelper.DISPOSED) { + this.ds = d; + scheduler.scheduleDirect(this); + } + } + + @Override + public void run() { + ds.dispose(); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.setOnce(this, d)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onSuccess(T value) { + downstream.onSuccess(value); + } + + @Override + public void onError(Throwable e) { + downstream.onError(e); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleUsing.java b/src/main/java/io/reactivex/internal/operators/single/SingleUsing.java new file mode 100755 index 0000000..0352bdd --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleUsing.java @@ -0,0 +1,195 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleUsing extends Single { + + final Callable resourceSupplier; + final Function> singleFunction; + final Consumer disposer; + final boolean eager; + + public SingleUsing(Callable resourceSupplier, + Function> singleFunction, + Consumer disposer, + boolean eager) { + this.resourceSupplier = resourceSupplier; + this.singleFunction = singleFunction; + this.disposer = disposer; + this.eager = eager; + } + + @Override + protected void subscribeActual(final SingleObserver observer) { + + final U resource; // NOPMD + + try { + resource = resourceSupplier.call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + SingleSource source; + + try { + source = ObjectHelper.requireNonNull(singleFunction.apply(resource), "The singleFunction returned a null SingleSource"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + if (eager) { + try { + disposer.accept(resource); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + ex = new CompositeException(ex, exc); + } + } + EmptyDisposable.error(ex, observer); + if (!eager) { + try { + disposer.accept(resource); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + RxJavaPlugins.onError(exc); + } + } + return; + } + + source.subscribe(new UsingSingleObserver(observer, resource, eager, disposer)); + } + + static final class UsingSingleObserver extends + AtomicReference implements SingleObserver, Disposable { + + private static final long serialVersionUID = -5331524057054083935L; + + final SingleObserver downstream; + + final Consumer disposer; + + final boolean eager; + + Disposable upstream; + + UsingSingleObserver(SingleObserver actual, U resource, boolean eager, + Consumer disposer) { + super(resource); + this.downstream = actual; + this.eager = eager; + this.disposer = disposer; + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + disposeAfter(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onSubscribe(Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onSuccess(T value) { + upstream = DisposableHelper.DISPOSED; + + if (eager) { + Object u = getAndSet(this); + if (u != this) { + try { + disposer.accept((U)u); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + } else { + return; + } + } + + downstream.onSuccess(value); + + if (!eager) { + disposeAfter(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable e) { + upstream = DisposableHelper.DISPOSED; + + if (eager) { + Object u = getAndSet(this); + if (u != this) { + try { + disposer.accept((U)u); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + e = new CompositeException(e, ex); + } + } else { + return; + } + } + + downstream.onError(e); + + if (!eager) { + disposeAfter(); + } + } + + @SuppressWarnings("unchecked") + void disposeAfter() { + Object u = getAndSet(this); + if (u != this) { + try { + disposer.accept((U)u); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java b/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java new file mode 100755 index 0000000..31fd470 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleZipArray.java @@ -0,0 +1,185 @@ +/** + * 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.internal.operators.single; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class SingleZipArray extends Single { + + final SingleSource[] sources; + + final Function zipper; + + public SingleZipArray(SingleSource[] sources, Function zipper) { + this.sources = sources; + this.zipper = zipper; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + SingleSource[] sources = this.sources; + int n = sources.length; + + if (n == 1) { + sources[0].subscribe(new SingleMap.MapSingleObserver(observer, new SingletonArrayFunc())); + return; + } + + ZipCoordinator parent = new ZipCoordinator(observer, n, zipper); + + observer.onSubscribe(parent); + + for (int i = 0; i < n; i++) { + if (parent.isDisposed()) { + return; + } + + SingleSource source = sources[i]; + + if (source == null) { + parent.innerError(new NullPointerException("One of the sources is null"), i); + return; + } + + source.subscribe(parent.observers[i]); + } + } + + static final class ZipCoordinator extends AtomicInteger implements Disposable { + + private static final long serialVersionUID = -5556924161382950569L; + + final SingleObserver downstream; + + final Function zipper; + + final ZipSingleObserver[] observers; + + final Object[] values; + + @SuppressWarnings("unchecked") + ZipCoordinator(SingleObserver observer, int n, Function zipper) { + super(n); + this.downstream = observer; + this.zipper = zipper; + ZipSingleObserver[] o = new ZipSingleObserver[n]; + for (int i = 0; i < n; i++) { + o[i] = new ZipSingleObserver(this, i); + } + this.observers = o; + this.values = new Object[n]; + } + + @Override + public boolean isDisposed() { + return get() <= 0; + } + + @Override + public void dispose() { + if (getAndSet(0) > 0) { + for (ZipSingleObserver d : observers) { + d.dispose(); + } + } + } + + void innerSuccess(T value, int index) { + values[index] = value; + if (decrementAndGet() == 0) { + R v; + + try { + v = ObjectHelper.requireNonNull(zipper.apply(values), "The zipper returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(v); + } + } + + void disposeExcept(int index) { + ZipSingleObserver[] observers = this.observers; + int n = observers.length; + for (int i = 0; i < index; i++) { + observers[i].dispose(); + } + for (int i = index + 1; i < n; i++) { + observers[i].dispose(); + } + } + + void innerError(Throwable ex, int index) { + if (getAndSet(0) > 0) { + disposeExcept(index); + downstream.onError(ex); + } else { + RxJavaPlugins.onError(ex); + } + } + } + + static final class ZipSingleObserver + extends AtomicReference + implements SingleObserver { + + private static final long serialVersionUID = 3323743579927613702L; + + final ZipCoordinator parent; + + final int index; + + ZipSingleObserver(ZipCoordinator parent, int index) { + this.parent = parent; + this.index = index; + } + + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(this, d); + } + + @Override + public void onSuccess(T value) { + parent.innerSuccess(value, index); + } + + @Override + public void onError(Throwable e) { + parent.innerError(e, index); + } + } + + final class SingletonArrayFunc implements Function { + @Override + public R apply(T t) throws Exception { + return ObjectHelper.requireNonNull(zipper.apply(new Object[] { t }), "The zipper returned a null value"); + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleZipIterable.java b/src/main/java/io/reactivex/internal/operators/single/SingleZipIterable.java new file mode 100755 index 0000000..6936642 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/single/SingleZipIterable.java @@ -0,0 +1,88 @@ +/** + * 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.internal.operators.single; + +import java.util.*; + +import io.reactivex.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.single.SingleZipArray.ZipCoordinator; + +public final class SingleZipIterable extends Single { + + final Iterable> sources; + + final Function zipper; + + public SingleZipIterable(Iterable> sources, Function zipper) { + this.sources = sources; + this.zipper = zipper; + } + + @Override + protected void subscribeActual(SingleObserver observer) { + @SuppressWarnings("unchecked") + SingleSource[] a = new SingleSource[8]; + int n = 0; + + try { + for (SingleSource source : sources) { + if (source == null) { + EmptyDisposable.error(new NullPointerException("One of the sources is null"), observer); + return; + } + if (n == a.length) { + a = Arrays.copyOf(a, n + (n >> 2)); + } + a[n++] = source; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + if (n == 0) { + EmptyDisposable.error(new NoSuchElementException(), observer); + return; + } + + if (n == 1) { + a[0].subscribe(new SingleMap.MapSingleObserver(observer, new SingletonArrayFunc())); + return; + } + + ZipCoordinator parent = new ZipCoordinator(observer, n, zipper); + + observer.onSubscribe(parent); + + for (int i = 0; i < n; i++) { + if (parent.isDisposed()) { + return; + } + + a[i].subscribe(parent.observers[i]); + } + } + + final class SingletonArrayFunc implements Function { + @Override + public R apply(T t) throws Exception { + return ObjectHelper.requireNonNull(zipper.apply(new Object[] { t }), "The zipper returned a null value"); + } + } +} diff --git a/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java b/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java new file mode 100755 index 0000000..7d78a00 --- /dev/null +++ b/src/main/java/io/reactivex/internal/queue/MpscLinkedQueue.java @@ -0,0 +1,189 @@ +/** + * 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. + */ + +/* + * The code was inspired by the similarly named JCTools class: + * https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/atomic + */ + +package io.reactivex.internal.queue; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.fuseable.SimplePlainQueue; + +/** + * A multi-producer single consumer unbounded queue. + * @param the contained value type + */ +public final class MpscLinkedQueue implements SimplePlainQueue { + private final AtomicReference> producerNode; + private final AtomicReference> consumerNode; + + public MpscLinkedQueue() { + producerNode = new AtomicReference>(); + consumerNode = new AtomicReference>(); + LinkedQueueNode node = new LinkedQueueNode(); + spConsumerNode(node); + xchgProducerNode(node); // this ensures correct construction: StoreLoad + } + + /** + * {@inheritDoc}
+ *

+ * IMPLEMENTATION NOTES:
+ * Offer is allowed from multiple threads.
+ * Offer allocates a new node and: + *

    + *
  1. Swaps it atomically with current producer node (only one producer 'wins') + *
  2. Sets the new node as the node following from the swapped producer node + *
+ * This works because each producer is guaranteed to 'plant' a new node and link the old node. No 2 producers can + * get the same producer node as part of XCHG guarantee. + * + * @see java.util.Queue#offer(Object) + */ + @Override + public boolean offer(final T e) { + if (null == e) { + throw new NullPointerException("Null is not a valid element"); + } + final LinkedQueueNode nextNode = new LinkedQueueNode(e); + final LinkedQueueNode prevProducerNode = xchgProducerNode(nextNode); + // Should a producer thread get interrupted here the chain WILL be broken until that thread is resumed + // and completes the store in prev.next. + prevProducerNode.soNext(nextNode); // StoreStore + return true; + } + + /** + * {@inheritDoc}
+ *

+ * IMPLEMENTATION NOTES:
+ * Poll is allowed from a SINGLE thread.
+ * Poll reads the next node from the consumerNode and: + *

    + *
  1. If it is null, the queue is assumed empty (though it might not be). + *
  2. If it is not null set it as the consumer node and return it's now evacuated value. + *
+ * This means the consumerNode.value is always null, which is also the starting point for the queue. Because null + * values are not allowed to be offered this is the only node with it's value set to null at any one time. + * + * @see java.util.Queue#poll() + */ + @Nullable + @Override + public T poll() { + LinkedQueueNode currConsumerNode = lpConsumerNode(); // don't load twice, it's alright + LinkedQueueNode nextNode = currConsumerNode.lvNext(); + if (nextNode != null) { + // we have to null out the value because we are going to hang on to the node + final T nextValue = nextNode.getAndNullValue(); + spConsumerNode(nextNode); + return nextValue; + } + else if (currConsumerNode != lvProducerNode()) { + // spin, we are no longer wait free + while ((nextNode = currConsumerNode.lvNext()) == null) { } // NOPMD + // got the next node... + + // we have to null out the value because we are going to hang on to the node + final T nextValue = nextNode.getAndNullValue(); + spConsumerNode(nextNode); + return nextValue; + } + return null; + } + + @Override + public boolean offer(T v1, T v2) { + offer(v1); + offer(v2); + return true; + } + + @Override + public void clear() { + while (poll() != null && !isEmpty()) { } // NOPMD + } + LinkedQueueNode lvProducerNode() { + return producerNode.get(); + } + LinkedQueueNode xchgProducerNode(LinkedQueueNode node) { + return producerNode.getAndSet(node); + } + LinkedQueueNode lvConsumerNode() { + return consumerNode.get(); + } + + LinkedQueueNode lpConsumerNode() { + return consumerNode.get(); + } + void spConsumerNode(LinkedQueueNode node) { + consumerNode.lazySet(node); + } + + /** + * {@inheritDoc}
+ *

+ * IMPLEMENTATION NOTES:
+ * Queue is empty when producerNode is the same as consumerNode. An alternative implementation would be to observe + * the producerNode.value is null, which also means an empty queue because only the consumerNode.value is allowed to + * be null. + */ + @Override + public boolean isEmpty() { + return lvConsumerNode() == lvProducerNode(); + } + + static final class LinkedQueueNode extends AtomicReference> { + + private static final long serialVersionUID = 2404266111789071508L; + + private E value; + + LinkedQueueNode() { + } + + LinkedQueueNode(E val) { + spValue(val); + } + /** + * Gets the current value and nulls out the reference to it from this node. + * + * @return value + */ + public E getAndNullValue() { + E temp = lpValue(); + spValue(null); + return temp; + } + + public E lpValue() { + return value; + } + + public void spValue(E newValue) { + value = newValue; + } + + public void soNext(LinkedQueueNode n) { + lazySet(n); + } + + public LinkedQueueNode lvNext() { + return get(); + } + } +} diff --git a/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java b/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java new file mode 100755 index 0000000..53ac212 --- /dev/null +++ b/src/main/java/io/reactivex/internal/queue/SpscArrayQueue.java @@ -0,0 +1,136 @@ +/** + * 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. + */ + +/* + * The code was inspired by the similarly named JCTools class: + * https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/atomic + */ + +package io.reactivex.internal.queue; + +import java.util.concurrent.atomic.*; + +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.util.Pow2; + +/** + * A Single-Producer-Single-Consumer queue backed by a pre-allocated buffer. + *

+ * This implementation is a mashup of the Fast Flow + * algorithm with an optimization of the offer method taken from the BQueue algorithm (a variation on Fast + * Flow), and adjusted to comply with Queue.offer semantics with regards to capacity.
+ * For convenience the relevant papers are available in the resources folder:
+ * 2010 - Pisa - SPSC Queues on Shared Cache Multi-Core Systems.pdf
+ * 2012 - Junchang- BQueue- Efficient and Practical Queuing.pdf
+ *
This implementation is wait free. + * + * @param the element type of the queue + */ +public final class SpscArrayQueue extends AtomicReferenceArray implements SimplePlainQueue { + private static final long serialVersionUID = -1296597691183856449L; + private static final Integer MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); + final int mask; + final AtomicLong producerIndex; + long producerLookAhead; + final AtomicLong consumerIndex; + final int lookAheadStep; + + public SpscArrayQueue(int capacity) { + super(Pow2.roundToPowerOfTwo(capacity)); + this.mask = length() - 1; + this.producerIndex = new AtomicLong(); + this.consumerIndex = new AtomicLong(); + lookAheadStep = Math.min(capacity / 4, MAX_LOOK_AHEAD_STEP); + } + + @Override + public boolean offer(E e) { + if (null == e) { + throw new NullPointerException("Null is not a valid element"); + } + // local load of field to avoid repeated loads after volatile reads + final int mask = this.mask; + final long index = producerIndex.get(); + final int offset = calcElementOffset(index, mask); + if (index >= producerLookAhead) { + int step = lookAheadStep; + if (null == lvElement(calcElementOffset(index + step, mask))) { // LoadLoad + producerLookAhead = index + step; + } else if (null != lvElement(offset)) { + return false; + } + } + soElement(offset, e); // StoreStore + soProducerIndex(index + 1); // ordered store -> atomic and ordered for size() + return true; + } + + @Override + public boolean offer(E v1, E v2) { + // FIXME + return offer(v1) && offer(v2); + } + + @Nullable + @Override + public E poll() { + final long index = consumerIndex.get(); + final int offset = calcElementOffset(index); + // local load of field to avoid repeated loads after volatile reads + final E e = lvElement(offset); // LoadLoad + if (null == e) { + return null; + } + soConsumerIndex(index + 1); // ordered store -> atomic and ordered for size() + soElement(offset, null); // StoreStore + return e; + } + + @Override + public boolean isEmpty() { + return producerIndex.get() == consumerIndex.get(); + } + + void soProducerIndex(long newIndex) { + producerIndex.lazySet(newIndex); + } + + void soConsumerIndex(long newIndex) { + consumerIndex.lazySet(newIndex); + } + + @Override + public void clear() { + // we have to test isEmpty because of the weaker poll() guarantee + while (poll() != null || !isEmpty()) { } // NOPMD + } + + int calcElementOffset(long index, int mask) { + return (int)index & mask; + } + + int calcElementOffset(long index) { + return (int)index & mask; + } + + void soElement(int offset, E value) { + lazySet(offset, value); + } + + E lvElement(int offset) { + return get(offset); + } +} + diff --git a/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java b/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java new file mode 100755 index 0000000..e5e71e2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/queue/SpscLinkedArrayQueue.java @@ -0,0 +1,291 @@ +/** + * 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. + */ + +/* + * The code was inspired by the similarly named JCTools class: + * https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/atomic + */ + +package io.reactivex.internal.queue; + +import java.util.concurrent.atomic.*; + +import io.reactivex.annotations.Nullable; +import io.reactivex.internal.fuseable.SimplePlainQueue; +import io.reactivex.internal.util.Pow2; + +/** + * A single-producer single-consumer array-backed queue which can allocate new arrays in case the consumer is slower + * than the producer. + * @param the contained value type + */ +public final class SpscLinkedArrayQueue implements SimplePlainQueue { + static final int MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); + final AtomicLong producerIndex = new AtomicLong(); + + int producerLookAheadStep; + long producerLookAhead; + + final int producerMask; + + AtomicReferenceArray producerBuffer; + final int consumerMask; + AtomicReferenceArray consumerBuffer; + final AtomicLong consumerIndex = new AtomicLong(); + + private static final Object HAS_NEXT = new Object(); + + public SpscLinkedArrayQueue(final int bufferSize) { + int p2capacity = Pow2.roundToPowerOfTwo(Math.max(8, bufferSize)); + int mask = p2capacity - 1; + AtomicReferenceArray buffer = new AtomicReferenceArray(p2capacity + 1); + producerBuffer = buffer; + producerMask = mask; + adjustLookAheadStep(p2capacity); + consumerBuffer = buffer; + consumerMask = mask; + producerLookAhead = mask - 1; // we know it's all empty to start with + soProducerIndex(0L); + } + + /** + * {@inheritDoc} + *

+ * This implementation is correct for single producer thread use only. + */ + @Override + public boolean offer(final T e) { + if (null == e) { + throw new NullPointerException("Null is not a valid element"); + } + // local load of field to avoid repeated loads after volatile reads + final AtomicReferenceArray buffer = producerBuffer; + final long index = lpProducerIndex(); + final int mask = producerMask; + final int offset = calcWrappedOffset(index, mask); + if (index < producerLookAhead) { + return writeToQueue(buffer, e, index, offset); + } else { + final int lookAheadStep = producerLookAheadStep; + // go around the buffer or resize if full (unless we hit max capacity) + int lookAheadElementOffset = calcWrappedOffset(index + lookAheadStep, mask); + if (null == lvElement(buffer, lookAheadElementOffset)) { // LoadLoad + producerLookAhead = index + lookAheadStep - 1; // joy, there's plenty of room + return writeToQueue(buffer, e, index, offset); + } else if (null == lvElement(buffer, calcWrappedOffset(index + 1, mask))) { // buffer is not full + return writeToQueue(buffer, e, index, offset); + } else { + resize(buffer, index, offset, e, mask); // add a buffer and link old to new + return true; + } + } + } + + private boolean writeToQueue(final AtomicReferenceArray buffer, final T e, final long index, final int offset) { + soElement(buffer, offset, e); // StoreStore + soProducerIndex(index + 1); // this ensures atomic write of long on 32bit platforms + return true; + } + + private void resize(final AtomicReferenceArray oldBuffer, final long currIndex, final int offset, final T e, + final long mask) { + final int capacity = oldBuffer.length(); + final AtomicReferenceArray newBuffer = new AtomicReferenceArray(capacity); + producerBuffer = newBuffer; + producerLookAhead = currIndex + mask - 1; + soElement(newBuffer, offset, e); // StoreStore + soNext(oldBuffer, newBuffer); + soElement(oldBuffer, offset, HAS_NEXT); // new buffer is visible after element is + // inserted + soProducerIndex(currIndex + 1); // this ensures correctness on 32bit platforms + } + + private void soNext(AtomicReferenceArray curr, AtomicReferenceArray next) { + soElement(curr, calcDirectOffset(curr.length() - 1), next); + } + + @SuppressWarnings("unchecked") + private AtomicReferenceArray lvNextBufferAndUnlink(AtomicReferenceArray curr, int nextIndex) { + int nextOffset = calcDirectOffset(nextIndex); + AtomicReferenceArray nextBuffer = (AtomicReferenceArray)lvElement(curr, nextOffset); + soElement(curr, nextOffset, null); // Avoid GC nepotism + return nextBuffer; + } + /** + * {@inheritDoc} + *

+ * This implementation is correct for single consumer thread use only. + */ + @Nullable + @SuppressWarnings("unchecked") + @Override + public T poll() { + // local load of field to avoid repeated loads after volatile reads + final AtomicReferenceArray buffer = consumerBuffer; + final long index = lpConsumerIndex(); + final int mask = consumerMask; + final int offset = calcWrappedOffset(index, mask); + final Object e = lvElement(buffer, offset); // LoadLoad + boolean isNextBuffer = e == HAS_NEXT; + if (null != e && !isNextBuffer) { + soElement(buffer, offset, null); // StoreStore + soConsumerIndex(index + 1); // this ensures correctness on 32bit platforms + return (T) e; + } else if (isNextBuffer) { + return newBufferPoll(lvNextBufferAndUnlink(buffer, mask + 1), index, mask); + } + + return null; + } + + @SuppressWarnings("unchecked") + private T newBufferPoll(AtomicReferenceArray nextBuffer, final long index, final int mask) { + consumerBuffer = nextBuffer; + final int offsetInNew = calcWrappedOffset(index, mask); + final T n = (T) lvElement(nextBuffer, offsetInNew); // LoadLoad + if (null != n) { + soElement(nextBuffer, offsetInNew, null); // StoreStore + soConsumerIndex(index + 1); // this ensures correctness on 32bit platforms + } + return n; + } + + @SuppressWarnings("unchecked") + public T peek() { + final AtomicReferenceArray buffer = consumerBuffer; + final long index = lpConsumerIndex(); + final int mask = consumerMask; + final int offset = calcWrappedOffset(index, mask); + final Object e = lvElement(buffer, offset); // LoadLoad + if (e == HAS_NEXT) { + return newBufferPeek(lvNextBufferAndUnlink(buffer, mask + 1), index, mask); + } + + return (T) e; + } + + @SuppressWarnings("unchecked") + private T newBufferPeek(AtomicReferenceArray nextBuffer, final long index, final int mask) { + consumerBuffer = nextBuffer; + final int offsetInNew = calcWrappedOffset(index, mask); + return (T) lvElement(nextBuffer, offsetInNew); // LoadLoad + } + + @Override + public void clear() { + while (poll() != null || !isEmpty()) { } // NOPMD + } + + public int size() { + /* + * It is possible for a thread to be interrupted or reschedule between the read of the producer and + * consumer indices, therefore protection is required to ensure size is within valid range. In the + * event of concurrent polls/offers to this method the size is OVER estimated as we read consumer + * index BEFORE the producer index. + */ + long after = lvConsumerIndex(); + while (true) { + final long before = after; + final long currentProducerIndex = lvProducerIndex(); + after = lvConsumerIndex(); + if (before == after) { + return (int) (currentProducerIndex - after); + } + } + } + + @Override + public boolean isEmpty() { + return lvProducerIndex() == lvConsumerIndex(); + } + + private void adjustLookAheadStep(int capacity) { + producerLookAheadStep = Math.min(capacity / 4, MAX_LOOK_AHEAD_STEP); + } + + private long lvProducerIndex() { + return producerIndex.get(); + } + + private long lvConsumerIndex() { + return consumerIndex.get(); + } + + private long lpProducerIndex() { + return producerIndex.get(); + } + + private long lpConsumerIndex() { + return consumerIndex.get(); + } + + private void soProducerIndex(long v) { + producerIndex.lazySet(v); + } + + private void soConsumerIndex(long v) { + consumerIndex.lazySet(v); + } + + private static int calcWrappedOffset(long index, int mask) { + return calcDirectOffset((int)index & mask); + } + private static int calcDirectOffset(int index) { + return index; + } + private static void soElement(AtomicReferenceArray buffer, int offset, Object e) { + buffer.lazySet(offset, e); + } + + private static Object lvElement(AtomicReferenceArray buffer, int offset) { + return buffer.get(offset); + } + + /** + * Offer two elements at the same time. + *

Don't use the regular offer() with this at all! + * @param first the first value, not null + * @param second the second value, not null + * @return true if the queue accepted the two new values + */ + @Override + public boolean offer(T first, T second) { + final AtomicReferenceArray buffer = producerBuffer; + final long p = lvProducerIndex(); + final int m = producerMask; + + int pi = calcWrappedOffset(p + 2, m); + + if (null == lvElement(buffer, pi)) { + pi = calcWrappedOffset(p, m); + soElement(buffer, pi + 1, second); + soElement(buffer, pi, first); + soProducerIndex(p + 2); + } else { + final int capacity = buffer.length(); + final AtomicReferenceArray newBuffer = new AtomicReferenceArray(capacity); + producerBuffer = newBuffer; + + pi = calcWrappedOffset(p, m); + soElement(newBuffer, pi + 1, second); // StoreStore + soElement(newBuffer, pi, first); + soNext(buffer, newBuffer); + + soElement(buffer, pi, HAS_NEXT); // new buffer is visible after element is + + soProducerIndex(p + 2); // this ensures correctness on 32bit platforms + } + + return true; + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/AbstractDirectTask.java b/src/main/java/io/reactivex/internal/schedulers/AbstractDirectTask.java new file mode 100755 index 0000000..2bf3c45 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/AbstractDirectTask.java @@ -0,0 +1,86 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.Functions; +import io.reactivex.schedulers.SchedulerRunnableIntrospection; + +/** + * Base functionality for direct tasks that manage a runnable and cancellation/completion. + * @since 2.0.8 + */ +abstract class AbstractDirectTask +extends AtomicReference> +implements Disposable, SchedulerRunnableIntrospection { + + private static final long serialVersionUID = 1811839108042568751L; + + protected final Runnable runnable; + + protected Thread runner; + + protected static final FutureTask FINISHED = new FutureTask(Functions.EMPTY_RUNNABLE, null); + + protected static final FutureTask DISPOSED = new FutureTask(Functions.EMPTY_RUNNABLE, null); + + AbstractDirectTask(Runnable runnable) { + this.runnable = runnable; + } + + @Override + public final void dispose() { + Future f = get(); + if (f != FINISHED && f != DISPOSED) { + if (compareAndSet(f, DISPOSED)) { + if (f != null) { + f.cancel(runner != Thread.currentThread()); + } + } + } + } + + @Override + public final boolean isDisposed() { + Future f = get(); + return f == FINISHED || f == DISPOSED; + } + + public final void setFuture(Future future) { + for (;;) { + Future f = get(); + if (f == FINISHED) { + break; + } + if (f == DISPOSED) { + future.cancel(runner != Thread.currentThread()); + break; + } + if (compareAndSet(f, future)) { + break; + } + } + } + + @Override + public Runnable getWrappedRunnable() { + return runnable; + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java b/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java new file mode 100755 index 0000000..7fa7e0a --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/ComputationScheduler.java @@ -0,0 +1,246 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Holds a fixed pool of worker threads and assigns them + * to requested Scheduler.Workers in a round-robin fashion. + */ +public final class ComputationScheduler extends Scheduler implements SchedulerMultiWorkerSupport { + /** This will indicate no pool is active. */ + static final FixedSchedulerPool NONE; + /** Manages a fixed number of workers. */ + private static final String THREAD_NAME_PREFIX = "RxComputationThreadPool"; + static final RxThreadFactory THREAD_FACTORY; + /** + * Key to setting the maximum number of computation scheduler threads. + * Zero or less is interpreted as use available. Capped by available. + */ + static final String KEY_MAX_THREADS = "rx2.computation-threads"; + /** The maximum number of computation scheduler threads. */ + static final int MAX_THREADS; + + static final PoolWorker SHUTDOWN_WORKER; + + final ThreadFactory threadFactory; + final AtomicReference pool; + /** The name of the system property for setting the thread priority for this Scheduler. */ + private static final String KEY_COMPUTATION_PRIORITY = "rx2.computation-priority"; + + static { + MAX_THREADS = cap(Runtime.getRuntime().availableProcessors(), Integer.getInteger(KEY_MAX_THREADS, 0)); + + SHUTDOWN_WORKER = new PoolWorker(new RxThreadFactory("RxComputationShutdown")); + SHUTDOWN_WORKER.dispose(); + + int priority = Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY, + Integer.getInteger(KEY_COMPUTATION_PRIORITY, Thread.NORM_PRIORITY))); + + THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX, priority, true); + + NONE = new FixedSchedulerPool(0, THREAD_FACTORY); + NONE.shutdown(); + } + + static int cap(int cpuCount, int paramThreads) { + return paramThreads <= 0 || paramThreads > cpuCount ? cpuCount : paramThreads; + } + + static final class FixedSchedulerPool implements SchedulerMultiWorkerSupport { + final int cores; + + final PoolWorker[] eventLoops; + long n; + + FixedSchedulerPool(int maxThreads, ThreadFactory threadFactory) { + // initialize event loops + this.cores = maxThreads; + this.eventLoops = new PoolWorker[maxThreads]; + for (int i = 0; i < maxThreads; i++) { + this.eventLoops[i] = new PoolWorker(threadFactory); + } + } + + public PoolWorker getEventLoop() { + int c = cores; + if (c == 0) { + return SHUTDOWN_WORKER; + } + // simple round robin, improvements to come + return eventLoops[(int)(n++ % c)]; + } + + public void shutdown() { + for (PoolWorker w : eventLoops) { + w.dispose(); + } + } + + @Override + public void createWorkers(int number, WorkerCallback callback) { + int c = cores; + if (c == 0) { + for (int i = 0; i < number; i++) { + callback.onWorker(i, SHUTDOWN_WORKER); + } + } else { + int index = (int)n % c; + for (int i = 0; i < number; i++) { + callback.onWorker(i, new EventLoopWorker(eventLoops[index])); + if (++index == c) { + index = 0; + } + } + n = index; + } + } + } + + /** + * Create a scheduler with pool size equal to the available processor + * count and using least-recent worker selection policy. + */ + public ComputationScheduler() { + this(THREAD_FACTORY); + } + + /** + * Create a scheduler with pool size equal to the available processor + * count and using least-recent worker selection policy. + * + * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any + * system properties for configuring new thread creation. Cannot be null. + */ + public ComputationScheduler(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + this.pool = new AtomicReference(NONE); + start(); + } + + @NonNull + @Override + public Worker createWorker() { + return new EventLoopWorker(pool.get().getEventLoop()); + } + + @Override + public void createWorkers(int number, WorkerCallback callback) { + ObjectHelper.verifyPositive(number, "number > 0 required"); + pool.get().createWorkers(number, callback); + } + + @NonNull + @Override + public Disposable scheduleDirect(@NonNull Runnable run, long delay, TimeUnit unit) { + PoolWorker w = pool.get().getEventLoop(); + return w.scheduleDirect(run, delay, unit); + } + + @NonNull + @Override + public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initialDelay, long period, TimeUnit unit) { + PoolWorker w = pool.get().getEventLoop(); + return w.schedulePeriodicallyDirect(run, initialDelay, period, unit); + } + + @Override + public void start() { + FixedSchedulerPool update = new FixedSchedulerPool(MAX_THREADS, threadFactory); + if (!pool.compareAndSet(NONE, update)) { + update.shutdown(); + } + } + + @Override + public void shutdown() { + for (;;) { + FixedSchedulerPool curr = pool.get(); + if (curr == NONE) { + return; + } + if (pool.compareAndSet(curr, NONE)) { + curr.shutdown(); + return; + } + } + } + + static final class EventLoopWorker extends Worker { + private final ListCompositeDisposable serial; + private final CompositeDisposable timed; + private final ListCompositeDisposable both; + private final PoolWorker poolWorker; + + volatile boolean disposed; + + EventLoopWorker(PoolWorker poolWorker) { + this.poolWorker = poolWorker; + this.serial = new ListCompositeDisposable(); + this.timed = new CompositeDisposable(); + this.both = new ListCompositeDisposable(); + this.both.add(serial); + this.both.add(timed); + } + + @Override + public void dispose() { + if (!disposed) { + disposed = true; + both.dispose(); + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable action) { + if (disposed) { + return EmptyDisposable.INSTANCE; + } + + return poolWorker.scheduleActual(action, 0, TimeUnit.MILLISECONDS, serial); + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable action, long delayTime, @NonNull TimeUnit unit) { + if (disposed) { + return EmptyDisposable.INSTANCE; + } + + return poolWorker.scheduleActual(action, delayTime, unit, timed); + } + } + + static final class PoolWorker extends NewThreadWorker { + PoolWorker(ThreadFactory threadFactory) { + super(threadFactory); + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/DisposeOnCancel.java b/src/main/java/io/reactivex/internal/schedulers/DisposeOnCancel.java new file mode 100755 index 0000000..c79eaeb --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/DisposeOnCancel.java @@ -0,0 +1,58 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.*; + +import io.reactivex.disposables.Disposable; + +/** + * Implements the Future interface and calls dispose() on cancel() but + * the other methods are not implemented. + */ +final class DisposeOnCancel implements Future { + + final Disposable upstream; + + DisposeOnCancel(Disposable d) { + this.upstream = d; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + upstream.dispose(); + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return false; + } + + @Override + public Object get() throws InterruptedException, ExecutionException { + return null; + } + + @Override + public Object get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return null; + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java b/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java new file mode 100755 index 0000000..8c0bb9a --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/ExecutorScheduler.java @@ -0,0 +1,473 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.queue.MpscLinkedQueue; +import io.reactivex.internal.schedulers.ExecutorScheduler.ExecutorWorker.*; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.*; + +/** + * Wraps an Executor and provides the Scheduler API over it. + */ +public final class ExecutorScheduler extends Scheduler { + + final boolean interruptibleWorker; + + @NonNull + final Executor executor; + + static final Scheduler HELPER = Schedulers.single(); + + public ExecutorScheduler(@NonNull Executor executor, boolean interruptibleWorker) { + this.executor = executor; + this.interruptibleWorker = interruptibleWorker; + } + + @NonNull + @Override + public Worker createWorker() { + return new ExecutorWorker(executor, interruptibleWorker); + } + + @NonNull + @Override + public Disposable scheduleDirect(@NonNull Runnable run) { + Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + try { + if (executor instanceof ExecutorService) { + ScheduledDirectTask task = new ScheduledDirectTask(decoratedRun); + Future f = ((ExecutorService)executor).submit(task); + task.setFuture(f); + return task; + } + + if (interruptibleWorker) { + InterruptibleRunnable interruptibleTask = new InterruptibleRunnable(decoratedRun, null); + executor.execute(interruptibleTask); + return interruptibleTask; + } else { + BooleanRunnable br = new BooleanRunnable(decoratedRun); + executor.execute(br); + return br; + } + } catch (RejectedExecutionException ex) { + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + } + + @NonNull + @Override + public Disposable scheduleDirect(@NonNull Runnable run, final long delay, final TimeUnit unit) { + final Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + if (executor instanceof ScheduledExecutorService) { + try { + ScheduledDirectTask task = new ScheduledDirectTask(decoratedRun); + Future f = ((ScheduledExecutorService)executor).schedule(task, delay, unit); + task.setFuture(f); + return task; + } catch (RejectedExecutionException ex) { + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + } + + final DelayedRunnable dr = new DelayedRunnable(decoratedRun); + + Disposable delayed = HELPER.scheduleDirect(new DelayedDispose(dr), delay, unit); + + dr.timed.replace(delayed); + + return dr; + } + + @NonNull + @Override + public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initialDelay, long period, TimeUnit unit) { + if (executor instanceof ScheduledExecutorService) { + Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + try { + ScheduledDirectPeriodicTask task = new ScheduledDirectPeriodicTask(decoratedRun); + Future f = ((ScheduledExecutorService)executor).scheduleAtFixedRate(task, initialDelay, period, unit); + task.setFuture(f); + return task; + } catch (RejectedExecutionException ex) { + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + } + return super.schedulePeriodicallyDirect(run, initialDelay, period, unit); + } + /* public: test support. */ + public static final class ExecutorWorker extends Worker implements Runnable { + + final boolean interruptibleWorker; + + final Executor executor; + + final MpscLinkedQueue queue; + + volatile boolean disposed; + + final AtomicInteger wip = new AtomicInteger(); + + final CompositeDisposable tasks = new CompositeDisposable(); + + public ExecutorWorker(Executor executor, boolean interruptibleWorker) { + this.executor = executor; + this.queue = new MpscLinkedQueue(); + this.interruptibleWorker = interruptibleWorker; + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable run) { + if (disposed) { + return EmptyDisposable.INSTANCE; + } + + Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + + Runnable task; + Disposable disposable; + + if (interruptibleWorker) { + InterruptibleRunnable interruptibleTask = new InterruptibleRunnable(decoratedRun, tasks); + tasks.add(interruptibleTask); + + task = interruptibleTask; + disposable = interruptibleTask; + } else { + BooleanRunnable runnableTask = new BooleanRunnable(decoratedRun); + + task = runnableTask; + disposable = runnableTask; + } + + queue.offer(task); + + if (wip.getAndIncrement() == 0) { + try { + executor.execute(this); + } catch (RejectedExecutionException ex) { + disposed = true; + queue.clear(); + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + } + + return disposable; + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) { + if (delay <= 0) { + return schedule(run); + } + if (disposed) { + return EmptyDisposable.INSTANCE; + } + + SequentialDisposable first = new SequentialDisposable(); + + final SequentialDisposable mar = new SequentialDisposable(first); + + final Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + + ScheduledRunnable sr = new ScheduledRunnable(new SequentialDispose(mar, decoratedRun), tasks); + tasks.add(sr); + + if (executor instanceof ScheduledExecutorService) { + try { + Future f = ((ScheduledExecutorService)executor).schedule((Callable)sr, delay, unit); + sr.setFuture(f); + } catch (RejectedExecutionException ex) { + disposed = true; + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + } else { + final Disposable d = HELPER.scheduleDirect(sr, delay, unit); + sr.setFuture(new DisposeOnCancel(d)); + } + + first.replace(sr); + + return mar; + } + + @Override + public void dispose() { + if (!disposed) { + disposed = true; + tasks.dispose(); + if (wip.getAndIncrement() == 0) { + queue.clear(); + } + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void run() { + int missed = 1; + final MpscLinkedQueue q = queue; + for (;;) { + + if (disposed) { + q.clear(); + return; + } + + for (;;) { + Runnable run = q.poll(); + if (run == null) { + break; + } + run.run(); + + if (disposed) { + q.clear(); + return; + } + } + + if (disposed) { + q.clear(); + return; + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class BooleanRunnable extends AtomicBoolean implements Runnable, Disposable { + + private static final long serialVersionUID = -2421395018820541164L; + + final Runnable actual; + BooleanRunnable(Runnable actual) { + this.actual = actual; + } + + @Override + public void run() { + if (get()) { + return; + } + try { + actual.run(); + } finally { + lazySet(true); + } + } + + @Override + public void dispose() { + lazySet(true); + } + + @Override + public boolean isDisposed() { + return get(); + } + } + + final class SequentialDispose implements Runnable { + private final SequentialDisposable mar; + private final Runnable decoratedRun; + + SequentialDispose(SequentialDisposable mar, Runnable decoratedRun) { + this.mar = mar; + this.decoratedRun = decoratedRun; + } + + @Override + public void run() { + mar.replace(schedule(decoratedRun)); + } + } + + /** + * Wrapper for a {@link Runnable} with additional logic for handling interruption on + * a shared thread, similar to how Java Executors do it. + */ + static final class InterruptibleRunnable extends AtomicInteger implements Runnable, Disposable { + + private static final long serialVersionUID = -3603436687413320876L; + + final Runnable run; + + final DisposableContainer tasks; + + volatile Thread thread; + + static final int READY = 0; + + static final int RUNNING = 1; + + static final int FINISHED = 2; + + static final int INTERRUPTING = 3; + + static final int INTERRUPTED = 4; + + InterruptibleRunnable(Runnable run, DisposableContainer tasks) { + this.run = run; + this.tasks = tasks; + } + + @Override + public void run() { + if (get() == READY) { + thread = Thread.currentThread(); + if (compareAndSet(READY, RUNNING)) { + try { + run.run(); + } finally { + thread = null; + if (compareAndSet(RUNNING, FINISHED)) { + cleanup(); + } else { + while (get() == INTERRUPTING) { + Thread.yield(); + } + Thread.interrupted(); + } + } + } else { + thread = null; + } + } + } + + @Override + public void dispose() { + for (;;) { + int state = get(); + if (state >= FINISHED) { + break; + } else if (state == READY) { + if (compareAndSet(READY, INTERRUPTED)) { + cleanup(); + break; + } + } else { + if (compareAndSet(RUNNING, INTERRUPTING)) { + Thread t = thread; + if (t != null) { + t.interrupt(); + thread = null; + } + set(INTERRUPTED); + cleanup(); + break; + } + } + } + } + + void cleanup() { + if (tasks != null) { + tasks.delete(this); + } + } + + @Override + public boolean isDisposed() { + return get() >= FINISHED; + } + } + } + + static final class DelayedRunnable extends AtomicReference + implements Runnable, Disposable, SchedulerRunnableIntrospection { + + private static final long serialVersionUID = -4101336210206799084L; + + final SequentialDisposable timed; + + final SequentialDisposable direct; + + DelayedRunnable(Runnable run) { + super(run); + this.timed = new SequentialDisposable(); + this.direct = new SequentialDisposable(); + } + + @Override + public void run() { + Runnable r = get(); + if (r != null) { + try { + r.run(); + } finally { + lazySet(null); + timed.lazySet(DisposableHelper.DISPOSED); + direct.lazySet(DisposableHelper.DISPOSED); + } + } + } + + @Override + public boolean isDisposed() { + return get() == null; + } + + @Override + public void dispose() { + if (getAndSet(null) != null) { + timed.dispose(); + direct.dispose(); + } + } + + @Override + public Runnable getWrappedRunnable() { + Runnable r = get(); + return r != null ? r : Functions.EMPTY_RUNNABLE; + } + } + + final class DelayedDispose implements Runnable { + private final DelayedRunnable dr; + + DelayedDispose(DelayedRunnable dr) { + this.dr = dr; + } + + @Override + public void run() { + dr.direct.replace(scheduleDirect(dr)); + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/ImmediateThinScheduler.java b/src/main/java/io/reactivex/internal/schedulers/ImmediateThinScheduler.java new file mode 100755 index 0000000..990a8cd --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/ImmediateThinScheduler.java @@ -0,0 +1,105 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.*; + +/** + * A Scheduler partially implementing the API by allowing only non-delayed, non-periodic + * task execution on the current thread immediately. + *

+ * Note that this doesn't support recursive scheduling and disposing the returned Disposable + * has no effect (because when the schedule() method returns, the task has been already run). + */ +public final class ImmediateThinScheduler extends Scheduler { + + /** + * The singleton instance of the immediate (thin) scheduler. + */ + public static final Scheduler INSTANCE = new ImmediateThinScheduler(); + + static final Worker WORKER = new ImmediateThinWorker(); + + static final Disposable DISPOSED; + + static { + DISPOSED = Disposables.empty(); + DISPOSED.dispose(); + } + + private ImmediateThinScheduler() { + // singleton class + } + + @NonNull + @Override + public Disposable scheduleDirect(@NonNull Runnable run) { + run.run(); + return DISPOSED; + } + + @NonNull + @Override + public Disposable scheduleDirect(@NonNull Runnable run, long delay, TimeUnit unit) { + throw new UnsupportedOperationException("This scheduler doesn't support delayed execution"); + } + + @NonNull + @Override + public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initialDelay, long period, TimeUnit unit) { + throw new UnsupportedOperationException("This scheduler doesn't support periodic execution"); + } + + @NonNull + @Override + public Worker createWorker() { + return WORKER; + } + + static final class ImmediateThinWorker extends Worker { + + @Override + public void dispose() { + // This worker is always stateless and won't track tasks + } + + @Override + public boolean isDisposed() { + return false; // dispose() has no effect + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable run) { + run.run(); + return DISPOSED; + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) { + throw new UnsupportedOperationException("This scheduler doesn't support delayed execution"); + } + + @NonNull + @Override + public Disposable schedulePeriodically(@NonNull Runnable run, long initialDelay, long period, TimeUnit unit) { + throw new UnsupportedOperationException("This scheduler doesn't support periodic execution"); + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/InstantPeriodicTask.java b/src/main/java/io/reactivex/internal/schedulers/InstantPeriodicTask.java new file mode 100755 index 0000000..ec9b2a0 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/InstantPeriodicTask.java @@ -0,0 +1,107 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.Functions; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Wrapper for a regular task that gets immediately rescheduled when the task completed. + */ +final class InstantPeriodicTask implements Callable, Disposable { + + final Runnable task; + + final AtomicReference> rest; + + final AtomicReference> first; + + final ExecutorService executor; + + Thread runner; + + static final FutureTask CANCELLED = new FutureTask(Functions.EMPTY_RUNNABLE, null); + + InstantPeriodicTask(Runnable task, ExecutorService executor) { + super(); + this.task = task; + this.first = new AtomicReference>(); + this.rest = new AtomicReference>(); + this.executor = executor; + } + + @Override + public Void call() throws Exception { + runner = Thread.currentThread(); + try { + task.run(); + setRest(executor.submit(this)); + runner = null; + } catch (Throwable ex) { + runner = null; + RxJavaPlugins.onError(ex); + } + return null; + } + + @Override + public void dispose() { + Future current = first.getAndSet(CANCELLED); + if (current != null && current != CANCELLED) { + current.cancel(runner != Thread.currentThread()); + } + current = rest.getAndSet(CANCELLED); + if (current != null && current != CANCELLED) { + current.cancel(runner != Thread.currentThread()); + } + } + + @Override + public boolean isDisposed() { + return first.get() == CANCELLED; + } + + void setFirst(Future f) { + for (;;) { + Future current = first.get(); + if (current == CANCELLED) { + f.cancel(runner != Thread.currentThread()); + return; + } + if (first.compareAndSet(current, f)) { + return; + } + } + } + + void setRest(Future f) { + for (;;) { + Future current = rest.get(); + if (current == CANCELLED) { + f.cancel(runner != Thread.currentThread()); + return; + } + if (rest.compareAndSet(current, f)) { + return; + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java b/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java new file mode 100755 index 0000000..833ce26 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java @@ -0,0 +1,273 @@ +/** + * 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.internal.schedulers; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.*; +import io.reactivex.internal.disposables.EmptyDisposable; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +/** + * Scheduler that creates and caches a set of thread pools and reuses them if possible. + */ +public final class IoScheduler extends Scheduler { + private static final String WORKER_THREAD_NAME_PREFIX = "RxCachedThreadScheduler"; + static final RxThreadFactory WORKER_THREAD_FACTORY; + + private static final String EVICTOR_THREAD_NAME_PREFIX = "RxCachedWorkerPoolEvictor"; + static final RxThreadFactory EVICTOR_THREAD_FACTORY; + + /** The name of the system property for setting the keep-alive time (in seconds) for this Scheduler workers. */ + private static final String KEY_KEEP_ALIVE_TIME = "rx2.io-keep-alive-time"; + public static final long KEEP_ALIVE_TIME_DEFAULT = 60; + + private static final long KEEP_ALIVE_TIME; + private static final TimeUnit KEEP_ALIVE_UNIT = TimeUnit.SECONDS; + + static final ThreadWorker SHUTDOWN_THREAD_WORKER; + final ThreadFactory threadFactory; + final AtomicReference pool; + + /** The name of the system property for setting the thread priority for this Scheduler. */ + private static final String KEY_IO_PRIORITY = "rx2.io-priority"; + + /** The name of the system property for setting the release behaviour for this Scheduler. */ + private static final String KEY_SCHEDULED_RELEASE = "rx2.io-scheduled-release"; + static boolean USE_SCHEDULED_RELEASE; + + static final CachedWorkerPool NONE; + + static { + KEEP_ALIVE_TIME = Long.getLong(KEY_KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_DEFAULT); + + SHUTDOWN_THREAD_WORKER = new ThreadWorker(new RxThreadFactory("RxCachedThreadSchedulerShutdown")); + SHUTDOWN_THREAD_WORKER.dispose(); + + int priority = Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY, + Integer.getInteger(KEY_IO_PRIORITY, Thread.NORM_PRIORITY))); + + WORKER_THREAD_FACTORY = new RxThreadFactory(WORKER_THREAD_NAME_PREFIX, priority); + + EVICTOR_THREAD_FACTORY = new RxThreadFactory(EVICTOR_THREAD_NAME_PREFIX, priority); + + USE_SCHEDULED_RELEASE = Boolean.getBoolean(KEY_SCHEDULED_RELEASE); + + NONE = new CachedWorkerPool(0, null, WORKER_THREAD_FACTORY); + NONE.shutdown(); + } + + static final class CachedWorkerPool implements Runnable { + private final long keepAliveTime; + private final ConcurrentLinkedQueue expiringWorkerQueue; + final CompositeDisposable allWorkers; + private final ScheduledExecutorService evictorService; + private final Future evictorTask; + private final ThreadFactory threadFactory; + + CachedWorkerPool(long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory) { + this.keepAliveTime = unit != null ? unit.toNanos(keepAliveTime) : 0L; + this.expiringWorkerQueue = new ConcurrentLinkedQueue(); + this.allWorkers = new CompositeDisposable(); + this.threadFactory = threadFactory; + + ScheduledExecutorService evictor = null; + Future task = null; + if (unit != null) { + evictor = Executors.newScheduledThreadPool(1, EVICTOR_THREAD_FACTORY); + task = evictor.scheduleWithFixedDelay(this, this.keepAliveTime, this.keepAliveTime, TimeUnit.NANOSECONDS); + } + evictorService = evictor; + evictorTask = task; + } + + @Override + public void run() { + evictExpiredWorkers(); + } + + ThreadWorker get() { + if (allWorkers.isDisposed()) { + return SHUTDOWN_THREAD_WORKER; + } + while (!expiringWorkerQueue.isEmpty()) { + ThreadWorker threadWorker = expiringWorkerQueue.poll(); + if (threadWorker != null) { + return threadWorker; + } + } + + // No cached worker found, so create a new one. + ThreadWorker w = new ThreadWorker(threadFactory); + allWorkers.add(w); + return w; + } + + void release(ThreadWorker threadWorker) { + // Refresh expire time before putting worker back in pool + threadWorker.setExpirationTime(now() + keepAliveTime); + + expiringWorkerQueue.offer(threadWorker); + } + + void evictExpiredWorkers() { + if (!expiringWorkerQueue.isEmpty()) { + long currentTimestamp = now(); + + for (ThreadWorker threadWorker : expiringWorkerQueue) { + if (threadWorker.getExpirationTime() <= currentTimestamp) { + if (expiringWorkerQueue.remove(threadWorker)) { + allWorkers.remove(threadWorker); + } + } else { + // Queue is ordered with the worker that will expire first in the beginning, so when we + // find a non-expired worker we can stop evicting. + break; + } + } + } + } + + long now() { + return System.nanoTime(); + } + + void shutdown() { + allWorkers.dispose(); + if (evictorTask != null) { + evictorTask.cancel(true); + } + if (evictorService != null) { + evictorService.shutdownNow(); + } + } + } + + public IoScheduler() { + this(WORKER_THREAD_FACTORY); + } + + /** + * Constructs an IoScheduler with the given thread factory and starts the pool of workers. + * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any + * system properties for configuring new thread creation. Cannot be null. + */ + public IoScheduler(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + this.pool = new AtomicReference(NONE); + start(); + } + + @Override + public void start() { + CachedWorkerPool update = new CachedWorkerPool(KEEP_ALIVE_TIME, KEEP_ALIVE_UNIT, threadFactory); + if (!pool.compareAndSet(NONE, update)) { + update.shutdown(); + } + } + + @Override + public void shutdown() { + for (;;) { + CachedWorkerPool curr = pool.get(); + if (curr == NONE) { + return; + } + if (pool.compareAndSet(curr, NONE)) { + curr.shutdown(); + return; + } + } + } + + @NonNull + @Override + public Worker createWorker() { + return new EventLoopWorker(pool.get()); + } + + public int size() { + return pool.get().allWorkers.size(); + } + + static final class EventLoopWorker extends Worker implements Runnable { + private final CompositeDisposable tasks; + private final CachedWorkerPool pool; + private final ThreadWorker threadWorker; + + final AtomicBoolean once = new AtomicBoolean(); + + EventLoopWorker(CachedWorkerPool pool) { + this.pool = pool; + this.tasks = new CompositeDisposable(); + this.threadWorker = pool.get(); + } + + @Override + public void dispose() { + if (once.compareAndSet(false, true)) { + tasks.dispose(); + if (USE_SCHEDULED_RELEASE) { + threadWorker.scheduleActual(this, 0, TimeUnit.NANOSECONDS, null); + } else { + // releasing the pool should be the last action + pool.release(threadWorker); + } + } + } + + @Override + public void run() { + pool.release(threadWorker); + } + + @Override + public boolean isDisposed() { + return once.get(); + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable action, long delayTime, @NonNull TimeUnit unit) { + if (tasks.isDisposed()) { + // don't schedule, we are unsubscribed + return EmptyDisposable.INSTANCE; + } + + return threadWorker.scheduleActual(action, delayTime, unit, tasks); + } + } + + static final class ThreadWorker extends NewThreadWorker { + private long expirationTime; + + ThreadWorker(ThreadFactory threadFactory) { + super(threadFactory); + this.expirationTime = 0L; + } + + public long getExpirationTime() { + return expirationTime; + } + + public void setExpirationTime(long expirationTime) { + this.expirationTime = expirationTime; + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/NewThreadScheduler.java b/src/main/java/io/reactivex/internal/schedulers/NewThreadScheduler.java new file mode 100755 index 0000000..2513cb3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/NewThreadScheduler.java @@ -0,0 +1,57 @@ +/** + * 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.internal.schedulers; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; + +import java.util.concurrent.ThreadFactory; + +/** + * Schedules work on a new thread. + */ +public final class NewThreadScheduler extends Scheduler { + + final ThreadFactory threadFactory; + + private static final String THREAD_NAME_PREFIX = "RxNewThreadScheduler"; + private static final RxThreadFactory THREAD_FACTORY; + + /** The name of the system property for setting the thread priority for this Scheduler. */ + private static final String KEY_NEWTHREAD_PRIORITY = "rx2.newthread-priority"; + + static { + int priority = Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY, + Integer.getInteger(KEY_NEWTHREAD_PRIORITY, Thread.NORM_PRIORITY))); + + THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX, priority); + } + + public NewThreadScheduler() { + this(THREAD_FACTORY); + } + + public NewThreadScheduler(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + @NonNull + @Override + public Worker createWorker() { + return new NewThreadWorker(threadFactory); + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java b/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java new file mode 100755 index 0000000..9ef225a --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/NewThreadWorker.java @@ -0,0 +1,182 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.*; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Base class that manages a single-threaded ScheduledExecutorService as a + * worker but doesn't perform task-tracking operations. + * + */ +public class NewThreadWorker extends Scheduler.Worker implements Disposable { + private final ScheduledExecutorService executor; + + volatile boolean disposed; + + public NewThreadWorker(ThreadFactory threadFactory) { + executor = SchedulerPoolFactory.create(threadFactory); + } + + @NonNull + @Override + public Disposable schedule(@NonNull final Runnable run) { + return schedule(run, 0, null); + } + + @NonNull + @Override + public Disposable schedule(@NonNull final Runnable action, long delayTime, @NonNull TimeUnit unit) { + if (disposed) { + return EmptyDisposable.INSTANCE; + } + return scheduleActual(action, delayTime, unit, null); + } + + /** + * Schedules the given runnable on the underlying executor directly and + * returns its future wrapped into a Disposable. + * @param run the Runnable to execute in a delayed fashion + * @param delayTime the delay amount + * @param unit the delay time unit + * @return the ScheduledRunnable instance + */ + public Disposable scheduleDirect(final Runnable run, long delayTime, TimeUnit unit) { + ScheduledDirectTask task = new ScheduledDirectTask(RxJavaPlugins.onSchedule(run)); + try { + Future f; + if (delayTime <= 0L) { + f = executor.submit(task); + } else { + f = executor.schedule(task, delayTime, unit); + } + task.setFuture(f); + return task; + } catch (RejectedExecutionException ex) { + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + } + + /** + * Schedules the given runnable periodically on the underlying executor directly + * and returns its future wrapped into a Disposable. + * @param run the Runnable to execute in a periodic fashion + * @param initialDelay the initial delay amount + * @param period the repeat period amount + * @param unit the time unit for both the initialDelay and period + * @return the ScheduledRunnable instance + */ + public Disposable schedulePeriodicallyDirect(Runnable run, long initialDelay, long period, TimeUnit unit) { + final Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + if (period <= 0L) { + + InstantPeriodicTask periodicWrapper = new InstantPeriodicTask(decoratedRun, executor); + try { + Future f; + if (initialDelay <= 0L) { + f = executor.submit(periodicWrapper); + } else { + f = executor.schedule(periodicWrapper, initialDelay, unit); + } + periodicWrapper.setFirst(f); + } catch (RejectedExecutionException ex) { + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + + return periodicWrapper; + } + ScheduledDirectPeriodicTask task = new ScheduledDirectPeriodicTask(decoratedRun); + try { + Future f = executor.scheduleAtFixedRate(task, initialDelay, period, unit); + task.setFuture(f); + return task; + } catch (RejectedExecutionException ex) { + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + } + + /** + * Wraps the given runnable into a ScheduledRunnable and schedules it + * on the underlying ScheduledExecutorService. + *

If the schedule has been rejected, the ScheduledRunnable.wasScheduled will return + * false. + * @param run the runnable instance + * @param delayTime the time to delay the execution + * @param unit the time unit + * @param parent the optional tracker parent to add the created ScheduledRunnable instance to before it gets scheduled + * @return the ScheduledRunnable instance + */ + @NonNull + public ScheduledRunnable scheduleActual(final Runnable run, long delayTime, @NonNull TimeUnit unit, @Nullable DisposableContainer parent) { + Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + + ScheduledRunnable sr = new ScheduledRunnable(decoratedRun, parent); + + if (parent != null) { + if (!parent.add(sr)) { + return sr; + } + } + + Future f; + try { + if (delayTime <= 0) { + f = executor.submit((Callable)sr); + } else { + f = executor.schedule((Callable)sr, delayTime, unit); + } + sr.setFuture(f); + } catch (RejectedExecutionException ex) { + if (parent != null) { + parent.remove(sr); + } + RxJavaPlugins.onError(ex); + } + + return sr; + } + + @Override + public void dispose() { + if (!disposed) { + disposed = true; + executor.shutdownNow(); + } + } + + /** + * Shuts down the underlying executor in a non-interrupting fashion. + */ + public void shutdown() { + if (!disposed) { + disposed = true; + executor.shutdown(); + } + } + + @Override + public boolean isDisposed() { + return disposed; + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/NonBlockingThread.java b/src/main/java/io/reactivex/internal/schedulers/NonBlockingThread.java new file mode 100755 index 0000000..cc54aad --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/NonBlockingThread.java @@ -0,0 +1,22 @@ +/** + * 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.internal.schedulers; + +/** + * Marker interface to indicate blocking is not recommended while running + * on a Scheduler with a thread type implementing it. + */ +public interface NonBlockingThread { + +} diff --git a/src/main/java/io/reactivex/internal/schedulers/RxThreadFactory.java b/src/main/java/io/reactivex/internal/schedulers/RxThreadFactory.java new file mode 100755 index 0000000..45b924e --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/RxThreadFactory.java @@ -0,0 +1,90 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A ThreadFactory that counts how many threads have been created and given a prefix, + * sets the created Thread's name to {@code prefix-count}. + */ +public final class RxThreadFactory extends AtomicLong implements ThreadFactory { + + private static final long serialVersionUID = -7789753024099756196L; + + final String prefix; + + final int priority; + + final boolean nonBlocking; + +// static volatile boolean CREATE_TRACE; + + public RxThreadFactory(String prefix) { + this(prefix, Thread.NORM_PRIORITY, false); + } + + public RxThreadFactory(String prefix, int priority) { + this(prefix, priority, false); + } + + public RxThreadFactory(String prefix, int priority, boolean nonBlocking) { + this.prefix = prefix; + this.priority = priority; + this.nonBlocking = nonBlocking; + } + + @Override + public Thread newThread(Runnable r) { + StringBuilder nameBuilder = new StringBuilder(prefix).append('-').append(incrementAndGet()); + +// if (CREATE_TRACE) { +// nameBuilder.append("\r\n"); +// for (StackTraceElement se :Thread.currentThread().getStackTrace()) { +// String s = se.toString(); +// if (s.contains("sun.reflect.")) { +// continue; +// } +// if (s.contains("junit.runners.")) { +// continue; +// } +// if (s.contains("org.gradle.internal.")) { +// continue; +// } +// if (s.contains("java.util.concurrent.ThreadPoolExecutor")) { +// continue; +// } +// nameBuilder.append(s).append("\r\n"); +// } +// } + + String name = nameBuilder.toString(); + Thread t = nonBlocking ? new RxCustomThread(r, name) : new Thread(r, name); + t.setPriority(priority); + t.setDaemon(true); + return t; + } + + @Override + public String toString() { + return "RxThreadFactory[" + prefix + "]"; + } + + static final class RxCustomThread extends Thread implements NonBlockingThread { + RxCustomThread(Runnable run, String name) { + super(run, name); + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectPeriodicTask.java b/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectPeriodicTask.java new file mode 100755 index 0000000..201847b --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectPeriodicTask.java @@ -0,0 +1,46 @@ +/** + * 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.internal.schedulers; + +import io.reactivex.plugins.RxJavaPlugins; + +/** + * A Callable to be submitted to an ExecutorService that runs a Runnable + * action periodically and manages completion/cancellation. + * @since 2.0.8 + */ +public final class ScheduledDirectPeriodicTask extends AbstractDirectTask implements Runnable { + + private static final long serialVersionUID = 1811839108042568751L; + + public ScheduledDirectPeriodicTask(Runnable runnable) { + super(runnable); + } + + @Override + public void run() { + runner = Thread.currentThread(); + try { + runnable.run(); + runner = null; + } catch (Throwable ex) { + runner = null; + lazySet(FINISHED); + RxJavaPlugins.onError(ex); + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectTask.java b/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectTask.java new file mode 100755 index 0000000..44d4ce5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/ScheduledDirectTask.java @@ -0,0 +1,45 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.Callable; + +/** + * A Callable to be submitted to an ExecutorService that runs a Runnable + * action and manages completion/cancellation. + * @since 2.0.8 + */ +public final class ScheduledDirectTask extends AbstractDirectTask implements Callable { + + private static final long serialVersionUID = 1811839108042568751L; + + public ScheduledDirectTask(Runnable runnable) { + super(runnable); + } + + @Override + public Void call() throws Exception { + runner = Thread.currentThread(); + try { + runnable.run(); + } finally { + lazySet(FINISHED); + runner = null; + } + return null; + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/ScheduledRunnable.java b/src/main/java/io/reactivex/internal/schedulers/ScheduledRunnable.java new file mode 100755 index 0000000..61218fb --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/ScheduledRunnable.java @@ -0,0 +1,140 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableContainer; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ScheduledRunnable extends AtomicReferenceArray +implements Runnable, Callable, Disposable { + + private static final long serialVersionUID = -6120223772001106981L; + final Runnable actual; + + /** Indicates that the parent tracking this task has been notified about its completion. */ + static final Object PARENT_DISPOSED = new Object(); + /** Indicates the dispose() was called from within the run/call method. */ + static final Object SYNC_DISPOSED = new Object(); + /** Indicates the dispose() was called from another thread. */ + static final Object ASYNC_DISPOSED = new Object(); + + static final Object DONE = new Object(); + + static final int PARENT_INDEX = 0; + static final int FUTURE_INDEX = 1; + static final int THREAD_INDEX = 2; + + /** + * Creates a ScheduledRunnable by wrapping the given action and setting + * up the optional parent. + * @param actual the runnable to wrap, not-null (not verified) + * @param parent the parent tracking container or null if none + */ + public ScheduledRunnable(Runnable actual, DisposableContainer parent) { + super(3); + this.actual = actual; + this.lazySet(0, parent); + } + + @Override + public Object call() { + // Being Callable saves an allocation in ThreadPoolExecutor + run(); + return null; + } + + @Override + public void run() { + lazySet(THREAD_INDEX, Thread.currentThread()); + try { + try { + actual.run(); + } catch (Throwable e) { + // Exceptions.throwIfFatal(e); nowhere to go + RxJavaPlugins.onError(e); + } + } finally { + lazySet(THREAD_INDEX, null); + Object o = get(PARENT_INDEX); + if (o != PARENT_DISPOSED && compareAndSet(PARENT_INDEX, o, DONE) && o != null) { + ((DisposableContainer)o).delete(this); + } + + for (;;) { + o = get(FUTURE_INDEX); + if (o == SYNC_DISPOSED || o == ASYNC_DISPOSED || compareAndSet(FUTURE_INDEX, o, DONE)) { + break; + } + } + } + } + + public void setFuture(Future f) { + for (;;) { + Object o = get(FUTURE_INDEX); + if (o == DONE) { + return; + } + if (o == SYNC_DISPOSED) { + f.cancel(false); + return; + } + if (o == ASYNC_DISPOSED) { + f.cancel(true); + return; + } + if (compareAndSet(FUTURE_INDEX, o, f)) { + return; + } + } + } + + @Override + public void dispose() { + for (;;) { + Object o = get(FUTURE_INDEX); + if (o == DONE || o == SYNC_DISPOSED || o == ASYNC_DISPOSED) { + break; + } + boolean async = get(THREAD_INDEX) != Thread.currentThread(); + if (compareAndSet(FUTURE_INDEX, o, async ? ASYNC_DISPOSED : SYNC_DISPOSED)) { + if (o != null) { + ((Future)o).cancel(async); + } + break; + } + } + + for (;;) { + Object o = get(PARENT_INDEX); + if (o == DONE || o == PARENT_DISPOSED || o == null) { + return; + } + if (compareAndSet(PARENT_INDEX, o, PARENT_DISPOSED)) { + ((DisposableContainer)o).delete(this); + return; + } + } + } + + @Override + public boolean isDisposed() { + Object o = get(PARENT_INDEX); + return o == PARENT_DISPOSED || o == DONE; + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/SchedulerMultiWorkerSupport.java b/src/main/java/io/reactivex/internal/schedulers/SchedulerMultiWorkerSupport.java new file mode 100755 index 0000000..af3fd93 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/SchedulerMultiWorkerSupport.java @@ -0,0 +1,51 @@ +/** + * 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.internal.schedulers; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.*; + +/** + * Allows retrieving multiple workers from the implementing + * {@link Scheduler} in a way that when asking for + * at most the parallelism level of the Scheduler, those + * {@link Scheduler.Worker} instances will be running + * with different backing threads. + *

History: 2.1.8 - experimental + * @since 2.2 + */ +public interface SchedulerMultiWorkerSupport { + + /** + * Creates the given number of {@link Scheduler.Worker} instances + * that are possibly backed by distinct threads + * and calls the specified {@code Consumer} with them. + * @param number the number of workers to create, positive + * @param callback the callback to send worker instances to + */ + void createWorkers(int number, @NonNull WorkerCallback callback); + + /** + * The callback interface for the {@link SchedulerMultiWorkerSupport#createWorkers(int, WorkerCallback)} + * method. + */ + interface WorkerCallback { + /** + * Called with the Worker index and instance. + * @param index the worker index, zero-based + * @param worker the worker instance + */ + void onWorker(int index, @NonNull Scheduler.Worker worker); + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java b/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java new file mode 100755 index 0000000..ab46504 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java @@ -0,0 +1,169 @@ +/** + * 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.internal.schedulers; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.functions.Function; + +/** + * Manages the creating of ScheduledExecutorServices and sets up purging. + */ +public final class SchedulerPoolFactory { + /** Utility class. */ + private SchedulerPoolFactory() { + throw new IllegalStateException("No instances!"); + } + + static final String PURGE_ENABLED_KEY = "rx2.purge-enabled"; + + /** + * Indicates the periodic purging of the ScheduledExecutorService is enabled. + */ + public static final boolean PURGE_ENABLED; + + static final String PURGE_PERIOD_SECONDS_KEY = "rx2.purge-period-seconds"; + + /** + * Indicates the purge period of the ScheduledExecutorServices created by create(). + */ + public static final int PURGE_PERIOD_SECONDS; + + static final AtomicReference PURGE_THREAD = + new AtomicReference(); + + // Upcast to the Map interface here to avoid 8.x compatibility issues. + // See http://stackoverflow.com/a/32955708/61158 + static final Map POOLS = + new ConcurrentHashMap(); + + /** + * Starts the purge thread if not already started. + */ + public static void start() { + tryStart(PURGE_ENABLED); + } + + static void tryStart(boolean purgeEnabled) { + if (purgeEnabled) { + for (;;) { + ScheduledExecutorService curr = PURGE_THREAD.get(); + if (curr != null) { + return; + } + ScheduledExecutorService next = Executors.newScheduledThreadPool(1, new RxThreadFactory("RxSchedulerPurge")); + if (PURGE_THREAD.compareAndSet(curr, next)) { + + next.scheduleAtFixedRate(new ScheduledTask(), PURGE_PERIOD_SECONDS, PURGE_PERIOD_SECONDS, TimeUnit.SECONDS); + + return; + } else { + next.shutdownNow(); + } + } + } + } + + /** + * Stops the purge thread. + */ + public static void shutdown() { + ScheduledExecutorService exec = PURGE_THREAD.getAndSet(null); + if (exec != null) { + exec.shutdownNow(); + } + POOLS.clear(); + } + + static { + SystemPropertyAccessor propertyAccessor = new SystemPropertyAccessor(); + PURGE_ENABLED = getBooleanProperty(true, PURGE_ENABLED_KEY, true, true, propertyAccessor); + PURGE_PERIOD_SECONDS = getIntProperty(PURGE_ENABLED, PURGE_PERIOD_SECONDS_KEY, 1, 1, propertyAccessor); + + start(); + } + + static int getIntProperty(boolean enabled, String key, int defaultNotFound, int defaultNotEnabled, Function propertyAccessor) { + if (enabled) { + try { + String value = propertyAccessor.apply(key); + if (value == null) { + return defaultNotFound; + } + return Integer.parseInt(value); + } catch (Throwable ex) { + return defaultNotFound; + } + } + return defaultNotEnabled; + } + + static boolean getBooleanProperty(boolean enabled, String key, boolean defaultNotFound, boolean defaultNotEnabled, Function propertyAccessor) { + if (enabled) { + try { + String value = propertyAccessor.apply(key); + if (value == null) { + return defaultNotFound; + } + return "true".equals(value); + } catch (Throwable ex) { + return defaultNotFound; + } + } + return defaultNotEnabled; + } + + static final class SystemPropertyAccessor implements Function { + @Override + public String apply(String t) throws Exception { + return System.getProperty(t); + } + } + + /** + * Creates a ScheduledExecutorService with the given factory. + * @param factory the thread factory + * @return the ScheduledExecutorService + */ + public static ScheduledExecutorService create(ThreadFactory factory) { + final ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, factory); + tryPutIntoPool(PURGE_ENABLED, exec); + return exec; + } + + static void tryPutIntoPool(boolean purgeEnabled, ScheduledExecutorService exec) { + if (purgeEnabled && exec instanceof ScheduledThreadPoolExecutor) { + ScheduledThreadPoolExecutor e = (ScheduledThreadPoolExecutor) exec; + POOLS.put(e, exec); + } + } + + static final class ScheduledTask implements Runnable { + @Override + public void run() { + for (ScheduledThreadPoolExecutor e : new ArrayList(POOLS.keySet())) { + if (e.isShutdown()) { + POOLS.remove(e); + } else { + e.purge(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/SchedulerWhen.java b/src/main/java/io/reactivex/internal/schedulers/SchedulerWhen.java new file mode 100755 index 0000000..23b0001 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/SchedulerWhen.java @@ -0,0 +1,348 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Completable; +import io.reactivex.CompletableObserver; +import io.reactivex.Flowable; +import io.reactivex.Observable; +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.disposables.Disposables; +import io.reactivex.functions.Function; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.processors.FlowableProcessor; +import io.reactivex.processors.UnicastProcessor; + +/** + * Allows the use of operators for controlling the timing around when actions + * scheduled on workers are actually done. This makes it possible to layer + * additional behavior on this {@link Scheduler}. The only parameter is a + * function that flattens an {@link Observable} of {@link Observable} of + * {@link Completable}s into just one {@link Completable}. There must be a chain + * of operators connecting the returned value to the source {@link Observable} + * otherwise any work scheduled on the returned {@link Scheduler} will not be + * executed. + *

+ * When {@link Scheduler#createWorker()} is invoked a {@link Observable} of + * {@link Completable}s is onNext'd to the combinator to be flattened. If the + * inner {@link Observable} is not immediately subscribed to an calls to + * {@link Worker#schedule} are buffered. Once the {@link Observable} is + * subscribed to actions are then onNext'd as {@link Completable}s. + *

+ * Finally the actions scheduled on the parent {@link Scheduler} when the inner + * most {@link Completable}s are subscribed to. + *

+ * When the {@link Worker Worker} is unsubscribed the {@link Completable} emits an + * onComplete and triggers any behavior in the flattening operator. The + * {@link Observable} and all {@link Completable}s give to the flattening + * function never onError. + *

+ * Limit the amount concurrency two at a time without creating a new fix size + * thread pool: + * + *

+ * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
+ *  // use merge max concurrent to limit the number of concurrent
+ *  // callbacks two at a time
+ *  return Completable.merge(Observable.merge(workers), 2);
+ * });
+ * 
+ *

+ * This is a slightly different way to limit the concurrency but it has some + * interesting benefits and drawbacks to the method above. It works by limited + * the number of concurrent {@link Worker Worker}s rather than individual actions. + * Generally each {@link Observable} uses its own {@link Worker Worker}. This means + * that this will essentially limit the number of concurrent subscribes. The + * danger comes from using operators like + * {@link Flowable#zip(org.reactivestreams.Publisher, org.reactivestreams.Publisher, io.reactivex.functions.BiFunction)} where + * subscribing to the first {@link Observable} could deadlock the subscription + * to the second. + * + *

+ * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
+ *  // use merge max concurrent to limit the number of concurrent
+ *  // Observables two at a time
+ *  return Completable.merge(Observable.merge(workers, 2));
+ * });
+ * 
+ * + * Slowing down the rate to no more than than 1 a second. This suffers from the + * same problem as the one above I could find an {@link Observable} operator + * that limits the rate without dropping the values (aka leaky bucket + * algorithm). + * + *
+ * Scheduler slowScheduler = Schedulers.computation().when(workers -> {
+ *  // use concatenate to make each worker happen one at a time.
+ *  return Completable.concat(workers.map(actions -> {
+ *      // delay the starting of the next worker by 1 second.
+ *      return Completable.merge(actions.delaySubscription(1, TimeUnit.SECONDS));
+ *  }));
+ * });
+ * 
+ *

History 2.0.1 - experimental + * @since 2.1 + */ +public class SchedulerWhen extends Scheduler implements Disposable { + private final Scheduler actualScheduler; + private final FlowableProcessor> workerProcessor; + private Disposable disposable; + + public SchedulerWhen(Function>, Completable> combine, Scheduler actualScheduler) { + this.actualScheduler = actualScheduler; + // workers are converted into completables and put in this queue. + this.workerProcessor = UnicastProcessor.>create().toSerialized(); + // send it to a custom combinator to pick the order and rate at which + // workers are processed. + try { + disposable = combine.apply(workerProcessor).subscribe(); + } catch (Throwable e) { + throw ExceptionHelper.wrapOrThrow(e); + } + } + + @Override + public void dispose() { + disposable.dispose(); + } + + @Override + public boolean isDisposed() { + return disposable.isDisposed(); + } + + @NonNull + @Override + public Worker createWorker() { + final Worker actualWorker = actualScheduler.createWorker(); + // a queue for the actions submitted while worker is waiting to get to + // the subscribe to off the workerQueue. + final FlowableProcessor actionProcessor = UnicastProcessor.create().toSerialized(); + // convert the work of scheduling all the actions into a completable + Flowable actions = actionProcessor.map(new CreateWorkerFunction(actualWorker)); + + // a worker that queues the action to the actionQueue subject. + Worker worker = new QueueWorker(actionProcessor, actualWorker); + + // enqueue the completable that process actions put in reply subject + workerProcessor.onNext(actions); + + // return the worker that adds actions to the reply subject + return worker; + } + + static final Disposable SUBSCRIBED = new SubscribedDisposable(); + + static final Disposable DISPOSED = Disposables.disposed(); + + @SuppressWarnings("serial") + abstract static class ScheduledAction extends AtomicReference implements Disposable { + ScheduledAction() { + super(SUBSCRIBED); + } + + void call(Worker actualWorker, CompletableObserver actionCompletable) { + Disposable oldState = get(); + // either SUBSCRIBED or UNSUBSCRIBED + if (oldState == DISPOSED) { + // no need to schedule return + return; + } + if (oldState != SUBSCRIBED) { + // has already been scheduled return + // should not be able to get here but handle it anyway by not + // rescheduling. + return; + } + + Disposable newState = callActual(actualWorker, actionCompletable); + + if (!compareAndSet(SUBSCRIBED, newState)) { + // set would only fail if the new current state is some other + // subscription from a concurrent call to this method. + // Unsubscribe from the action just scheduled because it lost + // the race. + newState.dispose(); + } + } + + protected abstract Disposable callActual(Worker actualWorker, CompletableObserver actionCompletable); + + @Override + public boolean isDisposed() { + return get().isDisposed(); + } + + @Override + public void dispose() { + Disposable oldState; + // no matter what the current state is the new state is going to be + Disposable newState = DISPOSED; + do { + oldState = get(); + if (oldState == DISPOSED) { + // the action has already been unsubscribed + return; + } + } while (!compareAndSet(oldState, newState)); + + if (oldState != SUBSCRIBED) { + // the action was scheduled. stop it. + oldState.dispose(); + } + } + } + + @SuppressWarnings("serial") + static class ImmediateAction extends ScheduledAction { + private final Runnable action; + + ImmediateAction(Runnable action) { + this.action = action; + } + + @Override + protected Disposable callActual(Worker actualWorker, CompletableObserver actionCompletable) { + return actualWorker.schedule(new OnCompletedAction(action, actionCompletable)); + } + } + + @SuppressWarnings("serial") + static class DelayedAction extends ScheduledAction { + private final Runnable action; + private final long delayTime; + private final TimeUnit unit; + + DelayedAction(Runnable action, long delayTime, TimeUnit unit) { + this.action = action; + this.delayTime = delayTime; + this.unit = unit; + } + + @Override + protected Disposable callActual(Worker actualWorker, CompletableObserver actionCompletable) { + return actualWorker.schedule(new OnCompletedAction(action, actionCompletable), delayTime, unit); + } + } + + static class OnCompletedAction implements Runnable { + final CompletableObserver actionCompletable; + final Runnable action; + + OnCompletedAction(Runnable action, CompletableObserver actionCompletable) { + this.action = action; + this.actionCompletable = actionCompletable; + } + + @Override + public void run() { + try { + action.run(); + } finally { + actionCompletable.onComplete(); + } + } + } + + static final class CreateWorkerFunction implements Function { + final Worker actualWorker; + + CreateWorkerFunction(Worker actualWorker) { + this.actualWorker = actualWorker; + } + + @Override + public Completable apply(final ScheduledAction action) { + return new WorkerCompletable(action); + } + + final class WorkerCompletable extends Completable { + final ScheduledAction action; + + WorkerCompletable(ScheduledAction action) { + this.action = action; + } + + @Override + protected void subscribeActual(CompletableObserver actionCompletable) { + actionCompletable.onSubscribe(action); + action.call(actualWorker, actionCompletable); + } + } + } + + static final class QueueWorker extends Worker { + private final AtomicBoolean unsubscribed; + private final FlowableProcessor actionProcessor; + private final Worker actualWorker; + + QueueWorker(FlowableProcessor actionProcessor, Worker actualWorker) { + this.actionProcessor = actionProcessor; + this.actualWorker = actualWorker; + unsubscribed = new AtomicBoolean(); + } + + @Override + public void dispose() { + // complete the actionQueue when worker is unsubscribed to make + // room for the next worker in the workerQueue. + if (unsubscribed.compareAndSet(false, true)) { + actionProcessor.onComplete(); + actualWorker.dispose(); + } + } + + @Override + public boolean isDisposed() { + return unsubscribed.get(); + } + + @NonNull + @Override + public Disposable schedule(@NonNull final Runnable action, final long delayTime, @NonNull final TimeUnit unit) { + // send a scheduled action to the actionQueue + DelayedAction delayedAction = new DelayedAction(action, delayTime, unit); + actionProcessor.onNext(delayedAction); + return delayedAction; + } + + @NonNull + @Override + public Disposable schedule(@NonNull final Runnable action) { + // send a scheduled action to the actionQueue + ImmediateAction immediateAction = new ImmediateAction(action); + actionProcessor.onNext(immediateAction); + return immediateAction; + } + } + + static final class SubscribedDisposable implements Disposable { + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java b/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java new file mode 100755 index 0000000..cae35f1 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/SingleScheduler.java @@ -0,0 +1,218 @@ +/** + * 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.internal.schedulers; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.*; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A scheduler with a shared, single threaded underlying ScheduledExecutorService. + * @since 2.0 + */ +public final class SingleScheduler extends Scheduler { + + final ThreadFactory threadFactory; + final AtomicReference executor = new AtomicReference(); + + /** The name of the system property for setting the thread priority for this Scheduler. */ + private static final String KEY_SINGLE_PRIORITY = "rx2.single-priority"; + + private static final String THREAD_NAME_PREFIX = "RxSingleScheduler"; + + static final RxThreadFactory SINGLE_THREAD_FACTORY; + + static final ScheduledExecutorService SHUTDOWN; + static { + SHUTDOWN = Executors.newScheduledThreadPool(0); + SHUTDOWN.shutdown(); + + int priority = Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY, + Integer.getInteger(KEY_SINGLE_PRIORITY, Thread.NORM_PRIORITY))); + + SINGLE_THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX, priority, true); + } + + public SingleScheduler() { + this(SINGLE_THREAD_FACTORY); + } + + /** + * Constructs a SingleScheduler with the given ThreadFactory and prepares the + * single scheduler thread. + * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any + * system properties for configuring new thread creation. Cannot be null. + */ + public SingleScheduler(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + executor.lazySet(createExecutor(threadFactory)); + } + + static ScheduledExecutorService createExecutor(ThreadFactory threadFactory) { + return SchedulerPoolFactory.create(threadFactory); + } + + @Override + public void start() { + ScheduledExecutorService next = null; + for (;;) { + ScheduledExecutorService current = executor.get(); + if (current != SHUTDOWN) { + if (next != null) { + next.shutdown(); + } + return; + } + if (next == null) { + next = createExecutor(threadFactory); + } + if (executor.compareAndSet(current, next)) { + return; + } + + } + } + + @Override + public void shutdown() { + ScheduledExecutorService current = executor.get(); + if (current != SHUTDOWN) { + current = executor.getAndSet(SHUTDOWN); + if (current != SHUTDOWN) { + current.shutdownNow(); + } + } + } + + @NonNull + @Override + public Worker createWorker() { + return new ScheduledWorker(executor.get()); + } + + @NonNull + @Override + public Disposable scheduleDirect(@NonNull Runnable run, long delay, TimeUnit unit) { + ScheduledDirectTask task = new ScheduledDirectTask(RxJavaPlugins.onSchedule(run)); + try { + Future f; + if (delay <= 0L) { + f = executor.get().submit(task); + } else { + f = executor.get().schedule(task, delay, unit); + } + task.setFuture(f); + return task; + } catch (RejectedExecutionException ex) { + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + } + + @NonNull + @Override + public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initialDelay, long period, TimeUnit unit) { + final Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + if (period <= 0L) { + + ScheduledExecutorService exec = executor.get(); + + InstantPeriodicTask periodicWrapper = new InstantPeriodicTask(decoratedRun, exec); + Future f; + try { + if (initialDelay <= 0L) { + f = exec.submit(periodicWrapper); + } else { + f = exec.schedule(periodicWrapper, initialDelay, unit); + } + periodicWrapper.setFirst(f); + } catch (RejectedExecutionException ex) { + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + + return periodicWrapper; + } + ScheduledDirectPeriodicTask task = new ScheduledDirectPeriodicTask(decoratedRun); + try { + Future f = executor.get().scheduleAtFixedRate(task, initialDelay, period, unit); + task.setFuture(f); + return task; + } catch (RejectedExecutionException ex) { + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + } + + static final class ScheduledWorker extends Worker { + + final ScheduledExecutorService executor; + + final CompositeDisposable tasks; + + volatile boolean disposed; + + ScheduledWorker(ScheduledExecutorService executor) { + this.executor = executor; + this.tasks = new CompositeDisposable(); + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) { + if (disposed) { + return EmptyDisposable.INSTANCE; + } + + Runnable decoratedRun = RxJavaPlugins.onSchedule(run); + + ScheduledRunnable sr = new ScheduledRunnable(decoratedRun, tasks); + tasks.add(sr); + + try { + Future f; + if (delay <= 0L) { + f = executor.submit((Callable)sr); + } else { + f = executor.schedule((Callable)sr, delay, unit); + } + + sr.setFuture(f); + } catch (RejectedExecutionException ex) { + dispose(); + RxJavaPlugins.onError(ex); + return EmptyDisposable.INSTANCE; + } + + return sr; + } + + @Override + public void dispose() { + if (!disposed) { + disposed = true; + tasks.dispose(); + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + } +} diff --git a/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java b/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java new file mode 100755 index 0000000..a9f94d8 --- /dev/null +++ b/src/main/java/io/reactivex/internal/schedulers/TrampolineScheduler.java @@ -0,0 +1,208 @@ +/** + * 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.internal.schedulers; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.*; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Schedules work on the current thread but does not execute immediately. Work is put in a queue and executed + * after the current unit of work is completed. + */ +public final class TrampolineScheduler extends Scheduler { + private static final TrampolineScheduler INSTANCE = new TrampolineScheduler(); + + public static TrampolineScheduler instance() { + return INSTANCE; + } + + @NonNull + @Override + public Worker createWorker() { + return new TrampolineWorker(); + } + + /* package accessible for unit tests */TrampolineScheduler() { + } + + @NonNull + @Override + public Disposable scheduleDirect(@NonNull Runnable run) { + RxJavaPlugins.onSchedule(run).run(); + return EmptyDisposable.INSTANCE; + } + + @NonNull + @Override + public Disposable scheduleDirect(@NonNull Runnable run, long delay, TimeUnit unit) { + try { + unit.sleep(delay); + RxJavaPlugins.onSchedule(run).run(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + RxJavaPlugins.onError(ex); + } + return EmptyDisposable.INSTANCE; + } + + static final class TrampolineWorker extends Worker implements Disposable { + final PriorityBlockingQueue queue = new PriorityBlockingQueue(); + + private final AtomicInteger wip = new AtomicInteger(); + + final AtomicInteger counter = new AtomicInteger(); + + volatile boolean disposed; + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable action) { + return enqueue(action, now(TimeUnit.MILLISECONDS)); + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable action, long delayTime, @NonNull TimeUnit unit) { + long execTime = now(TimeUnit.MILLISECONDS) + unit.toMillis(delayTime); + + return enqueue(new SleepingRunnable(action, this, execTime), execTime); + } + + Disposable enqueue(Runnable action, long execTime) { + if (disposed) { + return EmptyDisposable.INSTANCE; + } + final TimedRunnable timedRunnable = new TimedRunnable(action, execTime, counter.incrementAndGet()); + queue.add(timedRunnable); + + if (wip.getAndIncrement() == 0) { + int missed = 1; + for (;;) { + for (;;) { + if (disposed) { + queue.clear(); + return EmptyDisposable.INSTANCE; + } + final TimedRunnable polled = queue.poll(); + if (polled == null) { + break; + } + if (!polled.disposed) { + polled.run.run(); + } + } + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + + return EmptyDisposable.INSTANCE; + } else { + // queue wasn't empty, a parent is already processing so we just add to the end of the queue + return Disposables.fromRunnable(new AppendToQueueTask(timedRunnable)); + } + } + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + final class AppendToQueueTask implements Runnable { + final TimedRunnable timedRunnable; + + AppendToQueueTask(TimedRunnable timedRunnable) { + this.timedRunnable = timedRunnable; + } + + @Override + public void run() { + timedRunnable.disposed = true; + queue.remove(timedRunnable); + } + } + } + + static final class TimedRunnable implements Comparable { + final Runnable run; + final long execTime; + final int count; // In case if time between enqueueing took less than 1ms + + volatile boolean disposed; + + TimedRunnable(Runnable run, Long execTime, int count) { + this.run = run; + this.execTime = execTime; + this.count = count; + } + + @Override + public int compareTo(TimedRunnable that) { + int result = ObjectHelper.compare(execTime, that.execTime); + if (result == 0) { + return ObjectHelper.compare(count, that.count); + } + return result; + } + } + + static final class SleepingRunnable implements Runnable { + private final Runnable run; + private final TrampolineWorker worker; + private final long execTime; + + SleepingRunnable(Runnable run, TrampolineWorker worker, long execTime) { + this.run = run; + this.worker = worker; + this.execTime = execTime; + } + + @Override + public void run() { + if (!worker.disposed) { + long t = worker.now(TimeUnit.MILLISECONDS); + if (execTime > t) { + long delay = execTime - t; + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + RxJavaPlugins.onError(e); + return; + } + } + + if (!worker.disposed) { + run.run(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/BasicFuseableConditionalSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BasicFuseableConditionalSubscriber.java new file mode 100755 index 0000000..f665d48 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/BasicFuseableConditionalSubscriber.java @@ -0,0 +1,183 @@ +/** + * 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.internal.subscribers; + +import org.reactivestreams.Subscription; + +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Base class for a fuseable intermediate subscriber. + * @param the upstream value type + * @param the downstream value type + */ +public abstract class BasicFuseableConditionalSubscriber implements ConditionalSubscriber, QueueSubscription { + + /** The downstream subscriber. */ + protected final ConditionalSubscriber downstream; + + /** The upstream subscription. */ + protected Subscription upstream; + + /** The upstream's QueueSubscription if not null. */ + protected QueueSubscription qs; + + /** Flag indicating no further onXXX event should be accepted. */ + protected boolean done; + + /** Holds the established fusion mode of the upstream. */ + protected int sourceMode; + + /** + * Construct a BasicFuseableSubscriber by wrapping the given subscriber. + * @param downstream the subscriber, not null (not verified) + */ + public BasicFuseableConditionalSubscriber(ConditionalSubscriber downstream) { + this.downstream = downstream; + } + + // final: fixed protocol steps to support fuseable and non-fuseable upstream + @SuppressWarnings("unchecked") + @Override + public final void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + + this.upstream = s; + if (s instanceof QueueSubscription) { + this.qs = (QueueSubscription)s; + } + + if (beforeDownstream()) { + + downstream.onSubscribe(this); + + afterDownstream(); + } + + } + } + + /** + * Override this to perform actions before the call {@code actual.onSubscribe(this)} happens. + * @return true if onSubscribe should continue with the call + */ + protected boolean beforeDownstream() { + return true; + } + + /** + * Override this to perform actions after the call to {@code actual.onSubscribe(this)} happened. + */ + protected void afterDownstream() { + // default no-op + } + + // ----------------------------------- + // Convenience and state-aware methods + // ----------------------------------- + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + /** + * Rethrows the throwable if it is a fatal exception or calls {@link #onError(Throwable)}. + * @param t the throwable to rethrow or signal to the actual subscriber + */ + protected final void fail(Throwable t) { + Exceptions.throwIfFatal(t); + upstream.cancel(); + onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + /** + * Calls the upstream's QueueSubscription.requestFusion with the mode and + * saves the established mode in {@link #sourceMode} if that mode doesn't + * have the {@link QueueSubscription#BOUNDARY} flag set. + *

+ * If the upstream doesn't support fusion ({@link #qs} is null), the method + * returns {@link QueueSubscription#NONE}. + * @param mode the fusion mode requested + * @return the established fusion mode + */ + protected final int transitiveBoundaryFusion(int mode) { + QueueSubscription qs = this.qs; + if (qs != null) { + if ((mode & BOUNDARY) == 0) { + int m = qs.requestFusion(mode); + if (m != NONE) { + sourceMode = m; + } + return m; + } + } + return NONE; + } + + // -------------------------------------------------------------- + // Default implementation of the RS and QS protocol (can be overridden) + // -------------------------------------------------------------- + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public boolean isEmpty() { + return qs.isEmpty(); + } + + @Override + public void clear() { + qs.clear(); + } + + // ----------------------------------------------------------- + // The rest of the Queue interface methods shouldn't be called + // ----------------------------------------------------------- + + @Override + public final boolean offer(R e) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public final boolean offer(R v1, R v2) { + throw new UnsupportedOperationException("Should not be called!"); + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/BasicFuseableSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BasicFuseableSubscriber.java new file mode 100755 index 0000000..945d311 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/BasicFuseableSubscriber.java @@ -0,0 +1,184 @@ +/** + * 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.internal.subscribers; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Base class for a fuseable intermediate subscriber. + * @param the upstream value type + * @param the downstream value type + */ +public abstract class BasicFuseableSubscriber implements FlowableSubscriber, QueueSubscription { + + /** The downstream subscriber. */ + protected final Subscriber downstream; + + /** The upstream subscription. */ + protected Subscription upstream; + + /** The upstream's QueueSubscription if not null. */ + protected QueueSubscription qs; + + /** Flag indicating no further onXXX event should be accepted. */ + protected boolean done; + + /** Holds the established fusion mode of the upstream. */ + protected int sourceMode; + + /** + * Construct a BasicFuseableSubscriber by wrapping the given subscriber. + * @param downstream the subscriber, not null (not verified) + */ + public BasicFuseableSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + // final: fixed protocol steps to support fuseable and non-fuseable upstream + @SuppressWarnings("unchecked") + @Override + public final void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + + this.upstream = s; + if (s instanceof QueueSubscription) { + this.qs = (QueueSubscription)s; + } + + if (beforeDownstream()) { + + downstream.onSubscribe(this); + + afterDownstream(); + } + + } + } + + /** + * Override this to perform actions before the call {@code actual.onSubscribe(this)} happens. + * @return true if onSubscribe should continue with the call + */ + protected boolean beforeDownstream() { + return true; + } + + /** + * Override this to perform actions after the call to {@code actual.onSubscribe(this)} happened. + */ + protected void afterDownstream() { + // default no-op + } + + // ----------------------------------- + // Convenience and state-aware methods + // ----------------------------------- + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + downstream.onError(t); + } + + /** + * Rethrows the throwable if it is a fatal exception or calls {@link #onError(Throwable)}. + * @param t the throwable to rethrow or signal to the actual subscriber + */ + protected final void fail(Throwable t) { + Exceptions.throwIfFatal(t); + upstream.cancel(); + onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + downstream.onComplete(); + } + + /** + * Calls the upstream's QueueSubscription.requestFusion with the mode and + * saves the established mode in {@link #sourceMode} if that mode doesn't + * have the {@link QueueSubscription#BOUNDARY} flag set. + *

+ * If the upstream doesn't support fusion ({@link #qs} is null), the method + * returns {@link QueueSubscription#NONE}. + * @param mode the fusion mode requested + * @return the established fusion mode + */ + protected final int transitiveBoundaryFusion(int mode) { + QueueSubscription qs = this.qs; + if (qs != null) { + if ((mode & BOUNDARY) == 0) { + int m = qs.requestFusion(mode); + if (m != NONE) { + sourceMode = m; + } + return m; + } + } + return NONE; + } + + // -------------------------------------------------------------- + // Default implementation of the RS and QS protocol (can be overridden) + // -------------------------------------------------------------- + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } + + @Override + public boolean isEmpty() { + return qs.isEmpty(); + } + + @Override + public void clear() { + qs.clear(); + } + + // ----------------------------------------------------------- + // The rest of the Queue interface methods shouldn't be called + // ----------------------------------------------------------- + + @Override + public final boolean offer(R e) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public final boolean offer(R v1, R v2) { + throw new UnsupportedOperationException("Should not be called!"); + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/BlockingBaseSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BlockingBaseSubscriber.java new file mode 100755 index 0000000..72a1374 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/BlockingBaseSubscriber.java @@ -0,0 +1,82 @@ +/** + * 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.internal.subscribers; + +import java.util.concurrent.CountDownLatch; + +import org.reactivestreams.Subscription; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; + +public abstract class BlockingBaseSubscriber extends CountDownLatch +implements FlowableSubscriber { + + T value; + Throwable error; + + Subscription upstream; + + volatile boolean cancelled; + + public BlockingBaseSubscriber() { + super(1); + } + + @Override + public final void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + if (!cancelled) { + s.request(Long.MAX_VALUE); + if (cancelled) { + this.upstream = SubscriptionHelper.CANCELLED; + s.cancel(); + } + } + } + } + + @Override + public final void onComplete() { + countDown(); + } + + /** + * Block until the first value arrives and return it, otherwise + * return null for an empty source and rethrow any exception. + * @return the first value or null if the source is empty + */ + public final T blockingGet() { + if (getCount() != 0) { + try { + BlockingHelper.verifyNonBlocking(); + await(); + } catch (InterruptedException ex) { + Subscription s = this.upstream; + this.upstream = SubscriptionHelper.CANCELLED; + if (s != null) { + s.cancel(); + } + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + return value; + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/BlockingFirstSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BlockingFirstSubscriber.java new file mode 100755 index 0000000..57fd446 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/BlockingFirstSubscriber.java @@ -0,0 +1,43 @@ +/** + * 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.internal.subscribers; + +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Blocks until the upstream signals its first value or completes. + * + * @param the value type + */ +public final class BlockingFirstSubscriber extends BlockingBaseSubscriber { + + @Override + public void onNext(T t) { + if (value == null) { + value = t; + upstream.cancel(); + countDown(); + } + } + + @Override + public void onError(Throwable t) { + if (value == null) { + error = t; + } else { + RxJavaPlugins.onError(t); + } + countDown(); + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/BlockingLastSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BlockingLastSubscriber.java new file mode 100755 index 0000000..e851b6b --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/BlockingLastSubscriber.java @@ -0,0 +1,34 @@ +/** + * 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.internal.subscribers; + +/** + * Blocks until the upstream signals its last value or completes. + * + * @param the value type + */ +public final class BlockingLastSubscriber extends BlockingBaseSubscriber { + + @Override + public void onNext(T t) { + value = t; + } + + @Override + public void onError(Throwable t) { + value = null; + error = t; + countDown(); + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/BlockingSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BlockingSubscriber.java new file mode 100755 index 0000000..5f84aef --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/BlockingSubscriber.java @@ -0,0 +1,74 @@ +/** + * 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.internal.subscribers; + +import java.util.Queue; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Subscription; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.NotificationLite; + +public final class BlockingSubscriber extends AtomicReference implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -4875965440900746268L; + + public static final Object TERMINATED = new Object(); + + final Queue queue; + + public BlockingSubscriber(Queue queue) { + this.queue = queue; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + queue.offer(NotificationLite.subscription(this)); + } + } + + @Override + public void onNext(T t) { + queue.offer(NotificationLite.next(t)); + } + + @Override + public void onError(Throwable t) { + queue.offer(NotificationLite.error(t)); + } + + @Override + public void onComplete() { + queue.offer(NotificationLite.complete()); + } + + @Override + public void request(long n) { + get().request(n); + } + + @Override + public void cancel() { + if (SubscriptionHelper.cancel(this)) { + queue.offer(TERMINATED); + } + } + + public boolean isCancelled() { + return get() == SubscriptionHelper.CANCELLED; + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/BoundedSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/BoundedSubscriber.java new file mode 100755 index 0000000..5adbfeb --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/BoundedSubscriber.java @@ -0,0 +1,140 @@ +/** + * 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.internal.subscribers; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.observers.LambdaConsumerIntrospection; +import io.reactivex.plugins.RxJavaPlugins; +import org.reactivestreams.Subscription; + +import java.util.concurrent.atomic.AtomicReference; + +public final class BoundedSubscriber extends AtomicReference + implements FlowableSubscriber, Subscription, Disposable, LambdaConsumerIntrospection { + + private static final long serialVersionUID = -7251123623727029452L; + final Consumer onNext; + final Consumer onError; + final Action onComplete; + final Consumer onSubscribe; + + final int bufferSize; + int consumed; + final int limit; + + public BoundedSubscriber(Consumer onNext, Consumer onError, + Action onComplete, Consumer onSubscribe, int bufferSize) { + super(); + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + this.onSubscribe = onSubscribe; + this.bufferSize = bufferSize; + this.limit = bufferSize - (bufferSize >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + try { + onSubscribe.accept(this); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + s.cancel(); + onError(e); + } + } + } + + @Override + public void onNext(T t) { + if (!isDisposed()) { + try { + onNext.accept(t); + + int c = consumed + 1; + if (c == limit) { + consumed = 0; + get().request(limit); + } else { + consumed = c; + } + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + get().cancel(); + onError(e); + } + } + } + + @Override + public void onError(Throwable t) { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + try { + onError.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(new CompositeException(t, e)); + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + try { + onComplete.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + } + + @Override + public void dispose() { + cancel(); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; + } + + @Override + public void request(long n) { + get().request(n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } +} \ No newline at end of file diff --git a/src/main/java/io/reactivex/internal/subscribers/DeferredScalarSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/DeferredScalarSubscriber.java new file mode 100755 index 0000000..701b4a4 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/DeferredScalarSubscriber.java @@ -0,0 +1,77 @@ +/** + * 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.internal.subscribers; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.subscriptions.*; + +/** + * A subscriber, extending a DeferredScalarSubscription, + * that is unbounded-in and can generate 0 or 1 resulting value. + * @param the input value type + * @param the output value type + */ +public abstract class DeferredScalarSubscriber extends DeferredScalarSubscription +implements FlowableSubscriber { + + private static final long serialVersionUID = 2984505488220891551L; + + /** The upstream subscription. */ + protected Subscription upstream; + + /** Can indicate if there was at least on onNext call. */ + protected boolean hasValue; + + /** + * Creates a DeferredScalarSubscriber instance and wraps a downstream Subscriber. + * @param downstream the downstream subscriber, not null (not verified) + */ + public DeferredScalarSubscriber(Subscriber downstream) { + super(downstream); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + + downstream.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onError(Throwable t) { + value = null; + downstream.onError(t); + } + + @Override + public void onComplete() { + if (hasValue) { + complete(value); + } else { + downstream.onComplete(); + } + } + + @Override + public void cancel() { + super.cancel(); + upstream.cancel(); + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java new file mode 100755 index 0000000..5e15bea --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/ForEachWhileSubscriber.java @@ -0,0 +1,113 @@ +/** + * 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.internal.subscribers; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Subscription; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class ForEachWhileSubscriber +extends AtomicReference +implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = -4403180040475402120L; + + final Predicate onNext; + + final Consumer onError; + + final Action onComplete; + + boolean done; + + public ForEachWhileSubscriber(Predicate onNext, + Consumer onError, Action onComplete) { + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this, s, Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + boolean b; + try { + b = onNext.test(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + dispose(); + onError(ex); + return; + } + + if (!b) { + dispose(); + onComplete(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + try { + onError.accept(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(new CompositeException(t, ex)); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + try { + onComplete.run(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean isDisposed() { + return this.get() == SubscriptionHelper.CANCELLED; + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java new file mode 100755 index 0000000..4b2c329 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/FutureSubscriber.java @@ -0,0 +1,171 @@ +/** + * 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.internal.subscribers; + +import java.util.NoSuchElementException; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Subscription; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BlockingHelper; +import io.reactivex.plugins.RxJavaPlugins; + +import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; + +/** + * A Subscriber + Future that expects exactly one upstream value and provides it + * via the (blocking) Future API. + * + * @param the value type + */ +public final class FutureSubscriber extends CountDownLatch +implements FlowableSubscriber, Future, Subscription { + + T value; + Throwable error; + + final AtomicReference upstream; + + public FutureSubscriber() { + super(1); + this.upstream = new AtomicReference(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + for (;;) { + Subscription a = upstream.get(); + if (a == this || a == SubscriptionHelper.CANCELLED) { + return false; + } + + if (upstream.compareAndSet(a, SubscriptionHelper.CANCELLED)) { + if (a != null) { + a.cancel(); + } + countDown(); + return true; + } + } + } + + @Override + public boolean isCancelled() { + return upstream.get() == SubscriptionHelper.CANCELLED; + } + + @Override + public boolean isDone() { + return getCount() == 0; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + if (getCount() != 0) { + BlockingHelper.verifyNonBlocking(); + await(); + } + + if (isCancelled()) { + throw new CancellationException(); + } + Throwable ex = error; + if (ex != null) { + throw new ExecutionException(ex); + } + return value; + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + if (getCount() != 0) { + BlockingHelper.verifyNonBlocking(); + if (!await(timeout, unit)) { + throw new TimeoutException(timeoutMessage(timeout, unit)); + } + } + + if (isCancelled()) { + throw new CancellationException(); + } + + Throwable ex = error; + if (ex != null) { + throw new ExecutionException(ex); + } + return value; + } + + @Override + public void onSubscribe(Subscription s) { + SubscriptionHelper.setOnce(this.upstream, s, Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + if (value != null) { + upstream.get().cancel(); + onError(new IndexOutOfBoundsException("More than one element received")); + return; + } + value = t; + } + + @Override + public void onError(Throwable t) { + for (;;) { + Subscription a = upstream.get(); + if (a == this || a == SubscriptionHelper.CANCELLED) { + RxJavaPlugins.onError(t); + return; + } + error = t; + if (upstream.compareAndSet(a, this)) { + countDown(); + return; + } + } + } + + @Override + public void onComplete() { + if (value == null) { + onError(new NoSuchElementException("The source is empty")); + return; + } + for (;;) { + Subscription a = upstream.get(); + if (a == this || a == SubscriptionHelper.CANCELLED) { + return; + } + if (upstream.compareAndSet(a, this)) { + countDown(); + return; + } + } + } + + @Override + public void cancel() { + // ignoring as `this` means a finished Subscription only + } + + @Override + public void request(long n) { + // ignoring as `this` means a finished Subscription only + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java new file mode 100755 index 0000000..70ea267 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriber.java @@ -0,0 +1,146 @@ +/** + * 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.internal.subscribers; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Subscription; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.QueueDrainHelper; + +/** + * Subscriber that can fuse with the upstream and calls a support interface + * whenever an event is available. + * + * @param the value type + */ +public final class InnerQueuedSubscriber +extends AtomicReference +implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = 22876611072430776L; + + final InnerQueuedSubscriberSupport parent; + + final int prefetch; + + final int limit; + + volatile SimpleQueue queue; + + volatile boolean done; + + long produced; + + int fusionMode; + + public InnerQueuedSubscriber(InnerQueuedSubscriberSupport parent, int prefetch) { + this.parent = parent; + this.prefetch = prefetch; + this.limit = prefetch - (prefetch >> 2); + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY); + if (m == QueueSubscription.SYNC) { + fusionMode = m; + queue = qs; + done = true; + parent.innerComplete(this); + return; + } + if (m == QueueSubscription.ASYNC) { + fusionMode = m; + queue = qs; + QueueDrainHelper.request(s, prefetch); + return; + } + } + + queue = QueueDrainHelper.createQueue(prefetch); + + QueueDrainHelper.request(s, prefetch); + } + } + + @Override + public void onNext(T t) { + if (fusionMode == QueueSubscription.NONE) { + parent.innerNext(this, t); + } else { + parent.drain(); + } + } + + @Override + public void onError(Throwable t) { + parent.innerError(this, t); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + @Override + public void request(long n) { + if (fusionMode != QueueSubscription.SYNC) { + long p = produced + n; + if (p >= limit) { + produced = 0L; + get().request(p); + } else { + produced = p; + } + } + } + + public void requestOne() { + if (fusionMode != QueueSubscription.SYNC) { + long p = produced + 1; + if (p == limit) { + produced = 0L; + get().request(p); + } else { + produced = p; + } + } + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(this); + } + + public boolean isDone() { + return done; + } + + public void setDone() { + this.done = true; + } + + public SimpleQueue queue() { + return queue; + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberSupport.java b/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberSupport.java new file mode 100755 index 0000000..6a87c96 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/InnerQueuedSubscriberSupport.java @@ -0,0 +1,31 @@ +/** + * 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.internal.subscribers; + +/** + * Interface to allow the InnerQueuedSubscriber to call back a parent + * with signals. + * + * @param the value type + */ +public interface InnerQueuedSubscriberSupport { + + void innerNext(InnerQueuedSubscriber inner, T value); + + void innerError(InnerQueuedSubscriber inner, Throwable e); + + void innerComplete(InnerQueuedSubscriber inner); + + void drain(); +} diff --git a/src/main/java/io/reactivex/internal/subscribers/LambdaSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/LambdaSubscriber.java new file mode 100755 index 0000000..5568cf5 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/LambdaSubscriber.java @@ -0,0 +1,126 @@ +/** + * 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.internal.subscribers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.internal.functions.Functions; +import io.reactivex.observers.LambdaConsumerIntrospection; +import org.reactivestreams.Subscription; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +public final class LambdaSubscriber extends AtomicReference + implements FlowableSubscriber, Subscription, Disposable, LambdaConsumerIntrospection { + + private static final long serialVersionUID = -7251123623727029452L; + final Consumer onNext; + final Consumer onError; + final Action onComplete; + final Consumer onSubscribe; + + public LambdaSubscriber(Consumer onNext, Consumer onError, + Action onComplete, + Consumer onSubscribe) { + super(); + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + this.onSubscribe = onSubscribe; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this, s)) { + try { + onSubscribe.accept(this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + s.cancel(); + onError(ex); + } + } + } + + @Override + public void onNext(T t) { + if (!isDisposed()) { + try { + onNext.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + get().cancel(); + onError(e); + } + } + } + + @Override + public void onError(Throwable t) { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + try { + onError.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(new CompositeException(t, e)); + } + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (get() != SubscriptionHelper.CANCELLED) { + lazySet(SubscriptionHelper.CANCELLED); + try { + onComplete.run(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + } + + @Override + public void dispose() { + cancel(); + } + + @Override + public boolean isDisposed() { + return get() == SubscriptionHelper.CANCELLED; + } + + @Override + public void request(long n) { + get().request(n); + } + + @Override + public void cancel() { + SubscriptionHelper.cancel(this); + } + + @Override + public boolean hasCustomOnError() { + return onError != Functions.ON_ERROR_MISSING; + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/QueueDrainSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/QueueDrainSubscriber.java new file mode 100755 index 0000000..d07f748 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/QueueDrainSubscriber.java @@ -0,0 +1,196 @@ +/** + * 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.internal.subscribers; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.Subscriber; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; + +/** + * Abstract base class for subscribers that hold another subscriber, a queue + * and requires queue-drain behavior. + * + * @param the source type to which this subscriber will be subscribed + * @param the value type in the queue + * @param the value type the child subscriber accepts + */ +public abstract class QueueDrainSubscriber extends QueueDrainSubscriberPad4 implements FlowableSubscriber, QueueDrain { + + protected final Subscriber downstream; + + protected final SimplePlainQueue queue; + + protected volatile boolean cancelled; + + protected volatile boolean done; + protected Throwable error; + + public QueueDrainSubscriber(Subscriber actual, SimplePlainQueue queue) { + this.downstream = actual; + this.queue = queue; + } + + @Override + public final boolean cancelled() { + return cancelled; + } + + @Override + public final boolean done() { + return done; + } + + @Override + public final boolean enter() { + return wip.getAndIncrement() == 0; + } + + public final boolean fastEnter() { + return wip.get() == 0 && wip.compareAndSet(0, 1); + } + + protected final void fastPathEmitMax(U value, boolean delayError, Disposable dispose) { + final Subscriber s = downstream; + final SimplePlainQueue q = queue; + + if (fastEnter()) { + long r = requested.get(); + if (r != 0L) { + if (accept(s, value)) { + if (r != Long.MAX_VALUE) { + produced(1); + } + } + if (leave(-1) == 0) { + return; + } + } else { + dispose.dispose(); + s.onError(new MissingBackpressureException("Could not emit buffer due to lack of requests")); + return; + } + } else { + q.offer(value); + if (!enter()) { + return; + } + } + QueueDrainHelper.drainMaxLoop(q, s, delayError, dispose, this); + } + + protected final void fastPathOrderedEmitMax(U value, boolean delayError, Disposable dispose) { + final Subscriber s = downstream; + final SimplePlainQueue q = queue; + + if (fastEnter()) { + long r = requested.get(); + if (r != 0L) { + if (q.isEmpty()) { + if (accept(s, value)) { + if (r != Long.MAX_VALUE) { + produced(1); + } + } + if (leave(-1) == 0) { + return; + } + } else { + q.offer(value); + } + } else { + cancelled = true; + dispose.dispose(); + s.onError(new MissingBackpressureException("Could not emit buffer due to lack of requests")); + return; + } + } else { + q.offer(value); + if (!enter()) { + return; + } + } + QueueDrainHelper.drainMaxLoop(q, s, delayError, dispose, this); + } + + @Override + public boolean accept(Subscriber a, U v) { + return false; + } + + @Override + public final Throwable error() { + return error; + } + + @Override + public final int leave(int m) { + return wip.addAndGet(m); + } + + @Override + public final long requested() { + return requested.get(); + } + + @Override + public final long produced(long n) { + return requested.addAndGet(-n); + } + + public final void requested(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + } + } + +} + +// ------------------------------------------------------------------- +// Padding superclasses +//------------------------------------------------------------------- + +/** Pads the header away from other fields. */ +class QueueDrainSubscriberPad0 { + volatile long p1, p2, p3, p4, p5, p6, p7; + volatile long p8, p9, p10, p11, p12, p13, p14, p15; +} + +/** The WIP counter. */ +class QueueDrainSubscriberWip extends QueueDrainSubscriberPad0 { + final AtomicInteger wip = new AtomicInteger(); +} + +/** Pads away the wip from the other fields. */ +class QueueDrainSubscriberPad2 extends QueueDrainSubscriberWip { + volatile long p1a, p2a, p3a, p4a, p5a, p6a, p7a; + volatile long p8a, p9a, p10a, p11a, p12a, p13a, p14a, p15a; +} + +/** Contains the requested field. */ +class QueueDrainSubscriberPad3 extends QueueDrainSubscriberPad2 { + final AtomicLong requested = new AtomicLong(); +} + +/** Pads away the requested from the other fields. */ +class QueueDrainSubscriberPad4 extends QueueDrainSubscriberPad3 { + volatile long q1, q2, q3, q4, q5, q6, q7; + volatile long q8, q9, q10, q11, q12, q13, q14, q15; +} diff --git a/src/main/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriber.java new file mode 100755 index 0000000..4ea2ac2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/SinglePostCompleteSubscriber.java @@ -0,0 +1,126 @@ +/** + * 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.internal.subscribers; + +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; + +/** + * Relays signals from upstream according to downstream requests and allows + * signalling a final value followed by onComplete in a backpressure-aware manner. + * + * @param the input value type + * @param the output value type + */ +public abstract class SinglePostCompleteSubscriber extends AtomicLong implements FlowableSubscriber, Subscription { + private static final long serialVersionUID = 7917814472626990048L; + + /** The downstream consumer. */ + protected final Subscriber downstream; + + /** The upstream subscription. */ + protected Subscription upstream; + + /** The last value stored in case there is no request for it. */ + protected R value; + + /** Number of values emitted so far. */ + protected long produced; + + /** Masks out the 2^63 bit indicating a completed state. */ + static final long COMPLETE_MASK = Long.MIN_VALUE; + /** Masks out the lower 63 bit holding the current request amount. */ + static final long REQUEST_MASK = Long.MAX_VALUE; + + public SinglePostCompleteSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + /** + * Signals the given value and an onComplete if the downstream is ready to receive the final value. + * @param n the value to emit + */ + protected final void complete(R n) { + long p = produced; + if (p != 0) { + BackpressureHelper.produced(this, p); + } + + for (;;) { + long r = get(); + if ((r & COMPLETE_MASK) != 0) { + onDrop(n); + return; + } + if ((r & REQUEST_MASK) != 0) { + lazySet(COMPLETE_MASK + 1); + downstream.onNext(n); + downstream.onComplete(); + return; + } + value = n; + if (compareAndSet(0, COMPLETE_MASK)) { + return; + } + value = null; + } + } + + /** + * Called in case of multiple calls to complete. + * @param n the value dropped + */ + protected void onDrop(R n) { + // default is no-op + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + for (;;) { + long r = get(); + if ((r & COMPLETE_MASK) != 0) { + if (compareAndSet(COMPLETE_MASK, COMPLETE_MASK + 1)) { + downstream.onNext(value); + downstream.onComplete(); + } + break; + } + long u = BackpressureHelper.addCap(r, n); + if (compareAndSet(r, u)) { + upstream.request(n); + break; + } + } + } + } + + @Override + public void cancel() { + upstream.cancel(); + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java new file mode 100755 index 0000000..c7770bf --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java @@ -0,0 +1,111 @@ +/** + * 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.internal.subscribers; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; + +/** + * Ensures that the event flow between the upstream and downstream follow + * the Reactive Streams 1.0 specification by honoring the 3 additional rules + * (which are omitted in standard operators due to performance reasons). + *
    + *
  • §1.3: onNext should not be called concurrently until onSubscribe returns
  • + *
  • §2.3: onError or onComplete must not call cancel
  • + *
  • §3.9: negative requests should emit an onError(IllegalArgumentException)
  • + *
+ * In addition, if rule §2.12 (onSubscribe must be called at most once) is violated, + * the sequence is cancelled an onError(IllegalStateException) is emitted. + * @param the value type + * @since 2.0.7 + */ +public class StrictSubscriber +extends AtomicInteger +implements FlowableSubscriber, Subscription { + + private static final long serialVersionUID = -4945028590049415624L; + + final Subscriber downstream; + + final AtomicThrowable error; + + final AtomicLong requested; + + final AtomicReference upstream; + + final AtomicBoolean once; + + volatile boolean done; + + public StrictSubscriber(Subscriber downstream) { + this.downstream = downstream; + this.error = new AtomicThrowable(); + this.requested = new AtomicLong(); + this.upstream = new AtomicReference(); + this.once = new AtomicBoolean(); + } + + @Override + public void request(long n) { + if (n <= 0) { + cancel(); + onError(new IllegalArgumentException("§3.9 violated: positive request amount required but it was " + n)); + } else { + SubscriptionHelper.deferredRequest(upstream, requested, n); + } + } + + @Override + public void cancel() { + if (!done) { + SubscriptionHelper.cancel(upstream); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (once.compareAndSet(false, true)) { + + downstream.onSubscribe(this); + + SubscriptionHelper.deferredSetOnce(this.upstream, requested, s); + } else { + s.cancel(); + cancel(); + onError(new IllegalStateException("§2.12 violated: onSubscribe must be called at most once")); + } + } + + @Override + public void onNext(T t) { + HalfSerializer.onNext(downstream, t, this, error); + } + + @Override + public void onError(Throwable t) { + done = true; + HalfSerializer.onError(downstream, t, this, error); + } + + @Override + public void onComplete() { + done = true; + HalfSerializer.onComplete(downstream, this, error); + } +} diff --git a/src/main/java/io/reactivex/internal/subscribers/SubscriberResourceWrapper.java b/src/main/java/io/reactivex/internal/subscribers/SubscriberResourceWrapper.java new file mode 100755 index 0000000..807430b --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscribers/SubscriberResourceWrapper.java @@ -0,0 +1,88 @@ +/** + * 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.internal.subscribers; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; + +public final class SubscriberResourceWrapper extends AtomicReference implements FlowableSubscriber, Disposable, Subscription { + + private static final long serialVersionUID = -8612022020200669122L; + + final Subscriber downstream; + + final AtomicReference upstream = new AtomicReference(); + + public SubscriberResourceWrapper(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(upstream, s)) { + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + DisposableHelper.dispose(this); + downstream.onError(t); + } + + @Override + public void onComplete() { + DisposableHelper.dispose(this); + downstream.onComplete(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + upstream.get().request(n); + } + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(upstream); + + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return upstream.get() == SubscriptionHelper.CANCELLED; + } + + @Override + public void cancel() { + dispose(); + } + + public void setResource(Disposable resource) { + DisposableHelper.set(this, resource); + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/ArrayCompositeSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/ArrayCompositeSubscription.java new file mode 100755 index 0000000..b035e23 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/ArrayCompositeSubscription.java @@ -0,0 +1,102 @@ +/** + * 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.internal.subscriptions; + +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.reactivestreams.Subscription; + +import io.reactivex.disposables.Disposable; + +/** + * A composite disposable with a fixed number of slots. + * + *

Note that since the implementation leaks the methods of AtomicReferenceArray, one must be + * careful to only call setResource, replaceResource and dispose on it. All other methods may lead to undefined behavior + * and should be used by internal means only. + */ +public final class ArrayCompositeSubscription extends AtomicReferenceArray implements Disposable { + + private static final long serialVersionUID = 2746389416410565408L; + + public ArrayCompositeSubscription(int capacity) { + super(capacity); + } + + /** + * Sets the resource at the specified index and disposes the old resource. + * @param index the index of the resource to set + * @param resource the new resource + * @return true if the resource has ben set, false if the composite has been disposed + */ + public boolean setResource(int index, Subscription resource) { + for (;;) { + Subscription o = get(index); + if (o == SubscriptionHelper.CANCELLED) { + if (resource != null) { + resource.cancel(); + } + return false; + } + if (compareAndSet(index, o, resource)) { + if (o != null) { + o.cancel(); + } + return true; + } + } + } + + /** + * Replaces the resource at the specified index and returns the old resource. + * @param index the index of the resource to replace + * @param resource the new resource + * @return the old resource, can be null + */ + public Subscription replaceResource(int index, Subscription resource) { + for (;;) { + Subscription o = get(index); + if (o == SubscriptionHelper.CANCELLED) { + if (resource != null) { + resource.cancel(); + } + return null; + } + if (compareAndSet(index, o, resource)) { + return o; + } + } + } + + @Override + public void dispose() { + if (get(0) != SubscriptionHelper.CANCELLED) { + int s = length(); + for (int i = 0; i < s; i++) { + Subscription o = get(i); + if (o != SubscriptionHelper.CANCELLED) { + o = getAndSet(i, SubscriptionHelper.CANCELLED); + if (o != SubscriptionHelper.CANCELLED && o != null) { + o.cancel(); + } + } + } + } + } + + @Override + public boolean isDisposed() { + return get(0) == SubscriptionHelper.CANCELLED; + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/AsyncSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/AsyncSubscription.java new file mode 100755 index 0000000..4a075ce --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/AsyncSubscription.java @@ -0,0 +1,94 @@ +/** + * 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.internal.subscriptions; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.Subscription; + +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; + +/** + * A subscription implementation that arbitrates exactly one other Subscription and can + * hold a single disposable resource. + * + *

All methods are thread-safe. + */ +public final class AsyncSubscription extends AtomicLong implements Subscription, Disposable { + + private static final long serialVersionUID = 7028635084060361255L; + + final AtomicReference actual; + + final AtomicReference resource; + + public AsyncSubscription() { + resource = new AtomicReference(); + actual = new AtomicReference(); + } + + public AsyncSubscription(Disposable resource) { + this(); + this.resource.lazySet(resource); + } + + @Override + public void request(long n) { + SubscriptionHelper.deferredRequest(actual, this, n); + } + + @Override + public void cancel() { + dispose(); + } + + @Override + public void dispose() { + SubscriptionHelper.cancel(actual); + DisposableHelper.dispose(resource); + } + + @Override + public boolean isDisposed() { + return actual.get() == SubscriptionHelper.CANCELLED; + } + + /** + * Sets a new resource and disposes the currently held resource. + * @param r the new resource to set + * @return false if this AsyncSubscription has been cancelled/disposed + * @see #replaceResource(Disposable) + */ + public boolean setResource(Disposable r) { + return DisposableHelper.set(resource, r); + } + + /** + * Replaces the currently held resource with the given new one without disposing the old. + * @param r the new resource to set + * @return false if this AsyncSubscription has been cancelled/disposed + */ + public boolean replaceResource(Disposable r) { + return DisposableHelper.replace(resource, r); + } + + /** + * Sets the given subscription if there isn't any subscription held. + * @param s the first and only subscription to set + */ + public void setSubscription(Subscription s) { + SubscriptionHelper.deferredSetOnce(actual, this, s); + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java new file mode 100755 index 0000000..a3d2259 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/BasicIntQueueSubscription.java @@ -0,0 +1,38 @@ +/** + * 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.internal.subscriptions; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.internal.fuseable.QueueSubscription; + +/** + * Base class extending AtomicInteger (wip or request accounting) and QueueSubscription (fusion). + * + * @param the value type + */ +public abstract class BasicIntQueueSubscription extends AtomicInteger implements QueueSubscription { + + private static final long serialVersionUID = -6671519529404341862L; + + @Override + public final boolean offer(T e) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public final boolean offer(T v1, T v2) { + throw new UnsupportedOperationException("Should not be called!"); + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java new file mode 100755 index 0000000..ebb9935 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/BasicQueueSubscription.java @@ -0,0 +1,38 @@ +/** + * 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.internal.subscriptions; + +import java.util.concurrent.atomic.AtomicLong; + +import io.reactivex.internal.fuseable.QueueSubscription; + +/** + * Base class extending AtomicLong (wip or request accounting) and QueueSubscription (fusion). + * + * @param the value type + */ +public abstract class BasicQueueSubscription extends AtomicLong implements QueueSubscription { + + private static final long serialVersionUID = -6671519529404341862L; + + @Override + public final boolean offer(T e) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public final boolean offer(T v1, T v2) { + throw new UnsupportedOperationException("Should not be called!"); + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/BooleanSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/BooleanSubscription.java new file mode 100755 index 0000000..7190389 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/BooleanSubscription.java @@ -0,0 +1,49 @@ +/** + * 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.internal.subscriptions; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.Subscription; + +/** + * Subscription implementation that ignores request but remembers the cancellation + * which can be checked via isCancelled. + */ +public final class BooleanSubscription extends AtomicBoolean implements Subscription { + + private static final long serialVersionUID = -8127758972444290902L; + + @Override + public void request(long n) { + SubscriptionHelper.validate(n); + } + + @Override + public void cancel() { + lazySet(true); + } + + /** + * Returns true if this BooleanSubscription has been cancelled. + * @return true if this BooleanSubscription has been cancelled + */ + public boolean isCancelled() { + return get(); + } + + @Override + public String toString() { + return "BooleanSubscription(cancelled=" + get() + ")"; + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java new file mode 100755 index 0000000..131d1d2 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/DeferredScalarSubscription.java @@ -0,0 +1,204 @@ +/** + * 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.internal.subscriptions; + +import io.reactivex.annotations.Nullable; +import org.reactivestreams.Subscriber; + +/** + * A subscription that signals a single value eventually. + *

+ * Note that the class leaks all methods of {@link java.util.concurrent.atomic.AtomicLong}. + * Use {@link #complete(Object)} to signal the single value. + *

+ * The this atomic integer stores a bit field:
+ * bit 0: indicates that there is a value available
+ * bit 1: indicates that there was a request made
+ * bit 2: indicates there was a cancellation, exclusively set
+ * bit 3: indicates in fusion mode but no value yet, exclusively set
+ * bit 4: indicates in fusion mode and value is available, exclusively set
+ * bit 5: indicates in fusion mode and value has been consumed, exclusively set
+ * Where exclusively set means any other bits are 0 when that bit is set. + * @param the value type + */ +public class DeferredScalarSubscription extends BasicIntQueueSubscription { + + private static final long serialVersionUID = -2151279923272604993L; + + /** The Subscriber to emit the value to. */ + protected final Subscriber downstream; + + /** The value is stored here if there is no request yet or in fusion mode. */ + protected T value; + + /** Indicates this Subscription has no value and not requested yet. */ + static final int NO_REQUEST_NO_VALUE = 0; + /** Indicates this Subscription has a value but not requested yet. */ + static final int NO_REQUEST_HAS_VALUE = 1; + /** Indicates this Subscription has been requested but there is no value yet. */ + static final int HAS_REQUEST_NO_VALUE = 2; + /** Indicates this Subscription has both request and value. */ + static final int HAS_REQUEST_HAS_VALUE = 3; + + /** Indicates the Subscription has been cancelled. */ + static final int CANCELLED = 4; + + /** Indicates this Subscription is in fusion mode and is currently empty. */ + static final int FUSED_EMPTY = 8; + /** Indicates this Subscription is in fusion mode and has a value. */ + static final int FUSED_READY = 16; + /** Indicates this Subscription is in fusion mode and its value has been consumed. */ + static final int FUSED_CONSUMED = 32; + + /** + * Creates a DeferredScalarSubscription by wrapping the given Subscriber. + * @param downstream the Subscriber to wrap, not null (not verified) + */ + public DeferredScalarSubscription(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + for (;;) { + int state = get(); + // if the any bits 1-31 are set, we are either in fusion mode (FUSED_*) + // or request has been called (HAS_REQUEST_*) + if ((state & ~NO_REQUEST_HAS_VALUE) != 0) { + return; + } + if (state == NO_REQUEST_HAS_VALUE) { + if (compareAndSet(NO_REQUEST_HAS_VALUE, HAS_REQUEST_HAS_VALUE)) { + T v = value; + if (v != null) { + value = null; + Subscriber a = downstream; + a.onNext(v); + if (get() != CANCELLED) { + a.onComplete(); + } + } + } + return; + } + if (compareAndSet(NO_REQUEST_NO_VALUE, HAS_REQUEST_NO_VALUE)) { + return; + } + } + } + } + + /** + * Completes this subscription by indicating the given value should + * be emitted when the first request arrives. + *

Make sure this is called exactly once. + * @param v the value to signal, not null (not validated) + */ + public final void complete(T v) { + int state = get(); + for (;;) { + if (state == FUSED_EMPTY) { + value = v; + lazySet(FUSED_READY); + + Subscriber a = downstream; + a.onNext(v); + if (get() != CANCELLED) { + a.onComplete(); + } + return; + } + + // if state is >= CANCELLED or bit zero is set (*_HAS_VALUE) case, return + if ((state & ~HAS_REQUEST_NO_VALUE) != 0) { + return; + } + + if (state == HAS_REQUEST_NO_VALUE) { + lazySet(HAS_REQUEST_HAS_VALUE); + Subscriber a = downstream; + a.onNext(v); + if (get() != CANCELLED) { + a.onComplete(); + } + return; + } + value = v; + if (compareAndSet(NO_REQUEST_NO_VALUE, NO_REQUEST_HAS_VALUE)) { + return; + } + state = get(); + if (state == CANCELLED) { + value = null; + return; + } + } + } + + @Override + public final int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + lazySet(FUSED_EMPTY); + return ASYNC; + } + return NONE; + } + + @Nullable + @Override + public final T poll() { + if (get() == FUSED_READY) { + lazySet(FUSED_CONSUMED); + T v = value; + value = null; + return v; + } + return null; + } + + @Override + public final boolean isEmpty() { + return get() != FUSED_READY; + } + + @Override + public final void clear() { + lazySet(FUSED_CONSUMED); + value = null; + } + + @Override + public void cancel() { + set(CANCELLED); + value = null; + } + + /** + * Returns true if this Subscription has been cancelled. + * @return true if this Subscription has been cancelled + */ + public final boolean isCancelled() { + return get() == CANCELLED; + } + + /** + * Atomically sets a cancelled state and returns true if + * the current thread did it successfully. + * @return true if the current thread cancelled + */ + public final boolean tryCancel() { + return getAndSet(CANCELLED) != CANCELLED; + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java b/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java new file mode 100755 index 0000000..d4d78f3 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/EmptySubscription.java @@ -0,0 +1,102 @@ +/** + * 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.internal.subscriptions; + +import io.reactivex.annotations.Nullable; +import org.reactivestreams.Subscriber; + +import io.reactivex.internal.fuseable.QueueSubscription; + +/** + * An empty subscription that does nothing other than validates the request amount. + */ +public enum EmptySubscription implements QueueSubscription { + /** A singleton, stateless instance. */ + INSTANCE; + + @Override + public void request(long n) { + SubscriptionHelper.validate(n); + } + + @Override + public void cancel() { + // no-op + } + + @Override + public String toString() { + return "EmptySubscription"; + } + + /** + * Sets the empty subscription instance on the subscriber and then + * calls onError with the supplied error. + * + *

Make sure this is only called if the subscriber hasn't received a + * subscription already (there is no way of telling this). + * + * @param e the error to deliver to the subscriber + * @param s the target subscriber + */ + public static void error(Throwable e, Subscriber s) { + s.onSubscribe(INSTANCE); + s.onError(e); + } + + /** + * Sets the empty subscription instance on the subscriber and then + * calls onComplete. + * + *

Make sure this is only called if the subscriber hasn't received a + * subscription already (there is no way of telling this). + * + * @param s the target subscriber + */ + public static void complete(Subscriber s) { + s.onSubscribe(INSTANCE); + s.onComplete(); + } + + @Nullable + @Override + public Object poll() { + return null; // always empty + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public void clear() { + // nothing to do + } + + @Override + public int requestFusion(int mode) { + return mode & ASYNC; // accept async mode: an onComplete or onError will be signalled after anyway + } + + @Override + public boolean offer(Object value) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public boolean offer(Object v1, Object v2) { + throw new UnsupportedOperationException("Should not be called!"); + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/ScalarSubscription.java b/src/main/java/io/reactivex/internal/subscriptions/ScalarSubscription.java new file mode 100755 index 0000000..d16484c --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/ScalarSubscription.java @@ -0,0 +1,110 @@ +/** + * 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.internal.subscriptions; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.annotations.Nullable; +import org.reactivestreams.Subscriber; + +import io.reactivex.internal.fuseable.QueueSubscription; + +/** + * A Subscription that holds a constant value and emits it only when requested. + * @param the value type + */ +public final class ScalarSubscription extends AtomicInteger implements QueueSubscription { + + private static final long serialVersionUID = -3830916580126663321L; + /** The single value to emit, set to null. */ + final T value; + /** The actual subscriber. */ + final Subscriber subscriber; + + /** No request has been issued yet. */ + static final int NO_REQUEST = 0; + /** Request has been called.*/ + static final int REQUESTED = 1; + /** Cancel has been called. */ + static final int CANCELLED = 2; + + public ScalarSubscription(Subscriber subscriber, T value) { + this.subscriber = subscriber; + this.value = value; + } + + @Override + public void request(long n) { + if (!SubscriptionHelper.validate(n)) { + return; + } + if (compareAndSet(NO_REQUEST, REQUESTED)) { + Subscriber s = subscriber; + + s.onNext(value); + if (get() != CANCELLED) { + s.onComplete(); + } + } + + } + + @Override + public void cancel() { + lazySet(CANCELLED); + } + + /** + * Returns true if this Subscription was cancelled. + * @return true if this Subscription was cancelled + */ + public boolean isCancelled() { + return get() == CANCELLED; + } + + @Override + public boolean offer(T e) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Override + public boolean offer(T v1, T v2) { + throw new UnsupportedOperationException("Should not be called!"); + } + + @Nullable + @Override + public T poll() { + if (get() == NO_REQUEST) { + lazySet(REQUESTED); + return value; + } + return null; + } + + @Override + public boolean isEmpty() { + return get() != NO_REQUEST; + } + + @Override + public void clear() { + lazySet(1); + } + + @Override + public int requestFusion(int mode) { + return mode & SYNC; + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java new file mode 100755 index 0000000..2796573 --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionArbiter.java @@ -0,0 +1,285 @@ +/** + * 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.internal.subscriptions; +/** + * 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. + */ + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.Subscription; + +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.BackpressureHelper; + +/** + * Arbitrates requests and cancellation between Subscriptions. + */ +public class SubscriptionArbiter extends AtomicInteger implements Subscription { + + private static final long serialVersionUID = -2189523197179400958L; + + /** + * The current subscription which may null if no Subscriptions have been set. + */ + Subscription actual; + + /** + * The current outstanding request amount. + */ + long requested; + + final AtomicReference missedSubscription; + + final AtomicLong missedRequested; + + final AtomicLong missedProduced; + + final boolean cancelOnReplace; + + volatile boolean cancelled; + + protected boolean unbounded; + + public SubscriptionArbiter(boolean cancelOnReplace) { + this.cancelOnReplace = cancelOnReplace; + missedSubscription = new AtomicReference(); + missedRequested = new AtomicLong(); + missedProduced = new AtomicLong(); + } + + /** + * Atomically sets a new subscription. + * @param s the subscription to set, not null (verified) + */ + public final void setSubscription(Subscription s) { + if (cancelled) { + s.cancel(); + return; + } + + ObjectHelper.requireNonNull(s, "s is null"); + + if (get() == 0 && compareAndSet(0, 1)) { + Subscription a = actual; + + if (a != null && cancelOnReplace) { + a.cancel(); + } + + actual = s; + + long r = requested; + + if (decrementAndGet() != 0) { + drainLoop(); + } + + if (r != 0L) { + s.request(r); + } + + return; + } + + Subscription a = missedSubscription.getAndSet(s); + if (a != null && cancelOnReplace) { + a.cancel(); + } + drain(); + } + + @Override + public final void request(long n) { + if (SubscriptionHelper.validate(n)) { + if (unbounded) { + return; + } + if (get() == 0 && compareAndSet(0, 1)) { + long r = requested; + + if (r != Long.MAX_VALUE) { + r = BackpressureHelper.addCap(r, n); + requested = r; + if (r == Long.MAX_VALUE) { + unbounded = true; + } + } + Subscription a = actual; + + if (decrementAndGet() != 0) { + drainLoop(); + } + + if (a != null) { + a.request(n); + } + + return; + } + + BackpressureHelper.add(missedRequested, n); + + drain(); + } + } + + public final void produced(long n) { + if (unbounded) { + return; + } + if (get() == 0 && compareAndSet(0, 1)) { + long r = requested; + + if (r != Long.MAX_VALUE) { + long u = r - n; + if (u < 0L) { + SubscriptionHelper.reportMoreProduced(u); + u = 0; + } + requested = u; + } + + if (decrementAndGet() == 0) { + return; + } + + drainLoop(); + + return; + } + + BackpressureHelper.add(missedProduced, n); + + drain(); + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + drain(); + } + } + + final void drain() { + if (getAndIncrement() != 0) { + return; + } + drainLoop(); + } + + final void drainLoop() { + int missed = 1; + + long requestAmount = 0L; + Subscription requestTarget = null; + + for (; ; ) { + + Subscription ms = missedSubscription.get(); + + if (ms != null) { + ms = missedSubscription.getAndSet(null); + } + + long mr = missedRequested.get(); + if (mr != 0L) { + mr = missedRequested.getAndSet(0L); + } + + long mp = missedProduced.get(); + if (mp != 0L) { + mp = missedProduced.getAndSet(0L); + } + + Subscription a = actual; + + if (cancelled) { + if (a != null) { + a.cancel(); + actual = null; + } + if (ms != null) { + ms.cancel(); + } + } else { + long r = requested; + if (r != Long.MAX_VALUE) { + long u = BackpressureHelper.addCap(r, mr); + + if (u != Long.MAX_VALUE) { + long v = u - mp; + if (v < 0L) { + SubscriptionHelper.reportMoreProduced(v); + v = 0; + } + r = v; + } else { + r = u; + } + requested = r; + } + + if (ms != null) { + if (a != null && cancelOnReplace) { + a.cancel(); + } + actual = ms; + if (r != 0L) { + requestAmount = BackpressureHelper.addCap(requestAmount, r); + requestTarget = ms; + } + } else if (a != null && mr != 0L) { + requestAmount = BackpressureHelper.addCap(requestAmount, mr); + requestTarget = a; + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + if (requestAmount != 0L) { + requestTarget.request(requestAmount); + } + return; + } + } + } + + /** + * Returns true if the arbiter runs in unbounded mode. + * @return true if the arbiter runs in unbounded mode + */ + public final boolean isUnbounded() { + return unbounded; + } + + /** + * Returns true if the arbiter has been cancelled. + * @return true if the arbiter has been cancelled + */ + public final boolean isCancelled() { + return cancelled; + } +} diff --git a/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java new file mode 100755 index 0000000..ca19d0d --- /dev/null +++ b/src/main/java/io/reactivex/internal/subscriptions/SubscriptionHelper.java @@ -0,0 +1,254 @@ +/** + * 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.internal.subscriptions; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.Subscription; + +import io.reactivex.exceptions.ProtocolViolationException; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Utility methods to validate Subscriptions in the various onSubscribe calls. + */ +public enum SubscriptionHelper implements Subscription { + /** + * Represents a cancelled Subscription. + *

Don't leak this instance! + */ + CANCELLED + ; + + @Override + public void request(long n) { + // deliberately ignored + } + + @Override + public void cancel() { + // deliberately ignored + } + + /** + * Verifies that current is null, next is not null, otherwise signals errors + * to the RxJavaPlugins and returns false. + * @param current the current Subscription, expected to be null + * @param next the next Subscription, expected to be non-null + * @return true if the validation succeeded + */ + public static boolean validate(Subscription current, Subscription next) { + if (next == null) { + RxJavaPlugins.onError(new NullPointerException("next is null")); + return false; + } + if (current != null) { + next.cancel(); + reportSubscriptionSet(); + return false; + } + return true; + } + + /** + * Reports that the subscription is already set to the RxJavaPlugins error handler, + * which is an indication of a onSubscribe management bug. + */ + public static void reportSubscriptionSet() { + RxJavaPlugins.onError(new ProtocolViolationException("Subscription already set!")); + } + + /** + * Validates that the n is positive. + * @param n the request amount + * @return false if n is non-positive. + */ + public static boolean validate(long n) { + if (n <= 0) { + RxJavaPlugins.onError(new IllegalArgumentException("n > 0 required but it was " + n)); + return false; + } + return true; + } + + /** + * Reports to the plugin error handler that there were more values produced than requested, which + * is a sign of internal backpressure handling bug. + * @param n the overproduction amount + */ + public static void reportMoreProduced(long n) { + RxJavaPlugins.onError(new ProtocolViolationException("More produced than requested: " + n)); + } + + /** + * Atomically sets the subscription on the field and cancels the + * previous subscription if any. + * @param field the target field to set the new subscription on + * @param s the new subscription + * @return true if the operation succeeded, false if the target field + * holds the {@link #CANCELLED} instance. + * @see #replace(AtomicReference, Subscription) + */ + public static boolean set(AtomicReference field, Subscription s) { + for (;;) { + Subscription current = field.get(); + if (current == CANCELLED) { + if (s != null) { + s.cancel(); + } + return false; + } + if (field.compareAndSet(current, s)) { + if (current != null) { + current.cancel(); + } + return true; + } + } + } + + /** + * Atomically sets the subscription on the field if it is still null. + *

If the field is not null and doesn't contain the {@link #CANCELLED} + * instance, the {@link #reportSubscriptionSet()} is called. + * @param field the target field + * @param s the new subscription to set + * @return true if the operation succeeded, false if the target field was not null. + */ + public static boolean setOnce(AtomicReference field, Subscription s) { + ObjectHelper.requireNonNull(s, "s is null"); + if (!field.compareAndSet(null, s)) { + s.cancel(); + if (field.get() != CANCELLED) { + reportSubscriptionSet(); + } + return false; + } + return true; + } + + /** + * Atomically sets the subscription on the field but does not + * cancel the previous subscription. + * @param field the target field to set the new subscription on + * @param s the new subscription + * @return true if the operation succeeded, false if the target field + * holds the {@link #CANCELLED} instance. + * @see #set(AtomicReference, Subscription) + */ + public static boolean replace(AtomicReference field, Subscription s) { + for (;;) { + Subscription current = field.get(); + if (current == CANCELLED) { + if (s != null) { + s.cancel(); + } + return false; + } + if (field.compareAndSet(current, s)) { + return true; + } + } + } + + /** + * Atomically swaps in the common cancelled subscription instance + * and cancels the previous subscription if any. + * @param field the target field to dispose the contents of + * @return true if the swap from the non-cancelled instance to the + * common cancelled instance happened in the caller's thread (allows + * further one-time actions). + */ + public static boolean cancel(AtomicReference field) { + Subscription current = field.get(); + if (current != CANCELLED) { + current = field.getAndSet(CANCELLED); + if (current != CANCELLED) { + if (current != null) { + current.cancel(); + } + return true; + } + } + return false; + } + + /** + * Atomically sets the new Subscription on the field and requests any accumulated amount + * from the requested field. + * @param field the target field for the new Subscription + * @param requested the current requested amount + * @param s the new Subscription, not null (verified) + * @return true if the Subscription was set the first time + */ + public static boolean deferredSetOnce(AtomicReference field, AtomicLong requested, + Subscription s) { + if (SubscriptionHelper.setOnce(field, s)) { + long r = requested.getAndSet(0L); + if (r != 0L) { + s.request(r); + } + return true; + } + return false; + } + + /** + * Atomically requests from the Subscription in the field if not null, otherwise accumulates + * the request amount in the requested field to be requested once the field is set to non-null. + * @param field the target field that may already contain a Subscription + * @param requested the current requested amount + * @param n the request amount, positive (verified) + */ + public static void deferredRequest(AtomicReference field, AtomicLong requested, long n) { + Subscription s = field.get(); + if (s != null) { + s.request(n); + } else { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + + s = field.get(); + if (s != null) { + long r = requested.getAndSet(0L); + if (r != 0L) { + s.request(r); + } + } + } + } + } + + /** + * Atomically sets the subscription on the field if it is still null and issues a positive request + * to the given {@link Subscription}. + *

+ * If the field is not null and doesn't contain the {@link #CANCELLED} + * instance, the {@link #reportSubscriptionSet()} is called. + * @param field the target field + * @param s the new subscription to set + * @param request the amount to request, positive (not verified) + * @return true if the operation succeeded, false if the target field was not null. + * @since 2.1.11 + */ + public static boolean setOnce(AtomicReference field, Subscription s, long request) { + if (setOnce(field, s)) { + s.request(request); + return true; + } + return false; + } +} diff --git a/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java b/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java new file mode 100755 index 0000000..12c4d06 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/AppendOnlyLinkedArrayList.java @@ -0,0 +1,180 @@ +/** + * 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.internal.util; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Observer; +import io.reactivex.functions.*; + +/** + * A linked-array-list implementation that only supports appending and consumption. + * + * @param the value type + */ +public class AppendOnlyLinkedArrayList { + final int capacity; + final Object[] head; + Object[] tail; + int offset; + + /** + * Constructs an empty list with a per-link capacity. + * @param capacity the capacity of each link + */ + public AppendOnlyLinkedArrayList(int capacity) { + this.capacity = capacity; + this.head = new Object[capacity + 1]; + this.tail = head; + } + + /** + * Append a non-null value to the list. + *

Don't add null to the list! + * @param value the value to append + */ + public void add(T value) { + final int c = capacity; + int o = offset; + if (o == c) { + Object[] next = new Object[c + 1]; + tail[c] = next; + tail = next; + o = 0; + } + tail[o] = value; + offset = o + 1; + } + + /** + * Set a value as the first element of the list. + * @param value the value to set + */ + public void setFirst(T value) { + head[0] = value; + } + + /** + * Predicate interface suppressing the exception. + * + * @param the value type + */ + public interface NonThrowingPredicate extends Predicate { + @Override + boolean test(T t); + } + + /** + * Loops over all elements of the array until a null element is encountered or + * the given predicate returns true. + * @param consumer the consumer of values that returns true if the forEach should terminate + */ + @SuppressWarnings("unchecked") + public void forEachWhile(NonThrowingPredicate consumer) { + Object[] a = head; + final int c = capacity; + while (a != null) { + for (int i = 0; i < c; i++) { + Object o = a[i]; + if (o == null) { + break; + } + if (consumer.test((T)o)) { + return; + } + } + a = (Object[])a[c]; + } + } + + /** + * Interprets the contents as NotificationLite objects and calls + * the appropriate Subscriber method. + * + * @param the target type + * @param subscriber the subscriber to emit the events to + * @return true if a terminal event has been reached + */ + public boolean accept(Subscriber subscriber) { + Object[] a = head; + final int c = capacity; + while (a != null) { + for (int i = 0; i < c; i++) { + Object o = a[i]; + if (o == null) { + break; + } + + if (NotificationLite.acceptFull(o, subscriber)) { + return true; + } + } + a = (Object[])a[c]; + } + return false; + } + + /** + * Interprets the contents as NotificationLite objects and calls + * the appropriate Observer method. + * + * @param the target type + * @param observer the observer to emit the events to + * @return true if a terminal event has been reached + */ + public boolean accept(Observer observer) { + Object[] a = head; + final int c = capacity; + while (a != null) { + for (int i = 0; i < c; i++) { + Object o = a[i]; + if (o == null) { + break; + } + + if (NotificationLite.acceptFull(o, observer)) { + return true; + } + } + a = (Object[])a[c]; + } + return false; + } + + /** + * Loops over all elements of the array until a null element is encountered or + * the given predicate returns true. + * @param the extra state type + * @param state the extra state passed into the consumer + * @param consumer the consumer of values that returns true if the forEach should terminate + * @throws Exception if the predicate throws + */ + @SuppressWarnings("unchecked") + public void forEachWhile(S state, BiPredicate consumer) throws Exception { + Object[] a = head; + final int c = capacity; + for (;;) { + for (int i = 0; i < c; i++) { + Object o = a[i]; + if (o == null) { + return; + } + if (consumer.test(state, (T)o)) { + return; + } + } + a = (Object[])a[c]; + } + } +} diff --git a/src/main/java/io/reactivex/internal/util/ArrayListSupplier.java b/src/main/java/io/reactivex/internal/util/ArrayListSupplier.java new file mode 100755 index 0000000..1787d16 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/ArrayListSupplier.java @@ -0,0 +1,42 @@ +/** + * 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.internal.util; + +import java.util.*; +import java.util.concurrent.Callable; + +import io.reactivex.functions.Function; + +public enum ArrayListSupplier implements Callable>, Function> { + INSTANCE; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Callable> asCallable() { + return (Callable)INSTANCE; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Function> asFunction() { + return (Function)INSTANCE; + } + + @Override + public List call() throws Exception { + return new ArrayList(); + } + + @Override public List apply(Object o) throws Exception { + return new ArrayList(); + } +} diff --git a/src/main/java/io/reactivex/internal/util/AtomicThrowable.java b/src/main/java/io/reactivex/internal/util/AtomicThrowable.java new file mode 100755 index 0000000..60c1915 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/AtomicThrowable.java @@ -0,0 +1,49 @@ +/** + * 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.internal.util; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Atomic container for Throwables including combining and having a + * terminal state via ExceptionHelper. + *

+ * Watch out for the leaked AtomicReference methods! + */ +public final class AtomicThrowable extends AtomicReference { + + private static final long serialVersionUID = 3949248817947090603L; + + /** + * Atomically adds a Throwable to this container (combining with a previous Throwable is necessary). + * @param t the throwable to add + * @return true if successful, false if the container has been terminated + */ + public boolean addThrowable(Throwable t) { + return ExceptionHelper.addThrowable(this, t); + } + + /** + * Atomically terminate the container and return the contents of the last + * non-terminal Throwable of it. + * @return the last Throwable + */ + public Throwable terminate() { + return ExceptionHelper.terminate(this); + } + + public boolean isTerminated() { + return get() == ExceptionHelper.TERMINATED; + } +} diff --git a/src/main/java/io/reactivex/internal/util/BackpressureHelper.java b/src/main/java/io/reactivex/internal/util/BackpressureHelper.java new file mode 100755 index 0000000..f2f7822 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/BackpressureHelper.java @@ -0,0 +1,151 @@ +/** + * 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.internal.util; + +import java.util.concurrent.atomic.AtomicLong; + +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Utility class to help with backpressure-related operations such as request aggregation. + */ +public final class BackpressureHelper { + /** Utility class. */ + private BackpressureHelper() { + throw new IllegalStateException("No instances!"); + } + + /** + * Adds two long values and caps the sum at Long.MAX_VALUE. + * @param a the first value + * @param b the second value + * @return the sum capped at Long.MAX_VALUE + */ + public static long addCap(long a, long b) { + long u = a + b; + if (u < 0L) { + return Long.MAX_VALUE; + } + return u; + } + + /** + * Multiplies two long values and caps the product at Long.MAX_VALUE. + * @param a the first value + * @param b the second value + * @return the product capped at Long.MAX_VALUE + */ + public static long multiplyCap(long a, long b) { + long u = a * b; + if (((a | b) >>> 31) != 0) { + if (u / a != b) { + return Long.MAX_VALUE; + } + } + return u; + } + + /** + * Atomically adds the positive value n to the requested value in the AtomicLong and + * caps the result at Long.MAX_VALUE and returns the previous value. + * @param requested the AtomicLong holding the current requested value + * @param n the value to add, must be positive (not verified) + * @return the original value before the add + */ + public static long add(AtomicLong requested, long n) { + for (;;) { + long r = requested.get(); + if (r == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + long u = addCap(r, n); + if (requested.compareAndSet(r, u)) { + return r; + } + } + } + + /** + * Atomically adds the positive value n to the requested value in the AtomicLong and + * caps the result at Long.MAX_VALUE and returns the previous value and + * considers Long.MIN_VALUE as a cancel indication (no addition then). + * @param requested the AtomicLong holding the current requested value + * @param n the value to add, must be positive (not verified) + * @return the original value before the add + */ + public static long addCancel(AtomicLong requested, long n) { + for (;;) { + long r = requested.get(); + if (r == Long.MIN_VALUE) { + return Long.MIN_VALUE; + } + if (r == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + long u = addCap(r, n); + if (requested.compareAndSet(r, u)) { + return r; + } + } + } + + /** + * Atomically subtract the given number (positive, not validated) from the target field unless it contains Long.MAX_VALUE. + * @param requested the target field holding the current requested amount + * @param n the produced element count, positive (not validated) + * @return the new amount + */ + public static long produced(AtomicLong requested, long n) { + for (;;) { + long current = requested.get(); + if (current == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + long update = current - n; + if (update < 0L) { + RxJavaPlugins.onError(new IllegalStateException("More produced than requested: " + update)); + update = 0L; + } + if (requested.compareAndSet(current, update)) { + return update; + } + } + } + + /** + * Atomically subtract the given number (positive, not validated) from the target field if + * it doesn't contain Long.MIN_VALUE (indicating some cancelled state) or Long.MAX_VALUE (unbounded mode). + * @param requested the target field holding the current requested amount + * @param n the produced element count, positive (not validated) + * @return the new amount + */ + public static long producedCancel(AtomicLong requested, long n) { + for (;;) { + long current = requested.get(); + if (current == Long.MIN_VALUE) { + return Long.MIN_VALUE; + } + if (current == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + long update = current - n; + if (update < 0L) { + RxJavaPlugins.onError(new IllegalStateException("More produced than requested: " + update)); + update = 0L; + } + if (requested.compareAndSet(current, update)) { + return update; + } + } + } +} diff --git a/src/main/java/io/reactivex/internal/util/BlockingHelper.java b/src/main/java/io/reactivex/internal/util/BlockingHelper.java new file mode 100755 index 0000000..7be3695 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/BlockingHelper.java @@ -0,0 +1,63 @@ +/** + * 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.internal.util; + +import java.util.concurrent.CountDownLatch; + +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.schedulers.NonBlockingThread; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Utility methods for helping common blocking operations. + */ +public final class BlockingHelper { + /** Utility class. */ + private BlockingHelper() { + throw new IllegalStateException("No instances!"); + } + + public static void awaitForComplete(CountDownLatch latch, Disposable subscription) { + if (latch.getCount() == 0) { + // Synchronous observable completes before awaiting for it. + // Skip await so InterruptedException will never be thrown. + return; + } + // block until the subscription completes and then return + try { + verifyNonBlocking(); + latch.await(); + } catch (InterruptedException e) { + subscription.dispose(); + // set the interrupted flag again so callers can still get it + // for more information see https://github.com/ReactiveX/RxJava/pull/147#issuecomment-13624780 + Thread.currentThread().interrupt(); + // using Runtime so it is not checked + throw new IllegalStateException("Interrupted while waiting for subscription to complete.", e); + } + } + + /** + * Checks if the {@code failOnNonBlockingScheduler} plugin setting is enabled and the current + * thread is a Scheduler sensitive to blocking operators. + * @throws IllegalStateException if the {@code failOnNonBlockingScheduler} and the current thread is sensitive to blocking + */ + public static void verifyNonBlocking() { + if (RxJavaPlugins.isFailOnNonBlockingScheduler() + && (Thread.currentThread() instanceof NonBlockingThread + || RxJavaPlugins.onBeforeBlocking())) { + throw new IllegalStateException("Attempt to block on a Scheduler " + Thread.currentThread().getName() + " that doesn't support blocking operators as they may lead to deadlock"); + } + } +} diff --git a/src/main/java/io/reactivex/internal/util/BlockingIgnoringReceiver.java b/src/main/java/io/reactivex/internal/util/BlockingIgnoringReceiver.java new file mode 100755 index 0000000..2be0090 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/BlockingIgnoringReceiver.java @@ -0,0 +1,42 @@ +/** + * 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.internal.util; + +import java.util.concurrent.CountDownLatch; + +import io.reactivex.functions.*; + +/** + * Stores an incoming Throwable (if any) and counts itself down. + */ +public final class BlockingIgnoringReceiver +extends CountDownLatch +implements Consumer, Action { + public Throwable error; + + public BlockingIgnoringReceiver() { + super(1); + } + + @Override + public void accept(Throwable e) { + error = e; + countDown(); + } + + @Override + public void run() { + countDown(); + } +} diff --git a/src/main/java/io/reactivex/internal/util/ConnectConsumer.java b/src/main/java/io/reactivex/internal/util/ConnectConsumer.java new file mode 100755 index 0000000..e4a0106 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/ConnectConsumer.java @@ -0,0 +1,29 @@ +/** + * 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.internal.util; + +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; + +/** + * Store the Disposable received from the connection. + */ +public final class ConnectConsumer implements Consumer { + public Disposable disposable; + + @Override + public void accept(Disposable t) throws Exception { + this.disposable = t; + } +} diff --git a/src/main/java/io/reactivex/internal/util/EmptyComponent.java b/src/main/java/io/reactivex/internal/util/EmptyComponent.java new file mode 100755 index 0000000..348813c --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/EmptyComponent.java @@ -0,0 +1,88 @@ +/** + * 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.internal.util; + +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Singleton implementing many interfaces as empty. + */ +public enum EmptyComponent implements FlowableSubscriber, Observer, MaybeObserver, +SingleObserver, CompletableObserver, Subscription, Disposable { + INSTANCE; + + @SuppressWarnings("unchecked") + public static Subscriber asSubscriber() { + return (Subscriber)INSTANCE; + } + + @SuppressWarnings("unchecked") + public static Observer asObserver() { + return (Observer)INSTANCE; + } + + @Override + public void dispose() { + // deliberately no-op + } + + @Override + public boolean isDisposed() { + return true; + } + + @Override + public void request(long n) { + // deliberately no-op + } + + @Override + public void cancel() { + // deliberately no-op + } + + @Override + public void onSubscribe(Disposable d) { + d.dispose(); + } + + @Override + public void onSubscribe(Subscription s) { + s.cancel(); + } + + @Override + public void onNext(Object t) { + // deliberately no-op + } + + @Override + public void onError(Throwable t) { + RxJavaPlugins.onError(t); + } + + @Override + public void onComplete() { + // deliberately no-op + } + + @Override + public void onSuccess(Object value) { + // deliberately no-op + } +} diff --git a/src/main/java/io/reactivex/internal/util/EndConsumerHelper.java b/src/main/java/io/reactivex/internal/util/EndConsumerHelper.java new file mode 100755 index 0000000..48b8032 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/EndConsumerHelper.java @@ -0,0 +1,150 @@ +/** + * 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.internal.util; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.Subscription; + +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.ProtocolViolationException; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Utility class to help report multiple subscriptions with the same + * consumer type instead of the internal "Disposable already set!" message + * that is practically reserved for internal operators and indicate bugs in them. + */ +public final class EndConsumerHelper { + + /** + * Utility class. + */ + private EndConsumerHelper() { + throw new IllegalStateException("No instances!"); + } + + /** + * Ensures that the upstream Disposable is null and returns true, otherwise + * disposes the next Disposable and if the upstream is not the shared + * disposed instance, reports a ProtocolViolationException due to + * multiple subscribe attempts. + * @param upstream the upstream current value + * @param next the Disposable to check for nullness and dispose if necessary + * @param observer the class of the consumer to have a personalized + * error message if the upstream already contains a non-cancelled Disposable. + * @return true if successful, false if the upstream was non null + */ + public static boolean validate(Disposable upstream, Disposable next, Class observer) { + ObjectHelper.requireNonNull(next, "next is null"); + if (upstream != null) { + next.dispose(); + if (upstream != DisposableHelper.DISPOSED) { + reportDoubleSubscription(observer); + } + return false; + } + return true; + } + + /** + * Atomically updates the target upstream AtomicReference from null to the non-null + * next Disposable, otherwise disposes next and reports a ProtocolViolationException + * if the AtomicReference doesn't contain the shared disposed indicator. + * @param upstream the target AtomicReference to update + * @param next the Disposable to set on it atomically + * @param observer the class of the consumer to have a personalized + * error message if the upstream already contains a non-cancelled Disposable. + * @return true if successful, false if the content of the AtomicReference was non null + */ + public static boolean setOnce(AtomicReference upstream, Disposable next, Class observer) { + ObjectHelper.requireNonNull(next, "next is null"); + if (!upstream.compareAndSet(null, next)) { + next.dispose(); + if (upstream.get() != DisposableHelper.DISPOSED) { + reportDoubleSubscription(observer); + } + return false; + } + return true; + } + + /** + * Ensures that the upstream Subscription is null and returns true, otherwise + * cancels the next Subscription and if the upstream is not the shared + * cancelled instance, reports a ProtocolViolationException due to + * multiple subscribe attempts. + * @param upstream the upstream current value + * @param next the Subscription to check for nullness and cancel if necessary + * @param subscriber the class of the consumer to have a personalized + * error message if the upstream already contains a non-cancelled Subscription. + * @return true if successful, false if the upstream was non null + */ + public static boolean validate(Subscription upstream, Subscription next, Class subscriber) { + ObjectHelper.requireNonNull(next, "next is null"); + if (upstream != null) { + next.cancel(); + if (upstream != SubscriptionHelper.CANCELLED) { + reportDoubleSubscription(subscriber); + } + return false; + } + return true; + } + + /** + * Atomically updates the target upstream AtomicReference from null to the non-null + * next Subscription, otherwise cancels next and reports a ProtocolViolationException + * if the AtomicReference doesn't contain the shared cancelled indicator. + * @param upstream the target AtomicReference to update + * @param next the Subscription to set on it atomically + * @param subscriber the class of the consumer to have a personalized + * error message if the upstream already contains a non-cancelled Subscription. + * @return true if successful, false if the content of the AtomicReference was non null + */ + public static boolean setOnce(AtomicReference upstream, Subscription next, Class subscriber) { + ObjectHelper.requireNonNull(next, "next is null"); + if (!upstream.compareAndSet(null, next)) { + next.cancel(); + if (upstream.get() != SubscriptionHelper.CANCELLED) { + reportDoubleSubscription(subscriber); + } + return false; + } + return true; + } + + /** + * Builds the error message with the consumer class. + * @param consumer the class of the consumer + * @return the error message string + */ + public static String composeMessage(String consumer) { + return "It is not allowed to subscribe with a(n) " + consumer + " multiple times. " + + "Please create a fresh instance of " + consumer + " and subscribe that to the target source instead."; + } + + /** + * Report a ProtocolViolationException with a personalized message referencing + * the simple type name of the consumer class and report it via + * RxJavaPlugins.onError. + * @param consumer the class of the consumer + */ + public static void reportDoubleSubscription(Class consumer) { + RxJavaPlugins.onError(new ProtocolViolationException(composeMessage(consumer.getName()))); + } +} diff --git a/src/main/java/io/reactivex/internal/util/ErrorMode.java b/src/main/java/io/reactivex/internal/util/ErrorMode.java new file mode 100755 index 0000000..33b9ede --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/ErrorMode.java @@ -0,0 +1,26 @@ +/** + * 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.internal.util; + +/** + * Indicates when an error from the main source should be reported. + */ +public enum ErrorMode { + /** Report the error immediately, cancelling the active inner source. */ + IMMEDIATE, + /** Report error after an inner source terminated. */ + BOUNDARY, + /** Report the error after all sources terminated. */ + END +} diff --git a/src/main/java/io/reactivex/internal/util/ExceptionHelper.java b/src/main/java/io/reactivex/internal/util/ExceptionHelper.java new file mode 100755 index 0000000..ecd970c --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/ExceptionHelper.java @@ -0,0 +1,146 @@ +/** + * 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.internal.util; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.exceptions.CompositeException; + +/** + * Terminal atomics for Throwable containers. + */ +public final class ExceptionHelper { + + /** Utility class. */ + private ExceptionHelper() { + throw new IllegalStateException("No instances!"); + } + + /** + * If the provided Throwable is an Error this method + * throws it, otherwise returns a RuntimeException wrapping the error + * if that error is a checked exception. + * @param error the error to wrap or throw + * @return the (wrapped) error + */ + public static RuntimeException wrapOrThrow(Throwable error) { + if (error instanceof Error) { + throw (Error)error; + } + if (error instanceof RuntimeException) { + return (RuntimeException)error; + } + return new RuntimeException(error); + } + + /** + * A singleton instance of a Throwable indicating a terminal state for exceptions, + * don't leak this. + */ + public static final Throwable TERMINATED = new Termination(); + + public static boolean addThrowable(AtomicReference field, Throwable exception) { + for (;;) { + Throwable current = field.get(); + + if (current == TERMINATED) { + return false; + } + + Throwable update; + if (current == null) { + update = exception; + } else { + update = new CompositeException(current, exception); + } + + if (field.compareAndSet(current, update)) { + return true; + } + } + } + + public static Throwable terminate(AtomicReference field) { + Throwable current = field.get(); + if (current != TERMINATED) { + current = field.getAndSet(TERMINATED); + } + return current; + } + + /** + * Returns a flattened list of Throwables from tree-like CompositeException chain. + * @param t the starting throwable + * @return the list of Throwables flattened in a depth-first manner + */ + public static List flatten(Throwable t) { + List list = new ArrayList(); + ArrayDeque deque = new ArrayDeque(); + deque.offer(t); + + while (!deque.isEmpty()) { + Throwable e = deque.removeFirst(); + if (e instanceof CompositeException) { + CompositeException ce = (CompositeException) e; + List exceptions = ce.getExceptions(); + for (int i = exceptions.size() - 1; i >= 0; i--) { + deque.offerFirst(exceptions.get(i)); + } + } else { + list.add(e); + } + } + + return list; + } + + /** + * Workaround for Java 6 not supporting throwing a final Throwable from a catch block. + * @param the generic exception type + * @param e the Throwable error to return or throw + * @return the Throwable e if it is a subclass of Exception + * @throws E the generic exception thrown + */ + @SuppressWarnings("unchecked") + public static Exception throwIfThrowable(Throwable e) throws E { + if (e instanceof Exception) { + return (Exception)e; + } + throw (E)e; + } + + public static String timeoutMessage(long timeout, TimeUnit unit) { + return "The source did not signal an event for " + + timeout + + " " + + unit.toString().toLowerCase() + + " and has been terminated."; + } + + static final class Termination extends Throwable { + + private static final long serialVersionUID = -4649703670690200604L; + + Termination() { + super("No further exceptions"); + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + } +} diff --git a/src/main/java/io/reactivex/internal/util/HalfSerializer.java b/src/main/java/io/reactivex/internal/util/HalfSerializer.java new file mode 100755 index 0000000..8e160ab --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/HalfSerializer.java @@ -0,0 +1,157 @@ +/** + * 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.internal.util; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.reactivestreams.Subscriber; + +import io.reactivex.Observer; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Utility methods to perform half-serialization: a form of serialization + * where onNext is guaranteed to be called from a single thread but + * onError or onComplete may be called from any threads. + */ +public final class HalfSerializer { + /** Utility class. */ + private HalfSerializer() { + throw new IllegalStateException("No instances!"); + } + + /** + * Emits the given value if possible and terminates if there was an onComplete or onError + * while emitting, drops the value otherwise. + * @param the value type + * @param subscriber the target Subscriber to emit to + * @param value the value to emit + * @param wip the serialization work-in-progress counter/indicator + * @param error the holder of Throwables + */ + public static void onNext(Subscriber subscriber, T value, + AtomicInteger wip, AtomicThrowable error) { + if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + subscriber.onNext(value); + if (wip.decrementAndGet() != 0) { + Throwable ex = error.terminate(); + if (ex != null) { + subscriber.onError(ex); + } else { + subscriber.onComplete(); + } + } + } + } + + /** + * Emits the given exception if possible or adds it to the given error container to + * be emitted by a concurrent onNext if one is running. + * Undeliverable exceptions are sent to the RxJavaPlugins.onError. + * @param subscriber the target Subscriber to emit to + * @param ex the Throwable to emit + * @param wip the serialization work-in-progress counter/indicator + * @param error the holder of Throwables + */ + public static void onError(Subscriber subscriber, Throwable ex, + AtomicInteger wip, AtomicThrowable error) { + if (error.addThrowable(ex)) { + if (wip.getAndIncrement() == 0) { + subscriber.onError(error.terminate()); + } + } else { + RxJavaPlugins.onError(ex); + } + } + + /** + * Emits an onComplete signal or an onError signal with the given error or indicates + * the concurrently running onNext should do that. + * @param subscriber the target Subscriber to emit to + * @param wip the serialization work-in-progress counter/indicator + * @param error the holder of Throwables + */ + public static void onComplete(Subscriber subscriber, AtomicInteger wip, AtomicThrowable error) { + if (wip.getAndIncrement() == 0) { + Throwable ex = error.terminate(); + if (ex != null) { + subscriber.onError(ex); + } else { + subscriber.onComplete(); + } + } + } + + /** + * Emits the given value if possible and terminates if there was an onComplete or onError + * while emitting, drops the value otherwise. + * @param the value type + * @param observer the target Observer to emit to + * @param value the value to emit + * @param wip the serialization work-in-progress counter/indicator + * @param error the holder of Throwables + */ + public static void onNext(Observer observer, T value, + AtomicInteger wip, AtomicThrowable error) { + if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + observer.onNext(value); + if (wip.decrementAndGet() != 0) { + Throwable ex = error.terminate(); + if (ex != null) { + observer.onError(ex); + } else { + observer.onComplete(); + } + } + } + } + + /** + * Emits the given exception if possible or adds it to the given error container to + * be emitted by a concurrent onNext if one is running. + * Undeliverable exceptions are sent to the RxJavaPlugins.onError. + * @param observer the target Subscriber to emit to + * @param ex the Throwable to emit + * @param wip the serialization work-in-progress counter/indicator + * @param error the holder of Throwables + */ + public static void onError(Observer observer, Throwable ex, + AtomicInteger wip, AtomicThrowable error) { + if (error.addThrowable(ex)) { + if (wip.getAndIncrement() == 0) { + observer.onError(error.terminate()); + } + } else { + RxJavaPlugins.onError(ex); + } + } + + /** + * Emits an onComplete signal or an onError signal with the given error or indicates + * the concurrently running onNext should do that. + * @param observer the target Subscriber to emit to + * @param wip the serialization work-in-progress counter/indicator + * @param error the holder of Throwables + */ + public static void onComplete(Observer observer, AtomicInteger wip, AtomicThrowable error) { + if (wip.getAndIncrement() == 0) { + Throwable ex = error.terminate(); + if (ex != null) { + observer.onError(ex); + } else { + observer.onComplete(); + } + } + } + +} diff --git a/src/main/java/io/reactivex/internal/util/HashMapSupplier.java b/src/main/java/io/reactivex/internal/util/HashMapSupplier.java new file mode 100755 index 0000000..79394e6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/HashMapSupplier.java @@ -0,0 +1,31 @@ +/** + * 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.internal.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +public enum HashMapSupplier implements Callable> { + INSTANCE; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Callable> asCallable() { + return (Callable)INSTANCE; + } + + @Override public Map call() throws Exception { + return new HashMap(); + } +} diff --git a/src/main/java/io/reactivex/internal/util/LinkedArrayList.java b/src/main/java/io/reactivex/internal/util/LinkedArrayList.java new file mode 100755 index 0000000..6dbf3fb --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/LinkedArrayList.java @@ -0,0 +1,111 @@ +/** + * 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.internal.util; + +import java.util.*; + +/** + * A list implementation which combines an ArrayList with a LinkedList to + * avoid copying values when the capacity needs to be increased. + *

+ * The class is non final to allow embedding it directly and thus saving on object allocation. + */ +public class LinkedArrayList { + /** The capacity of each array segment. */ + final int capacityHint; + /** + * Contains the head of the linked array list if not null. The + * length is always capacityHint + 1 and the last element is an Object[] pointing + * to the next element of the linked array list. + */ + Object[] head; + /** The tail array where new elements will be added. */ + Object[] tail; + /** + * The total size of the list; written after elements have been added (release) and + * and when read, the value indicates how many elements can be safely read (acquire). + */ + volatile int size; + /** The next available slot in the current tail. */ + int indexInTail; + /** + * Constructor with the capacity hint of each array segment. + * @param capacityHint the expected number of elements to hold (can grow beyond that) + */ + public LinkedArrayList(int capacityHint) { + this.capacityHint = capacityHint; + } + /** + * Adds a new element to this list. + * @param o the object to add, nulls are accepted + */ + public void add(Object o) { + // if no value yet, create the first array + if (size == 0) { + head = new Object[capacityHint + 1]; + tail = head; + head[0] = o; + indexInTail = 1; + size = 1; + } else + // if the tail is full, create a new tail and link + if (indexInTail == capacityHint) { + Object[] t = new Object[capacityHint + 1]; + t[0] = o; + tail[capacityHint] = t; + tail = t; + indexInTail = 1; + size++; + } else { + tail[indexInTail] = o; + indexInTail++; + size++; + } + } + /** + * Returns the head buffer segment or null if the list is empty. + * @return the head object array + */ + public Object[] head() { + return head; // NOPMD + } + + /** + * Returns the total size of the list. + * @return the total size of the list + */ + public int size() { + return size; + } + + @Override + public String toString() { + final int cap = capacityHint; + final int s = size; + final List list = new ArrayList(s + 1); + + Object[] h = head(); + int j = 0; + int k = 0; + while (j < s) { + list.add(h[k]); + j++; + if (++k == cap) { + k = 0; + h = (Object[])h[cap]; + } + } + + return list.toString(); + } +} diff --git a/src/main/java/io/reactivex/internal/util/ListAddBiConsumer.java b/src/main/java/io/reactivex/internal/util/ListAddBiConsumer.java new file mode 100755 index 0000000..787c5ef --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/ListAddBiConsumer.java @@ -0,0 +1,35 @@ +/** + * 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.internal.util; + +import java.util.List; + +import io.reactivex.functions.*; + +@SuppressWarnings("rawtypes") +public enum ListAddBiConsumer implements BiFunction { + INSTANCE; + + @SuppressWarnings("unchecked") + public static BiFunction, T, List> instance() { + return (BiFunction)INSTANCE; + } + + @SuppressWarnings("unchecked") + @Override + public List apply(List t1, Object t2) throws Exception { + t1.add(t2); + return t1; + } +} diff --git a/src/main/java/io/reactivex/internal/util/MergerBiFunction.java b/src/main/java/io/reactivex/internal/util/MergerBiFunction.java new file mode 100755 index 0000000..ba13a4b --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/MergerBiFunction.java @@ -0,0 +1,70 @@ +/** + * 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.internal.util; + +import java.util.*; + +import io.reactivex.functions.BiFunction; + +/** + * A BiFunction that merges two Lists into a new list. + * @param the value type + */ +public final class MergerBiFunction implements BiFunction, List, List> { + + final Comparator comparator; + + public MergerBiFunction(Comparator comparator) { + this.comparator = comparator; + } + + @Override + public List apply(List a, List b) throws Exception { + int n = a.size() + b.size(); + if (n == 0) { + return new ArrayList(); + } + List both = new ArrayList(n); + + Iterator at = a.iterator(); + Iterator bt = b.iterator(); + + T s1 = at.hasNext() ? at.next() : null; + T s2 = bt.hasNext() ? bt.next() : null; + + while (s1 != null && s2 != null) { + if (comparator.compare(s1, s2) < 0) { // s1 comes before s2 + both.add(s1); + s1 = at.hasNext() ? at.next() : null; + } else { + both.add(s2); + s2 = bt.hasNext() ? bt.next() : null; + } + } + + if (s1 != null) { + both.add(s1); + while (at.hasNext()) { + both.add(at.next()); + } + } else { + both.add(s2); + while (bt.hasNext()) { + both.add(bt.next()); + } + } + + return both; + } +} diff --git a/src/main/java/io/reactivex/internal/util/NotificationLite.java b/src/main/java/io/reactivex/internal/util/NotificationLite.java new file mode 100755 index 0000000..2359141 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/NotificationLite.java @@ -0,0 +1,306 @@ +/** + * 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.internal.util; + +import java.io.Serializable; + +import org.reactivestreams.*; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Lightweight notification handling utility class. + */ +public enum NotificationLite { + COMPLETE + ; + + /** + * Wraps a Throwable. + */ + static final class ErrorNotification implements Serializable { + + private static final long serialVersionUID = -8759979445933046293L; + final Throwable e; + ErrorNotification(Throwable e) { + this.e = e; + } + + @Override + public String toString() { + return "NotificationLite.Error[" + e + "]"; + } + + @Override + public int hashCode() { + return e.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ErrorNotification) { + ErrorNotification n = (ErrorNotification) obj; + return ObjectHelper.equals(e, n.e); + } + return false; + } + } + + /** + * Wraps a Subscription. + */ + static final class SubscriptionNotification implements Serializable { + + private static final long serialVersionUID = -1322257508628817540L; + final Subscription upstream; + SubscriptionNotification(Subscription s) { + this.upstream = s; + } + + @Override + public String toString() { + return "NotificationLite.Subscription[" + upstream + "]"; + } + } + + /** + * Wraps a Disposable. + */ + static final class DisposableNotification implements Serializable { + + private static final long serialVersionUID = -7482590109178395495L; + final Disposable upstream; + + DisposableNotification(Disposable d) { + this.upstream = d; + } + + @Override + public String toString() { + return "NotificationLite.Disposable[" + upstream + "]"; + } + } + + /** + * Converts a value into a notification value. + * @param the actual value type + * @param value the value to convert + * @return the notification representing the value + */ + public static Object next(T value) { + return value; + } + + /** + * Returns a complete notification. + * @return a complete notification + */ + public static Object complete() { + return COMPLETE; + } + + /** + * Converts a Throwable into a notification value. + * @param e the Throwable to convert + * @return the notification representing the Throwable + */ + public static Object error(Throwable e) { + return new ErrorNotification(e); + } + + /** + * Converts a Subscription into a notification value. + * @param s the Subscription to convert + * @return the notification representing the Subscription + */ + public static Object subscription(Subscription s) { + return new SubscriptionNotification(s); + } + + /** + * Converts a Disposable into a notification value. + * @param d the disposable to convert + * @return the notification representing the Disposable + */ + public static Object disposable(Disposable d) { + return new DisposableNotification(d); + } + + /** + * Checks if the given object represents a complete notification. + * @param o the object to check + * @return true if the object represents a complete notification + */ + public static boolean isComplete(Object o) { + return o == COMPLETE; + } + + /** + * Checks if the given object represents a error notification. + * @param o the object to check + * @return true if the object represents a error notification + */ + public static boolean isError(Object o) { + return o instanceof ErrorNotification; + } + + /** + * Checks if the given object represents a subscription notification. + * @param o the object to check + * @return true if the object represents a subscription notification + */ + public static boolean isSubscription(Object o) { + return o instanceof SubscriptionNotification; + } + + public static boolean isDisposable(Object o) { + return o instanceof DisposableNotification; + } + + /** + * Extracts the value from the notification object. + * @param the expected value type when unwrapped + * @param o the notification object + * @return the extracted value + */ + @SuppressWarnings("unchecked") + public static T getValue(Object o) { + return (T)o; + } + + /** + * Extracts the Throwable from the notification object. + * @param o the notification object + * @return the extracted Throwable + */ + public static Throwable getError(Object o) { + return ((ErrorNotification)o).e; + } + + /** + * Extracts the Subscription from the notification object. + * @param o the notification object + * @return the extracted Subscription + */ + public static Subscription getSubscription(Object o) { + return ((SubscriptionNotification)o).upstream; + } + + public static Disposable getDisposable(Object o) { + return ((DisposableNotification)o).upstream; + } + + /** + * Calls the appropriate Subscriber method based on the type of the notification. + *

Does not check for a subscription notification, see {@link #acceptFull(Object, Subscriber)}. + * @param the expected value type when unwrapped + * @param o the notification object + * @param s the subscriber to call methods on + * @return true if the notification was a terminal event (i.e., complete or error) + * @see #acceptFull(Object, Subscriber) + */ + @SuppressWarnings("unchecked") + public static boolean accept(Object o, Subscriber s) { + if (o == COMPLETE) { + s.onComplete(); + return true; + } else + if (o instanceof ErrorNotification) { + s.onError(((ErrorNotification)o).e); + return true; + } + s.onNext((T)o); + return false; + } + + /** + * Calls the appropriate Observer method based on the type of the notification. + *

Does not check for a subscription notification. + * @param the expected value type when unwrapped + * @param o the notification object + * @param observer the Observer to call methods on + * @return true if the notification was a terminal event (i.e., complete or error) + */ + @SuppressWarnings("unchecked") + public static boolean accept(Object o, Observer observer) { + if (o == COMPLETE) { + observer.onComplete(); + return true; + } else + if (o instanceof ErrorNotification) { + observer.onError(((ErrorNotification)o).e); + return true; + } + observer.onNext((T)o); + return false; + } + + /** + * Calls the appropriate Subscriber method based on the type of the notification. + * @param the expected value type when unwrapped + * @param o the notification object + * @param s the subscriber to call methods on + * @return true if the notification was a terminal event (i.e., complete or error) + * @see #accept(Object, Subscriber) + */ + @SuppressWarnings("unchecked") + public static boolean acceptFull(Object o, Subscriber s) { + if (o == COMPLETE) { + s.onComplete(); + return true; + } else + if (o instanceof ErrorNotification) { + s.onError(((ErrorNotification)o).e); + return true; + } else + if (o instanceof SubscriptionNotification) { + s.onSubscribe(((SubscriptionNotification)o).upstream); + return false; + } + s.onNext((T)o); + return false; + } + + /** + * Calls the appropriate Observer method based on the type of the notification. + * @param the expected value type when unwrapped + * @param o the notification object + * @param observer the subscriber to call methods on + * @return true if the notification was a terminal event (i.e., complete or error) + * @see #accept(Object, Observer) + */ + @SuppressWarnings("unchecked") + public static boolean acceptFull(Object o, Observer observer) { + if (o == COMPLETE) { + observer.onComplete(); + return true; + } else + if (o instanceof ErrorNotification) { + observer.onError(((ErrorNotification)o).e); + return true; + } else + if (o instanceof DisposableNotification) { + observer.onSubscribe(((DisposableNotification)o).upstream); + return false; + } + observer.onNext((T)o); + return false; + } + + @Override + public String toString() { + return "NotificationLite.Complete"; + } +} diff --git a/src/main/java/io/reactivex/internal/util/ObservableQueueDrain.java b/src/main/java/io/reactivex/internal/util/ObservableQueueDrain.java new file mode 100755 index 0000000..5b595e6 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/ObservableQueueDrain.java @@ -0,0 +1,41 @@ +/** + * 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.internal.util; + +import io.reactivex.Observer; + +public interface ObservableQueueDrain { + + boolean cancelled(); + + boolean done(); + + Throwable error(); + + boolean enter(); + + /** + * Adds m to the wip counter. + * @param m the value to add + * @return the wip value after adding the value + */ + int leave(int m); + + /** + * Accept the value and return true if forwarded. + * @param a the subscriber to deliver values to + * @param v the value to deliver + */ + void accept(Observer a, T v); +} diff --git a/src/main/java/io/reactivex/internal/util/OpenHashSet.java b/src/main/java/io/reactivex/internal/util/OpenHashSet.java new file mode 100755 index 0000000..f971002 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/OpenHashSet.java @@ -0,0 +1,174 @@ +/** + * 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. + */ + +/* + * Inspired by fastutils' OpenHashSet implementation at + * https://github.com/vigna/fastutil/blob/master/drv/OpenHashSet.drv + */ + +package io.reactivex.internal.util; + +/** + * A simple open hash set with add, remove and clear capabilities only. + *

Doesn't support nor checks for {@code null}s. + * + * @param the element type + */ +public final class OpenHashSet { + private static final int INT_PHI = 0x9E3779B9; + + final float loadFactor; + int mask; + int size; + int maxSize; + T[] keys; + + public OpenHashSet() { + this(16, 0.75f); + } + + /** + * Creates an OpenHashSet with the initial capacity and load factor of 0.75f. + * @param capacity the initial capacity + */ + public OpenHashSet(int capacity) { + this(capacity, 0.75f); + } + + @SuppressWarnings("unchecked") + public OpenHashSet(int capacity, float loadFactor) { + this.loadFactor = loadFactor; + int c = Pow2.roundToPowerOfTwo(capacity); + this.mask = c - 1; + this.maxSize = (int)(loadFactor * c); + this.keys = (T[])new Object[c]; + } + + public boolean add(T value) { + final T[] a = keys; + final int m = mask; + + int pos = mix(value.hashCode()) & m; + T curr = a[pos]; + if (curr != null) { + if (curr.equals(value)) { + return false; + } + for (;;) { + pos = (pos + 1) & m; + curr = a[pos]; + if (curr == null) { + break; + } + if (curr.equals(value)) { + return false; + } + } + } + a[pos] = value; + if (++size >= maxSize) { + rehash(); + } + return true; + } + public boolean remove(T value) { + T[] a = keys; + int m = mask; + int pos = mix(value.hashCode()) & m; + T curr = a[pos]; + if (curr == null) { + return false; + } + if (curr.equals(value)) { + return removeEntry(pos, a, m); + } + for (;;) { + pos = (pos + 1) & m; + curr = a[pos]; + if (curr == null) { + return false; + } + if (curr.equals(value)) { + return removeEntry(pos, a, m); + } + } + } + + boolean removeEntry(int pos, T[] a, int m) { + size--; + + int last; + int slot; + T curr; + for (;;) { + last = pos; + pos = (pos + 1) & m; + for (;;) { + curr = a[pos]; + if (curr == null) { + a[last] = null; + return true; + } + slot = mix(curr.hashCode()) & m; + + if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) { + break; + } + + pos = (pos + 1) & m; + } + a[last] = curr; + } + } + + @SuppressWarnings("unchecked") + void rehash() { + T[] a = keys; + int i = a.length; + int newCap = i << 1; + int m = newCap - 1; + + T[] b = (T[])new Object[newCap]; + + for (int j = size; j-- != 0; ) { + while (a[--i] == null) { } // NOPMD + int pos = mix(a[i].hashCode()) & m; + if (b[pos] != null) { + for (;;) { + pos = (pos + 1) & m; + if (b[pos] == null) { + break; + } + } + } + b[pos] = a[i]; + } + + this.mask = m; + this.maxSize = (int)(newCap * loadFactor); + this.keys = b; + } + + static int mix(int x) { + final int h = x * INT_PHI; + return h ^ (h >>> 16); + } + + public Object[] keys() { + return keys; // NOPMD + } + + public int size() { + return size; + } +} diff --git a/src/main/java/io/reactivex/internal/util/Pow2.java b/src/main/java/io/reactivex/internal/util/Pow2.java new file mode 100755 index 0000000..db43171 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/Pow2.java @@ -0,0 +1,45 @@ +/** + * 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. + */ + +/* + * Original License: https://github.com/JCTools/JCTools/blob/master/LICENSE + * Original location: https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/util/Pow2.java + */ +package io.reactivex.internal.util; + +public final class Pow2 { + private Pow2() { + throw new IllegalStateException("No instances!"); + } + + /** + * Find the next larger positive power of two value up from the given value. If value is a power of two then + * this value will be returned. + * + * @param value from which next positive power of two will be found. + * @return the next positive power of 2 or this value if it is a power of 2. + */ + public static int roundToPowerOfTwo(final int value) { + return 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); + } + + /** + * Is this value a power of two. + * + * @param value to be tested to see if it is a power of two. + * @return true if the value is a power of 2 otherwise false. + */ + public static boolean isPowerOfTwo(final int value) { + return (value & (value - 1)) == 0; + } +} diff --git a/src/main/java/io/reactivex/internal/util/QueueDrain.java b/src/main/java/io/reactivex/internal/util/QueueDrain.java new file mode 100755 index 0000000..c652092 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/QueueDrain.java @@ -0,0 +1,46 @@ +/** + * 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.internal.util; + +import org.reactivestreams.Subscriber; + +public interface QueueDrain { + + boolean cancelled(); + + boolean done(); + + Throwable error(); + + boolean enter(); + + long requested(); + + long produced(long n); + + /** + * Adds m to the wip counter. + * @param m the value to add + * @return the current value after adding m + */ + int leave(int m); + + /** + * Accept the value and return true if forwarded. + * @param a the subscriber + * @param v the value + * @return true if the value was delivered + */ + boolean accept(Subscriber a, T v); +} diff --git a/src/main/java/io/reactivex/internal/util/QueueDrainHelper.java b/src/main/java/io/reactivex/internal/util/QueueDrainHelper.java new file mode 100755 index 0000000..ade0d5f --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/QueueDrainHelper.java @@ -0,0 +1,429 @@ +/** + * 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.internal.util; + +import java.util.Queue; +import java.util.concurrent.atomic.AtomicLong; + +import org.reactivestreams.*; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.functions.BooleanSupplier; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.*; + +/** + * Utility class to help with the queue-drain serialization idiom. + */ +public final class QueueDrainHelper { + /** Utility class. */ + private QueueDrainHelper() { + throw new IllegalStateException("No instances!"); + } + + /** + * Drain the queue but give up with an error if there aren't enough requests. + * @param the queue value type + * @param the emission value type + * @param q the queue + * @param a the subscriber + * @param delayError true if errors should be delayed after all normal items + * @param dispose the disposable to call when termination happens and cleanup is necessary + * @param qd the QueueDrain instance that gives status information to the drain logic + */ + public static void drainMaxLoop(SimplePlainQueue q, Subscriber a, boolean delayError, + Disposable dispose, QueueDrain qd) { + int missed = 1; + + for (;;) { + for (;;) { + boolean d = qd.done(); + + T v = q.poll(); + + boolean empty = v == null; + + if (checkTerminated(d, empty, a, delayError, q, qd)) { + if (dispose != null) { + dispose.dispose(); + } + return; + } + + if (empty) { + break; + } + + long r = qd.requested(); + if (r != 0L) { + if (qd.accept(a, v)) { + if (r != Long.MAX_VALUE) { + qd.produced(1); + } + } + } else { + q.clear(); + if (dispose != null) { + dispose.dispose(); + } + a.onError(new MissingBackpressureException("Could not emit value due to lack of requests.")); + return; + } + } + + missed = qd.leave(-missed); + if (missed == 0) { + break; + } + } + } + + public static boolean checkTerminated(boolean d, boolean empty, + Subscriber s, boolean delayError, SimpleQueue q, QueueDrain qd) { + if (qd.cancelled()) { + q.clear(); + return true; + } + + if (d) { + if (delayError) { + if (empty) { + Throwable err = qd.error(); + if (err != null) { + s.onError(err); + } else { + s.onComplete(); + } + return true; + } + } else { + Throwable err = qd.error(); + if (err != null) { + q.clear(); + s.onError(err); + return true; + } else + if (empty) { + s.onComplete(); + return true; + } + } + } + + return false; + } + + public static void drainLoop(SimplePlainQueue q, Observer a, boolean delayError, Disposable dispose, ObservableQueueDrain qd) { + + int missed = 1; + + for (;;) { + if (checkTerminated(qd.done(), q.isEmpty(), a, delayError, q, dispose, qd)) { + return; + } + + for (;;) { + boolean d = qd.done(); + T v = q.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, delayError, q, dispose, qd)) { + return; + } + + if (empty) { + break; + } + + qd.accept(a, v); + } + + missed = qd.leave(-missed); + if (missed == 0) { + break; + } + } + } + + public static boolean checkTerminated(boolean d, boolean empty, + Observer observer, boolean delayError, SimpleQueue q, Disposable disposable, ObservableQueueDrain qd) { + if (qd.cancelled()) { + q.clear(); + disposable.dispose(); + return true; + } + + if (d) { + if (delayError) { + if (empty) { + if (disposable != null) { + disposable.dispose(); + } + Throwable err = qd.error(); + if (err != null) { + observer.onError(err); + } else { + observer.onComplete(); + } + return true; + } + } else { + Throwable err = qd.error(); + if (err != null) { + q.clear(); + if (disposable != null) { + disposable.dispose(); + } + observer.onError(err); + return true; + } else + if (empty) { + if (disposable != null) { + disposable.dispose(); + } + observer.onComplete(); + return true; + } + } + } + + return false; + } + + /** + * Creates a queue: spsc-array if capacityHint is positive and + * spsc-linked-array if capacityHint is negative; in both cases, the + * capacity is the absolute value of prefetch. + * @param the value type of the queue + * @param capacityHint the capacity hint, negative value will create an array-based SPSC queue + * @return the queue instance + */ + public static SimpleQueue createQueue(int capacityHint) { + if (capacityHint < 0) { + return new SpscLinkedArrayQueue(-capacityHint); + } + return new SpscArrayQueue(capacityHint); + } + + /** + * Requests Long.MAX_VALUE if prefetch is negative or the exact + * amount if prefetch is positive. + * @param s the Subscription to request from + * @param prefetch the prefetch value + */ + public static void request(Subscription s, int prefetch) { + s.request(prefetch < 0 ? Long.MAX_VALUE : prefetch); + } + + static final long COMPLETED_MASK = 0x8000000000000000L; + static final long REQUESTED_MASK = 0x7FFFFFFFFFFFFFFFL; + + /** + * Accumulates requests (not validated) and handles the completed mode draining of the queue based on the requests. + * + *

+ * Post-completion backpressure handles the case when a source produces values based on + * requests when it is active but more values are available even after its completion. + * In this case, the onComplete() can't just emit the contents of the queue but has to + * coordinate with the requested amounts. This requires two distinct modes: active and + * completed. In active mode, requests flow through and the queue is not accessed but + * in completed mode, requests no-longer reach the upstream but help in draining the queue. + * + * @param the value type emitted + * @param n the request amount, positive (not validated) + * @param actual the target Subscriber to send events to + * @param queue the queue to drain if in the post-complete state + * @param state holds the request amount and the post-completed flag + * @param isCancelled a supplier that returns true if the drain has been cancelled + * @return true if the state indicates a completion state. + */ + public static boolean postCompleteRequest(long n, + Subscriber actual, + Queue queue, + AtomicLong state, + BooleanSupplier isCancelled) { + for (; ; ) { + long r = state.get(); + + // extract the current request amount + long r0 = r & REQUESTED_MASK; + + // preserve COMPLETED_MASK and calculate new requested amount + long u = (r & COMPLETED_MASK) | BackpressureHelper.addCap(r0, n); + + if (state.compareAndSet(r, u)) { + // (complete, 0) -> (complete, n) transition then replay + if (r == COMPLETED_MASK) { + + postCompleteDrain(n | COMPLETED_MASK, actual, queue, state, isCancelled); + + return true; + } + // (active, r) -> (active, r + n) transition then continue with requesting from upstream + return false; + } + } + + } + + static boolean isCancelled(BooleanSupplier cancelled) { + try { + return cancelled.getAsBoolean(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + return true; + } + } + + /** + * Drains the queue based on the outstanding requests in post-completed mode (only!). + * + * @param n the current request amount + * @param actual the target Subscriber to send events to + * @param queue the queue to drain if in the post-complete state + * @param state holds the request amount and the post-completed flag + * @param isCancelled a supplier that returns true if the drain has been cancelled + * @return true if the queue was completely drained or the drain process was cancelled + */ + static boolean postCompleteDrain(long n, + Subscriber actual, + Queue queue, + AtomicLong state, + BooleanSupplier isCancelled) { + +// TODO enable fast-path +// if (n == -1 || n == Long.MAX_VALUE) { +// for (;;) { +// if (isCancelled.getAsBoolean()) { +// break; +// } +// +// T v = queue.poll(); +// +// if (v == null) { +// actual.onComplete(); +// break; +// } +// +// actual.onNext(v); +// } +// +// return true; +// } + + long e = n & COMPLETED_MASK; + + for (; ; ) { + + while (e != n) { + if (isCancelled(isCancelled)) { + return true; + } + + T t = queue.poll(); + + if (t == null) { + actual.onComplete(); + return true; + } + + actual.onNext(t); + e++; + } + + if (isCancelled(isCancelled)) { + return true; + } + + if (queue.isEmpty()) { + actual.onComplete(); + return true; + } + + n = state.get(); + + if (n == e) { + + n = state.addAndGet(-(e & REQUESTED_MASK)); + + if ((n & REQUESTED_MASK) == 0L) { + return false; + } + + e = n & COMPLETED_MASK; + } + } + + } + + /** + * Signals the completion of the main sequence and switches to post-completion replay mode. + * + *

+ * Don't modify the queue after calling this method! + * + *

+ * Post-completion backpressure handles the case when a source produces values based on + * requests when it is active but more values are available even after its completion. + * In this case, the onComplete() can't just emit the contents of the queue but has to + * coordinate with the requested amounts. This requires two distinct modes: active and + * completed. In active mode, requests flow through and the queue is not accessed but + * in completed mode, requests no-longer reach the upstream but help in draining the queue. + *

+ * The algorithm utilizes the most significant bit (bit 63) of a long value (AtomicLong) since + * request amount only goes up to Long.MAX_VALUE (bits 0-62) and negative values aren't + * allowed. + * + * @param the value type emitted + * @param actual the target Subscriber to send events to + * @param queue the queue to drain if in the post-complete state + * @param state holds the request amount and the post-completed flag + * @param isCancelled a supplier that returns true if the drain has been cancelled + */ + public static void postComplete(Subscriber actual, + Queue queue, + AtomicLong state, + BooleanSupplier isCancelled) { + + if (queue.isEmpty()) { + actual.onComplete(); + return; + } + + if (postCompleteDrain(state.get(), actual, queue, state, isCancelled)) { + return; + } + + for (; ; ) { + long r = state.get(); + + if ((r & COMPLETED_MASK) != 0L) { + return; + } + + long u = r | COMPLETED_MASK; + // (active, r) -> (complete, r) transition + if (state.compareAndSet(r, u)) { + // if the requested amount was non-zero, drain the queue + if (r != 0L) { + postCompleteDrain(u, actual, queue, state, isCancelled); + } + + return; + } + } + + } +} diff --git a/src/main/java/io/reactivex/internal/util/SorterFunction.java b/src/main/java/io/reactivex/internal/util/SorterFunction.java new file mode 100755 index 0000000..33ef0ec --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/SorterFunction.java @@ -0,0 +1,33 @@ +/** + * 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.internal.util; + +import java.util.*; + +import io.reactivex.functions.Function; + +public final class SorterFunction implements Function, List> { + + final Comparator comparator; + + public SorterFunction(Comparator comparator) { + this.comparator = comparator; + } + + @Override + public List apply(List t) throws Exception { + Collections.sort(t, comparator); + return t; + } +} diff --git a/src/main/java/io/reactivex/internal/util/SuppressAnimalSniffer.java b/src/main/java/io/reactivex/internal/util/SuppressAnimalSniffer.java new file mode 100755 index 0000000..af3ca03 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/SuppressAnimalSniffer.java @@ -0,0 +1,26 @@ +/** + * 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.internal.util; + +import java.lang.annotation.*; + +/** + * Suppress errors by the AnimalSniffer plugin. + */ +@Retention(RetentionPolicy.CLASS) +@Documented +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) +public @interface SuppressAnimalSniffer { + +} diff --git a/src/main/java/io/reactivex/internal/util/VolatileSizeArrayList.java b/src/main/java/io/reactivex/internal/util/VolatileSizeArrayList.java new file mode 100755 index 0000000..45744c9 --- /dev/null +++ b/src/main/java/io/reactivex/internal/util/VolatileSizeArrayList.java @@ -0,0 +1,187 @@ +/** + * 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.internal.util; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Tracks the current underlying array size in a volatile field. + * + * @param the element type + * @since 2.0.7 + */ +public final class VolatileSizeArrayList extends AtomicInteger implements List, RandomAccess { + + private static final long serialVersionUID = 3972397474470203923L; + + final ArrayList list; + + public VolatileSizeArrayList() { + list = new ArrayList(); + } + + public VolatileSizeArrayList(int initialCapacity) { + list = new ArrayList(initialCapacity); + } + + @Override + public int size() { + return get(); + } + + @Override + public boolean isEmpty() { + return get() == 0; + } + + @Override + public boolean contains(Object o) { + return list.contains(o); + } + + @Override + public Iterator iterator() { + return list.iterator(); + } + + @Override + public Object[] toArray() { + return list.toArray(); + } + + @Override + public E[] toArray(E[] a) { + return list.toArray(a); + } + + @Override + public boolean add(T e) { + boolean b = list.add(e); + lazySet(list.size()); + return b; + } + + @Override + public boolean remove(Object o) { + boolean b = list.remove(o); + lazySet(list.size()); + return b; + } + + @Override + public boolean containsAll(Collection c) { + return list.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + boolean b = list.addAll(c); + lazySet(list.size()); + return b; + } + + @Override + public boolean addAll(int index, Collection c) { + boolean b = list.addAll(index, c); + lazySet(list.size()); + return b; + } + + @Override + public boolean removeAll(Collection c) { + boolean b = list.removeAll(c); + lazySet(list.size()); + return b; + } + + @Override + public boolean retainAll(Collection c) { + boolean b = list.retainAll(c); + lazySet(list.size()); + return b; + } + + @Override + public void clear() { + list.clear(); + lazySet(0); + } + + @Override + public T get(int index) { + return list.get(index); + } + + @Override + public T set(int index, T element) { + return list.set(index, element); + } + + @Override + public void add(int index, T element) { + list.add(index, element); + lazySet(list.size()); + } + + @Override + public T remove(int index) { + T v = list.remove(index); + lazySet(list.size()); + return v; + } + + @Override + public int indexOf(Object o) { + return list.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return list.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return list.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof VolatileSizeArrayList) { + return list.equals(((VolatileSizeArrayList)obj).list); + } + return list.equals(obj); + } + + @Override + public int hashCode() { + return list.hashCode(); + } + + @Override + public String toString() { + return list.toString(); + } +} diff --git a/src/main/java/io/reactivex/observables/ConnectableObservable.java b/src/main/java/io/reactivex/observables/ConnectableObservable.java new file mode 100755 index 0000000..09fa708 --- /dev/null +++ b/src/main/java/io/reactivex/observables/ConnectableObservable.java @@ -0,0 +1,294 @@ +/** + * 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.observables; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.*; +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.operators.observable.*; +import io.reactivex.internal.util.ConnectConsumer; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; + +/** + * A {@code ConnectableObservable} resembles an ordinary {@link Observable}, except that it does not begin + * emitting items when it is subscribed to, but only when its {@link #connect} method is called. In this way you + * can wait for all intended {@link Observer}s to {@link Observable#subscribe} to the {@code Observable} + * before the {@code Observable} begins emitting items. + *

+ * + * + * @see RxJava Wiki: + * Connectable Observable Operators + * @param + * the type of items emitted by the {@code ConnectableObservable} + */ +public abstract class ConnectableObservable extends Observable { + + /** + * Instructs the {@code ConnectableObservable} to begin emitting the items from its underlying + * {@link Observable} to its {@link Observer}s. + * + * @param connection + * the action that receives the connection subscription before the subscription to source happens + * allowing the caller to synchronously disconnect a synchronous source + * @see ReactiveX documentation: Connect + */ + public abstract void connect(@NonNull Consumer connection); + + /** + * Instructs the {@code ConnectableObservable} to begin emitting the items from its underlying + * {@link Observable} to its {@link Observer}s. + *

+ * To disconnect from a synchronous source, use the {@link #connect(Consumer)} method. + * + * @return the subscription representing the connection + * @see ReactiveX documentation: Connect + */ + public final Disposable connect() { + ConnectConsumer cc = new ConnectConsumer(); + connect(cc); + return cc.disposable; + } + + /** + * Apply a workaround for a race condition with the regular publish().refCount() + * so that racing observers and refCount won't hang. + * + * @return the ConnectableObservable to work with + * @since 2.2.10 + */ + @SuppressWarnings("unchecked") + private ConnectableObservable onRefCount() { + if (this instanceof ObservablePublishClassic) { + return RxJavaPlugins.onAssembly( + new ObservablePublishAlt(((ObservablePublishClassic)this).publishSource()) + ); + } + return this; + } + + /** + * Returns an {@code Observable} that stays connected to this {@code ConnectableObservable} as long as there + * is at least one subscription to this {@code ConnectableObservable}. + *

+ *
Scheduler:
+ *
This {@code refCount} overload does not operate on any particular {@link Scheduler}.
+ *
+ * @return an {@link Observable} + * @see ReactiveX documentation: RefCount + * @see #refCount(int) + * @see #refCount(long, TimeUnit) + * @see #refCount(int, long, TimeUnit) + */ + @NonNull + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public Observable refCount() { + return RxJavaPlugins.onAssembly(new ObservableRefCount(onRefCount())); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches the specified count and disconnect if all subscribers have unsubscribed. + *
+ *
Scheduler:
+ *
This {@code refCount} overload does not operate on any particular {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + public final Observable refCount(int subscriberCount) { + return refCount(subscriberCount, 0, TimeUnit.NANOSECONDS, Schedulers.trampoline()); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches 1 and disconnect after the specified + * timeout if all subscribers have unsubscribed. + *

+ *
Scheduler:
+ *
This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @return the new Observable instance + * @see #refCount(long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable refCount(long timeout, TimeUnit unit) { + return refCount(1, timeout, unit, Schedulers.computation()); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches 1 and disconnect after the specified + * timeout if all subscribers have unsubscribed. + *

+ *
Scheduler:
+ *
This {@code refCount} overload operates on the specified {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @param scheduler the target scheduler to wait on before disconnecting + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable refCount(long timeout, TimeUnit unit, Scheduler scheduler) { + return refCount(1, timeout, unit, scheduler); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches the specified count and disconnect after the specified + * timeout if all subscribers have unsubscribed. + *

+ *
Scheduler:
+ *
This {@code refCount} overload operates on the {@code computation} {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @return the new Observable instance + * @see #refCount(int, long, TimeUnit, Scheduler) + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.COMPUTATION) + public final Observable refCount(int subscriberCount, long timeout, TimeUnit unit) { + return refCount(subscriberCount, timeout, unit, Schedulers.computation()); + } + + /** + * Connects to the upstream {@code ConnectableObservable} if the number of subscribed + * subscriber reaches the specified count and disconnect after the specified + * timeout if all subscribers have unsubscribed. + *

+ *
Scheduler:
+ *
This {@code refCount} overload operates on the specified {@link Scheduler}.
+ *
+ *

History: 2.1.14 - experimental + * @param subscriberCount the number of subscribers required to connect to the upstream + * @param timeout the time to wait before disconnecting after all subscribers unsubscribed + * @param unit the time unit of the timeout + * @param scheduler the target scheduler to wait on before disconnecting + * @return the new Observable instance + * @since 2.2 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.CUSTOM) + public final Observable refCount(int subscriberCount, long timeout, TimeUnit unit, Scheduler scheduler) { + ObjectHelper.verifyPositive(subscriberCount, "subscriberCount"); + ObjectHelper.requireNonNull(unit, "unit is null"); + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaPlugins.onAssembly(new ObservableRefCount(onRefCount(), subscriberCount, timeout, unit, scheduler)); + } + + /** + * Returns an Observable that automatically connects (at most once) to this ConnectableObservable + * when the first Observer subscribes. + *

+ * + *

+ * The connection happens after the first subscription and happens at most once + * during the lifetime of the returned Observable. If this ConnectableObservable + * terminates, the connection is never renewed, no matter how Observers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Observer}s have disposed their {@code Disposable}s. + *

+ * This overload does not allow disconnecting the connection established via + * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload + * to gain access to the {@code Disposable} representing the only connection. + * + * @return an Observable that automatically connects to this ConnectableObservable + * when the first Observer subscribes + */ + @NonNull + public Observable autoConnect() { + return autoConnect(1); + } + + /** + * Returns an Observable that automatically connects (at most once) to this ConnectableObservable + * when the specified number of Observers subscribe to it. + *

+ * + *

+ * The connection happens after the given number of subscriptions and happens at most once + * during the lifetime of the returned Observable. If this ConnectableObservable + * terminates, the connection is never renewed, no matter how Observers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Observer}s have disposed their {@code Disposable}s. + *

+ * This overload does not allow disconnecting the connection established via + * {@link #connect(Consumer)}. Use the {@link #autoConnect(int, Consumer)} overload + * to gain access to the {@code Disposable} representing the only connection. + * + * @param numberOfSubscribers the number of subscribers to await before calling connect + * on the ConnectableObservable. A non-positive value indicates + * an immediate connection. + * @return an Observable that automatically connects to this ConnectableObservable + * when the specified number of Subscribers subscribe to it + */ + @NonNull + public Observable autoConnect(int numberOfSubscribers) { + return autoConnect(numberOfSubscribers, Functions.emptyConsumer()); + } + + /** + * Returns an Observable that automatically connects (at most once) to this ConnectableObservable + * when the specified number of Subscribers subscribe to it and calls the + * specified callback with the Subscription associated with the established connection. + *

+ * + *

+ * The connection happens after the given number of subscriptions and happens at most once + * during the lifetime of the returned Observable. If this ConnectableObservable + * terminates, the connection is never renewed, no matter how Observers come + * and go. Use {@link #refCount()} to renew a connection or dispose an active + * connection when all {@code Observer}s have disposed their {@code Disposable}s. + * + * @param numberOfSubscribers the number of subscribers to await before calling connect + * on the ConnectableObservable. A non-positive value indicates + * an immediate connection. + * @param connection the callback Consumer that will receive the Subscription representing the + * established connection + * @return an Observable that automatically connects to this ConnectableObservable + * when the specified number of Subscribers subscribe to it and calls the + * specified callback with the Subscription associated with the established connection + */ + @NonNull + public Observable autoConnect(int numberOfSubscribers, @NonNull Consumer connection) { + if (numberOfSubscribers <= 0) { + this.connect(connection); + return RxJavaPlugins.onAssembly(this); + } + return RxJavaPlugins.onAssembly(new ObservableAutoConnect(this, numberOfSubscribers, connection)); + } +} diff --git a/src/main/java/io/reactivex/observables/GroupedObservable.java b/src/main/java/io/reactivex/observables/GroupedObservable.java new file mode 100755 index 0000000..75518a8 --- /dev/null +++ b/src/main/java/io/reactivex/observables/GroupedObservable.java @@ -0,0 +1,54 @@ +/** + * 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.observables; + +import io.reactivex.Observable; +import io.reactivex.annotations.Nullable; + +/** + * An {@link Observable} that has been grouped by key, the value of which can be obtained with {@link #getKey()}. + *

+ * Note: A {@link GroupedObservable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedObservable}s that do not concern you. Instead, you can signal to them that they + * may discard their buffers by applying an operator like {@link Observable#take take}{@code (0)} to them. + * + * @param + * the type of the key + * @param + * the type of the items emitted by the {@code GroupedObservable} + * @see Observable#groupBy(io.reactivex.functions.Function) + * @see ReactiveX documentation: GroupBy + */ +public abstract class GroupedObservable extends Observable { + + final K key; + + /** + * Constructs a GroupedObservable with the given key. + * @param key the key + */ + protected GroupedObservable(@Nullable K key) { + this.key = key; + } + + /** + * Returns the key that identifies the group of items emitted by this {@code GroupedObservable}. + * + * @return the key that the items emitted by this {@code GroupedObservable} were grouped by + */ + @Nullable + public K getKey() { + return key; + } +} diff --git a/src/main/java/io/reactivex/observables/package-info.java b/src/main/java/io/reactivex/observables/package-info.java new file mode 100755 index 0000000..1cfff8c --- /dev/null +++ b/src/main/java/io/reactivex/observables/package-info.java @@ -0,0 +1,22 @@ +/** + * 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. + */ + +/** + * Classes supporting the Observable base reactive class: + * {@link io.reactivex.observables.ConnectableObservable} and + * {@link io.reactivex.observables.GroupedObservable}. + */ +package io.reactivex.observables; diff --git a/src/main/java/io/reactivex/observers/BaseTestConsumer.java b/src/main/java/io/reactivex/observers/BaseTestConsumer.java new file mode 100755 index 0000000..4c92c27 --- /dev/null +++ b/src/main/java/io/reactivex/observers/BaseTestConsumer.java @@ -0,0 +1,1077 @@ +/** + * 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.observers; + +import java.util.*; +import java.util.concurrent.*; + +import io.reactivex.Notification; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.CompositeException; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.util.*; + +/** + * Base class with shared infrastructure to support TestSubscriber and TestObserver. + * @param the value type consumed + * @param the subclass of this BaseTestConsumer + */ +public abstract class BaseTestConsumer> implements Disposable { + /** The latch that indicates an onError or onComplete has been called. */ + protected final CountDownLatch done; + /** The list of values received. */ + protected final List values; + /** The list of errors received. */ + protected final List errors; + /** The number of completions. */ + protected long completions; + /** The last thread seen by the observer. */ + protected Thread lastThread; + + protected boolean checkSubscriptionOnce; + + protected int initialFusionMode; + + protected int establishedFusionMode; + + /** + * The optional tag associated with this test consumer. + * @since 2.0.7 + */ + protected CharSequence tag; + + /** + * Indicates that one of the awaitX method has timed out. + * @since 2.0.7 + */ + protected boolean timeout; + + public BaseTestConsumer() { + this.values = new VolatileSizeArrayList(); + this.errors = new VolatileSizeArrayList(); + this.done = new CountDownLatch(1); + } + + /** + * Returns the last thread which called the onXXX methods of this TestObserver/TestSubscriber. + * @return the last thread which called the onXXX methods + */ + public final Thread lastThread() { + return lastThread; + } + + /** + * Returns a shared list of received onNext values. + *

+ * Note that accessing the items via certain methods of the {@link List} + * interface while the upstream is still actively emitting + * more items may result in a {@code ConcurrentModificationException}. + *

+ * The {@link List#size()} method will return the number of items + * already received by this TestObserver/TestSubscriber in a thread-safe + * manner that can be read via {@link List#get(int)}) method + * (index range of 0 to {@code List.size() - 1}). + *

+ * A view of the returned List can be created via {@link List#subList(int, int)} + * by using the bounds 0 (inclusive) to {@link List#size()} (exclusive) which, + * when accessed in a read-only fashion, should be also thread-safe and not throw any + * {@code ConcurrentModificationException}. + * @return a list of received onNext values + */ + public final List values() { + return values; + } + + /** + * Returns a shared list of received onError exceptions. + *

+ * Note that accessing the errors via certain methods of the {@link List} + * interface while the upstream is still actively emitting + * more items or errors may result in a {@code ConcurrentModificationException}. + *

+ * The {@link List#size()} method will return the number of errors + * already received by this TestObserver/TestSubscriber in a thread-safe + * manner that can be read via {@link List#get(int)}) method + * (index range of 0 to {@code List.size() - 1}). + *

+ * A view of the returned List can be created via {@link List#subList(int, int)} + * by using the bounds 0 (inclusive) to {@link List#size()} (exclusive) which, + * when accessed in a read-only fashion, should be also thread-safe and not throw any + * {@code ConcurrentModificationException}. + * @return a list of received events onError exceptions + */ + public final List errors() { + return errors; + } + + /** + * Returns the number of times onComplete was called. + * @return the number of times onComplete was called + */ + public final long completions() { + return completions; + } + + /** + * Returns true if TestObserver/TestSubscriber received any onError or onComplete events. + * @return true if TestObserver/TestSubscriber received any onError or onComplete events + */ + public final boolean isTerminated() { + return done.getCount() == 0; + } + + /** + * Returns the number of onNext values received. + * @return the number of onNext values received + */ + public final int valueCount() { + return values.size(); + } + + /** + * Returns the number of onError exceptions received. + * @return the number of onError exceptions received + */ + public final int errorCount() { + return errors.size(); + } + + /** + * Fail with the given message and add the sequence of errors as suppressed ones. + *

Note this is deliberately the only fail method. Most of the times an assertion + * would fail but it is possible it was due to an exception somewhere. This construct + * will capture those potential errors and report it along with the original failure. + * + * @param message the message to use + * @return AssertionError the prepared AssertionError instance + */ + protected final AssertionError fail(String message) { + StringBuilder b = new StringBuilder(64 + message.length()); + b.append(message); + + b.append(" (") + .append("latch = ").append(done.getCount()).append(", ") + .append("values = ").append(values.size()).append(", ") + .append("errors = ").append(errors.size()).append(", ") + .append("completions = ").append(completions) + ; + + if (timeout) { + b.append(", timeout!"); + } + + if (isDisposed()) { + b.append(", disposed!"); + } + + CharSequence tag = this.tag; + if (tag != null) { + b.append(", tag = ") + .append(tag); + } + + b + .append(')') + ; + + AssertionError ae = new AssertionError(b.toString()); + if (!errors.isEmpty()) { + if (errors.size() == 1) { + ae.initCause(errors.get(0)); + } else { + CompositeException ce = new CompositeException(errors); + ae.initCause(ce); + } + } + return ae; + } + + /** + * Awaits until this TestObserver/TestSubscriber receives an onError or onComplete events. + * @return this + * @throws InterruptedException if the current thread is interrupted while waiting + * @see #awaitTerminalEvent() + */ + @SuppressWarnings("unchecked") + public final U await() throws InterruptedException { + if (done.getCount() == 0) { + return (U)this; + } + + done.await(); + return (U)this; + } + + /** + * Awaits the specified amount of time or until this TestObserver/TestSubscriber + * receives an onError or onComplete events, whichever happens first. + * @param time the waiting time + * @param unit the time unit of the waiting time + * @return true if the TestObserver/TestSubscriber terminated, false if timeout happened + * @throws InterruptedException if the current thread is interrupted while waiting + * @see #awaitTerminalEvent(long, TimeUnit) + */ + public final boolean await(long time, TimeUnit unit) throws InterruptedException { + boolean d = done.getCount() == 0 || (done.await(time, unit)); + timeout = !d; + return d; + } + + // assertion methods + + /** + * Assert that this TestObserver/TestSubscriber received exactly one onComplete event. + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertComplete() { + long c = completions; + if (c == 0) { + throw fail("Not completed"); + } else + if (c > 1) { + throw fail("Multiple completions: " + c); + } + return (U)this; + } + + /** + * Assert that this TestObserver/TestSubscriber has not received any onComplete event. + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertNotComplete() { + long c = completions; + if (c == 1) { + throw fail("Completed!"); + } else + if (c > 1) { + throw fail("Multiple completions: " + c); + } + return (U)this; + } + + /** + * Assert that this TestObserver/TestSubscriber has not received any onError event. + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertNoErrors() { + int s = errors.size(); + if (s != 0) { + throw fail("Error(s) present: " + errors); + } + return (U)this; + } + + /** + * Assert that this TestObserver/TestSubscriber received exactly the specified onError event value. + * + *

The comparison is performed via Objects.equals(); since most exceptions don't + * implement equals(), this assertion may fail. Use the {@link #assertError(Class)} + * overload to test against the class of an error instead of an instance of an error + * or {@link #assertError(Predicate)} to test with different condition. + * @param error the error to check + * @return this + * @see #assertError(Class) + * @see #assertError(Predicate) + */ + public final U assertError(Throwable error) { + return assertError(Functions.equalsWith(error)); + } + + /** + * Asserts that this TestObserver/TestSubscriber received exactly one onError event which is an + * instance of the specified errorClass class. + * @param errorClass the error class to expect + * @return this + */ + @SuppressWarnings({ "unchecked", "rawtypes", "cast" }) + public final U assertError(Class errorClass) { + return (U)assertError((Predicate)Functions.isInstanceOf(errorClass)); + } + + /** + * Asserts that this TestObserver/TestSubscriber received exactly one onError event for which + * the provided predicate returns true. + * @param errorPredicate + * the predicate that receives the error Throwable + * and should return true for expected errors. + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertError(Predicate errorPredicate) { + int s = errors.size(); + if (s == 0) { + throw fail("No errors"); + } + + boolean found = false; + + for (Throwable e : errors) { + try { + if (errorPredicate.test(e)) { + found = true; + break; + } + } catch (Exception ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + if (found) { + if (s != 1) { + throw fail("Error present but other errors as well"); + } + } else { + throw fail("Error not present"); + } + return (U)this; + } + + /** + * Assert that this TestObserver/TestSubscriber received exactly one onNext value which is equal to + * the given value with respect to Objects.equals. + * @param value the value to expect + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertValue(T value) { + int s = values.size(); + if (s != 1) { + throw fail("expected: " + valueAndClass(value) + " but was: " + values); + } + T v = values.get(0); + if (!ObjectHelper.equals(value, v)) { + throw fail("expected: " + valueAndClass(value) + " but was: " + valueAndClass(v)); + } + return (U)this; + } + + /** + * Assert that this TestObserver/TestSubscriber did not receive an onNext value which is equal to + * the given value with respect to null-safe Object.equals. + * + *

History: 2.0.5 - experimental + * @param value the value to expect not being received + * @return this + * @since 2.1 + */ + @SuppressWarnings("unchecked") + public final U assertNever(T value) { + int s = values.size(); + + for (int i = 0; i < s; i++) { + T v = this.values.get(i); + if (ObjectHelper.equals(v, value)) { + throw fail("Value at position " + i + " is equal to " + valueAndClass(value) + "; Expected them to be different"); + } + } + return (U) this; + } + + /** + * Asserts that this TestObserver/TestSubscriber received exactly one onNext value for which + * the provided predicate returns true. + * @param valuePredicate + * the predicate that receives the onNext value + * and should return true for the expected value. + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertValue(Predicate valuePredicate) { + assertValueAt(0, valuePredicate); + + if (values.size() > 1) { + throw fail("Value present but other values as well"); + } + + return (U)this; + } + + /** + * Asserts that this TestObserver/TestSubscriber did not receive any onNext value for which + * the provided predicate returns true. + * + *

History: 2.0.5 - experimental + * @param valuePredicate the predicate that receives the onNext value + * and should return true for the expected value. + * @return this + * @since 2.1 + */ + @SuppressWarnings("unchecked") + public final U assertNever(Predicate valuePredicate) { + int s = values.size(); + + for (int i = 0; i < s; i++) { + T v = this.values.get(i); + try { + if (valuePredicate.test(v)) { + throw fail("Value at position " + i + " matches predicate " + valuePredicate.toString() + ", which was not expected."); + } + } catch (Exception ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + return (U)this; + } + + /** + * Asserts that this TestObserver/TestSubscriber received an onNext value at the given index + * which is equal to the given value with respect to null-safe Object.equals. + *

History: 2.1.3 - experimental + * @param index the position to assert on + * @param value the value to expect + * @return this + * @since 2.2 + */ + @SuppressWarnings("unchecked") + public final U assertValueAt(int index, T value) { + int s = values.size(); + if (s == 0) { + throw fail("No values"); + } + + if (index >= s) { + throw fail("Invalid index: " + index); + } + + T v = values.get(index); + if (!ObjectHelper.equals(value, v)) { + throw fail("expected: " + valueAndClass(value) + " but was: " + valueAndClass(v)); + } + return (U)this; + } + + /** + * Asserts that this TestObserver/TestSubscriber received an onNext value at the given index + * for the provided predicate returns true. + * @param index the position to assert on + * @param valuePredicate + * the predicate that receives the onNext value + * and should return true for the expected value. + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertValueAt(int index, Predicate valuePredicate) { + int s = values.size(); + if (s == 0) { + throw fail("No values"); + } + + if (index >= values.size()) { + throw fail("Invalid index: " + index); + } + + boolean found = false; + + try { + if (valuePredicate.test(values.get(index))) { + found = true; + } + } catch (Exception ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + + if (!found) { + throw fail("Value not present"); + } + return (U)this; + } + + /** + * Appends the class name to a non-null value. + * @param o the object + * @return the string representation + */ + public static String valueAndClass(Object o) { + if (o != null) { + return o + " (class: " + o.getClass().getSimpleName() + ")"; + } + return "null"; + } + + /** + * Assert that this TestObserver/TestSubscriber received the specified number onNext events. + * @param count the expected number of onNext events + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertValueCount(int count) { + int s = values.size(); + if (s != count) { + throw fail("Value counts differ; expected: " + count + " but was: " + s); + } + return (U)this; + } + + /** + * Assert that this TestObserver/TestSubscriber has not received any onNext events. + * @return this + */ + public final U assertNoValues() { + return assertValueCount(0); + } + + /** + * Assert that the TestObserver/TestSubscriber received only the specified values in the specified order. + * @param values the values expected + * @return this + * @see #assertValueSet(Collection) + */ + @SuppressWarnings("unchecked") + public final U assertValues(T... values) { + int s = this.values.size(); + if (s != values.length) { + throw fail("Value count differs; expected: " + values.length + " " + Arrays.toString(values) + + " but was: " + s + " " + this.values); + } + for (int i = 0; i < s; i++) { + T v = this.values.get(i); + T u = values[i]; + if (!ObjectHelper.equals(u, v)) { + throw fail("Values at position " + i + " differ; expected: " + valueAndClass(u) + " but was: " + valueAndClass(v)); + } + } + return (U)this; + } + + /** + * Assert that the TestObserver/TestSubscriber received only the specified values in the specified order without terminating. + *

History: 2.1.4 - experimental + * @param values the values expected + * @return this + * @since 2.2 + */ + public final U assertValuesOnly(T... values) { + return assertSubscribed() + .assertValues(values) + .assertNoErrors() + .assertNotComplete(); + } + + /** + * Assert that the TestObserver/TestSubscriber received only items that are in the specified + * collection as well, irrespective of the order they were received. + *

+ * This helps asserting when the order of the values is not guaranteed, i.e., when merging + * asynchronous streams. + *

+ * To ensure that only the expected items have been received, no more and no less, in any order, + * apply {@link #assertValueCount(int)} with {@code expected.size()}. + * + * @param expected the collection of values expected in any order + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertValueSet(Collection expected) { + if (expected.isEmpty()) { + assertNoValues(); + return (U)this; + } + for (T v : this.values) { + if (!expected.contains(v)) { + throw fail("Value not in the expected collection: " + valueAndClass(v)); + } + } + return (U)this; + } + + /** + * Assert that the TestObserver/TestSubscriber received only the specified values in any order without terminating. + *

History: 2.1.14 - experimental + * @param expected the collection of values expected in any order + * @return this + * @since 2.2 + */ + public final U assertValueSetOnly(Collection expected) { + return assertSubscribed() + .assertValueSet(expected) + .assertNoErrors() + .assertNotComplete(); + } + + /** + * Assert that the TestObserver/TestSubscriber received only the specified sequence of values in the same order. + * @param sequence the sequence of expected values in order + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertValueSequence(Iterable sequence) { + int i = 0; + Iterator actualIterator = values.iterator(); + Iterator expectedIterator = sequence.iterator(); + boolean actualNext; + boolean expectedNext; + for (;;) { + expectedNext = expectedIterator.hasNext(); + actualNext = actualIterator.hasNext(); + + if (!actualNext || !expectedNext) { + break; + } + + T u = expectedIterator.next(); + T v = actualIterator.next(); + + if (!ObjectHelper.equals(u, v)) { + throw fail("Values at position " + i + " differ; expected: " + valueAndClass(u) + " but was: " + valueAndClass(v)); + } + i++; + } + + if (actualNext) { + throw fail("More values received than expected (" + i + ")"); + } + if (expectedNext) { + throw fail("Fewer values received than expected (" + i + ")"); + } + return (U)this; + } + + /** + * Assert that the TestObserver/TestSubscriber received only the specified values in the specified order without terminating. + *

History: 2.1.14 - experimental + * @param sequence the sequence of expected values in order + * @return this + * @since 2.2 + */ + public final U assertValueSequenceOnly(Iterable sequence) { + return assertSubscribed() + .assertValueSequence(sequence) + .assertNoErrors() + .assertNotComplete(); + } + + /** + * Assert that the TestObserver/TestSubscriber terminated (i.e., the terminal latch reached zero). + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertTerminated() { + if (done.getCount() != 0) { + throw fail("Subscriber still running!"); + } + long c = completions; + if (c > 1) { + throw fail("Terminated with multiple completions: " + c); + } + int s = errors.size(); + if (s > 1) { + throw fail("Terminated with multiple errors: " + s); + } + + if (c != 0 && s != 0) { + throw fail("Terminated with multiple completions and errors: " + c); + } + return (U)this; + } + + /** + * Assert that the TestObserver/TestSubscriber has not terminated (i.e., the terminal latch is still non-zero). + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertNotTerminated() { + if (done.getCount() == 0) { + throw fail("Subscriber terminated!"); + } + return (U)this; + } + + /** + * Waits until the any terminal event has been received by this TestObserver/TestSubscriber + * or returns false if the wait has been interrupted. + * @return true if the TestObserver/TestSubscriber terminated, false if the wait has been interrupted + */ + public final boolean awaitTerminalEvent() { + try { + await(); + return true; + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return false; + } + } + + /** + * Awaits the specified amount of time or until this TestObserver/TestSubscriber + * receives an onError or onComplete events, whichever happens first. + * @param duration the waiting time + * @param unit the time unit of the waiting time + * @return true if the TestObserver/TestSubscriber terminated, false if timeout or interrupt happened + */ + public final boolean awaitTerminalEvent(long duration, TimeUnit unit) { + try { + return await(duration, unit); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return false; + } + } + + /** + * Assert that there is a single error and it has the given message. + * @param message the message expected + * @return this + */ + @SuppressWarnings("unchecked") + public final U assertErrorMessage(String message) { + int s = errors.size(); + if (s == 0) { + throw fail("No errors"); + } else + if (s == 1) { + Throwable e = errors.get(0); + String errorMessage = e.getMessage(); + if (!ObjectHelper.equals(message, errorMessage)) { + throw fail("Error message differs; exptected: " + message + " but was: " + errorMessage); + } + } else { + throw fail("Multiple errors"); + } + return (U)this; + } + + /** + * Returns a list of 3 other lists: the first inner list contains the plain + * values received; the second list contains the potential errors + * and the final list contains the potential completions as Notifications. + * + * @return a list of (values, errors, completion-notifications) + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public final List> getEvents() { + List> result = new ArrayList>(); + + result.add((List)values()); + + result.add((List)errors()); + + List completeList = new ArrayList(); + for (long i = 0; i < completions; i++) { + completeList.add(Notification.createOnComplete()); + } + result.add(completeList); + + return result; + } + + /** + * Assert that the onSubscribe method was called exactly once. + * @return this + */ + public abstract U assertSubscribed(); + + /** + * Assert that the onSubscribe method hasn't been called at all. + * @return this + */ + public abstract U assertNotSubscribed(); + + /** + * Assert that the upstream signalled the specified values in order and + * completed normally. + * @param values the expected values, asserted in order + * @return this + * @see #assertFailure(Class, Object...) + * @see #assertFailure(Predicate, Object...) + * @see #assertFailureAndMessage(Class, String, Object...) + */ + public final U assertResult(T... values) { + return assertSubscribed() + .assertValues(values) + .assertNoErrors() + .assertComplete(); + } + + /** + * Assert that the upstream signalled the specified values in order + * and then failed with a specific class or subclass of Throwable. + * @param error the expected exception (parent) class + * @param values the expected values, asserted in order + * @return this + */ + public final U assertFailure(Class error, T... values) { + return assertSubscribed() + .assertValues(values) + .assertError(error) + .assertNotComplete(); + } + + /** + * Assert that the upstream signalled the specified values in order and then failed + * with a Throwable for which the provided predicate returns true. + * @param errorPredicate + * the predicate that receives the error Throwable + * and should return true for expected errors. + * @param values the expected values, asserted in order + * @return this + */ + public final U assertFailure(Predicate errorPredicate, T... values) { + return assertSubscribed() + .assertValues(values) + .assertError(errorPredicate) + .assertNotComplete(); + } + + /** + * Assert that the upstream signalled the specified values in order, + * then failed with a specific class or subclass of Throwable + * and with the given exact error message. + * @param error the expected exception (parent) class + * @param message the expected failure message + * @param values the expected values, asserted in order + * @return this + */ + public final U assertFailureAndMessage(Class error, + String message, T... values) { + return assertSubscribed() + .assertValues(values) + .assertError(error) + .assertErrorMessage(message) + .assertNotComplete(); + } + + /** + * Awaits until the internal latch is counted down. + *

If the wait times out or gets interrupted, the TestObserver/TestSubscriber is cancelled. + * @param time the waiting time + * @param unit the time unit of the waiting time + * @return this + * @throws RuntimeException wrapping an InterruptedException if the wait is interrupted + */ + @SuppressWarnings("unchecked") + public final U awaitDone(long time, TimeUnit unit) { + try { + if (!done.await(time, unit)) { + timeout = true; + dispose(); + } + } catch (InterruptedException ex) { + dispose(); + throw ExceptionHelper.wrapOrThrow(ex); + } + return (U)this; + } + + /** + * Assert that the TestObserver/TestSubscriber has received a Disposable but no other events. + * @return this + */ + public final U assertEmpty() { + return assertSubscribed() + .assertNoValues() + .assertNoErrors() + .assertNotComplete(); + } + + /** + * Set the tag displayed along with an assertion failure's + * other state information. + *

History: 2.0.7 - experimental + * @param tag the string to display (null won't print any tag) + * @return this + * @since 2.1 + */ + @SuppressWarnings("unchecked") + public final U withTag(CharSequence tag) { + this.tag = tag; + return (U)this; + } + + /** + * Enumeration of default wait strategies when waiting for a specific number of + * items in {@link BaseTestConsumer#awaitCount(int, Runnable)}. + *

History: 2.0.7 - experimental + * @since 2.1 + */ + public enum TestWaitStrategy implements Runnable { + /** The wait loop will spin as fast as possible. */ + SPIN { + @Override + public void run() { + // nothing to do + } + }, + /** The current thread will be yielded. */ + YIELD { + @Override + public void run() { + Thread.yield(); + } + }, + /** The current thread sleeps for 1 millisecond. */ + SLEEP_1MS { + @Override + public void run() { + sleep(1); + } + }, + /** The current thread sleeps for 10 milliseconds. */ + SLEEP_10MS { + @Override + public void run() { + sleep(10); + } + }, + /** The current thread sleeps for 100 milliseconds. */ + SLEEP_100MS { + @Override + public void run() { + sleep(100); + } + }, + /** The current thread sleeps for 1000 milliseconds. */ + SLEEP_1000MS { + @Override + public void run() { + sleep(1000); + } + } + ; + + @Override + public abstract void run(); + + static void sleep(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + } + + /** + * Await until the TestObserver/TestSubscriber receives the given + * number of items or terminates by sleeping 10 milliseconds at a time + * up to 5000 milliseconds of timeout. + *

History: 2.0.7 - experimental + * @param atLeast the number of items expected at least + * @return this + * @see #awaitCount(int, Runnable, long) + * @since 2.1 + */ + public final U awaitCount(int atLeast) { + return awaitCount(atLeast, TestWaitStrategy.SLEEP_10MS, 5000); + } + + /** + * Await until the TestObserver/TestSubscriber receives the given + * number of items or terminates by waiting according to the wait + * strategy and up to 5000 milliseconds of timeout. + *

History: 2.0.7 - experimental + * @param atLeast the number of items expected at least + * @param waitStrategy a Runnable called when the current received count + * hasn't reached the expected value and there was + * no terminal event either, see {@link TestWaitStrategy} + * for examples + * @return this + * @see #awaitCount(int, Runnable, long) + * @since 2.1 + */ + public final U awaitCount(int atLeast, Runnable waitStrategy) { + return awaitCount(atLeast, waitStrategy, 5000); + } + + /** + * Await until the TestObserver/TestSubscriber receives the given + * number of items or terminates. + *

History: 2.0.7 - experimental + * @param atLeast the number of items expected at least + * @param waitStrategy a Runnable called when the current received count + * hasn't reached the expected value and there was + * no terminal event either, see {@link TestWaitStrategy} + * for examples + * @param timeoutMillis if positive, the await ends if the specified amount of + * time has passed no matter how many items were received + * @return this + * @since 2.1 + */ + @SuppressWarnings("unchecked") + public final U awaitCount(int atLeast, Runnable waitStrategy, long timeoutMillis) { + long start = System.currentTimeMillis(); + for (;;) { + if (timeoutMillis > 0L && System.currentTimeMillis() - start >= timeoutMillis) { + timeout = true; + break; + } + if (done.getCount() == 0L) { + break; + } + if (values.size() >= atLeast) { + break; + } + + waitStrategy.run(); + } + return (U)this; + } + + /** + * Returns true if an await timed out. + * @return true if one of the timeout-based await methods has timed out. + *

History: 2.0.7 - experimental + * @see #clearTimeout() + * @see #assertTimeout() + * @see #assertNoTimeout() + * @since 2.1 + */ + public final boolean isTimeout() { + return timeout; + } + + /** + * Clears the timeout flag set by the await methods when they timed out. + *

History: 2.0.7 - experimental + * @return this + * @since 2.1 + * @see #isTimeout() + */ + @SuppressWarnings("unchecked") + public final U clearTimeout() { + timeout = false; + return (U)this; + } + + /** + * Asserts that some awaitX method has timed out. + *

History: 2.0.7 - experimental + * @return this + * @since 2.1 + */ + @SuppressWarnings("unchecked") + public final U assertTimeout() { + if (!timeout) { + throw fail("No timeout?!"); + } + return (U)this; + } + + /** + * Asserts that some awaitX method has not timed out. + *

History: 2.0.7 - experimental + * @return this + * @since 2.1 + */ + @SuppressWarnings("unchecked") + public final U assertNoTimeout() { + if (timeout) { + throw fail("Timeout?!"); + } + return (U)this; + } +} diff --git a/src/main/java/io/reactivex/observers/DefaultObserver.java b/src/main/java/io/reactivex/observers/DefaultObserver.java new file mode 100755 index 0000000..2041c9d --- /dev/null +++ b/src/main/java/io/reactivex/observers/DefaultObserver.java @@ -0,0 +1,91 @@ +/** + * 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.observers; + +import io.reactivex.Observer; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * Abstract base implementation of an {@link Observer Observer} with support for cancelling a + * subscription via {@link #cancel()} (synchronously) and calls {@link #onStart()} + * when the subscription happens. + * + *

All pre-implemented final methods are thread-safe. + * + *

Use the protected {@link #cancel()} to dispose the sequence from within an + * {@code onNext} implementation. + * + *

Like all other consumers, {@code DefaultObserver} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onNext(Object)}, {@link #onError(Throwable)} + * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * If for some reason this can't be avoided, use {@link io.reactivex.Observable#safeSubscribe(Observer)} + * instead of the standard {@code subscribe()} method. + * + *

Example


+ * Observable.range(1, 5)
+ *     .subscribe(new DefaultObserver<Integer>() {
+ *         @Override public void onStart() {
+ *             System.out.println("Start!");
+ *         }
+ *         @Override public void onNext(Integer t) {
+ *             if (t == 3) {
+ *                 cancel();
+ *             }
+ *             System.out.println(t);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *         }
+ *     });
+ * 
+ * + * @param the value type + */ +public abstract class DefaultObserver implements Observer { + + private Disposable upstream; + + @Override + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.validate(this.upstream, d, getClass())) { + this.upstream = d; + onStart(); + } + } + + /** + * Cancels the upstream's disposable. + */ + protected final void cancel() { + Disposable upstream = this.upstream; + this.upstream = DisposableHelper.DISPOSED; + upstream.dispose(); + } + /** + * Called once the subscription has been set on this observer; override this + * to perform initialization. + */ + protected void onStart() { + } + +} diff --git a/src/main/java/io/reactivex/observers/DisposableCompletableObserver.java b/src/main/java/io/reactivex/observers/DisposableCompletableObserver.java new file mode 100755 index 0000000..5ec5194 --- /dev/null +++ b/src/main/java/io/reactivex/observers/DisposableCompletableObserver.java @@ -0,0 +1,80 @@ +/** + * 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.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.CompletableObserver; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract {@link CompletableObserver} that allows asynchronous cancellation by implementing Disposable. + * + *

All pre-implemented final methods are thread-safe. + * + *

Like all other consumers, {@code DisposableCompletableObserver} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onError(Throwable)} and + * {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * + *

Example


+ * Disposable d =
+ *     Completable.complete().delay(1, TimeUnit.SECONDS)
+ *     .subscribeWith(new DisposableMaybeObserver<Integer>() {
+ *         @Override public void onStart() {
+ *             System.out.println("Start!");
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ */ +public abstract class DisposableCompletableObserver implements CompletableObserver, Disposable { + + final AtomicReference upstream = new AtomicReference(); + + @Override + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { + onStart(); + } + } + + /** + * Called once the single upstream Disposable is set via onSubscribe. + */ + protected void onStart() { + } + + @Override + public final boolean isDisposed() { + return upstream.get() == DisposableHelper.DISPOSED; + } + + @Override + public final void dispose() { + DisposableHelper.dispose(upstream); + } +} diff --git a/src/main/java/io/reactivex/observers/DisposableMaybeObserver.java b/src/main/java/io/reactivex/observers/DisposableMaybeObserver.java new file mode 100755 index 0000000..6cff241 --- /dev/null +++ b/src/main/java/io/reactivex/observers/DisposableMaybeObserver.java @@ -0,0 +1,89 @@ +/** + * 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.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.MaybeObserver; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract {@link MaybeObserver} that allows asynchronous cancellation by implementing Disposable. + * + *

All pre-implemented final methods are thread-safe. + * + *

Note that {@link #onSuccess(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} are + * exclusive to each other, unlike a regular {@link io.reactivex.Observer Observer}, and + * {@code onComplete()} is never called after an {@code onSuccess()}. + * + *

Like all other consumers, {@code DisposableMaybeObserver} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onSuccess(Object)}, {@link #onError(Throwable)} and + * {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * + *

Example


+ * Disposable d =
+ *     Maybe.just(1).delay(1, TimeUnit.SECONDS)
+ *     .subscribeWith(new DisposableMaybeObserver<Integer>() {
+ *         @Override public void onStart() {
+ *             System.out.println("Start!");
+ *         }
+ *         @Override public void onSuccess(Integer t) {
+ *             System.out.println(t);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ * + * @param the received value type + */ +public abstract class DisposableMaybeObserver implements MaybeObserver, Disposable { + + final AtomicReference upstream = new AtomicReference(); + + @Override + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { + onStart(); + } + } + + /** + * Called once the single upstream Disposable is set via onSubscribe. + */ + protected void onStart() { + } + + @Override + public final boolean isDisposed() { + return upstream.get() == DisposableHelper.DISPOSED; + } + + @Override + public final void dispose() { + DisposableHelper.dispose(upstream); + } +} diff --git a/src/main/java/io/reactivex/observers/DisposableObserver.java b/src/main/java/io/reactivex/observers/DisposableObserver.java new file mode 100755 index 0000000..f98711b --- /dev/null +++ b/src/main/java/io/reactivex/observers/DisposableObserver.java @@ -0,0 +1,93 @@ +/** + * 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.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Observer; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract {@link Observer} that allows asynchronous cancellation by implementing Disposable. + * + *

All pre-implemented final methods are thread-safe. + * + *

Use the public {@link #dispose()} method to dispose the sequence from within an + * {@code onNext} implementation. + * + *

Like all other consumers, {@code DisposableObserver} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onNext(Object)}, {@link #onError(Throwable)} + * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * If for some reason this can't be avoided, use {@link io.reactivex.Observable#safeSubscribe(Observer)} + * instead of the standard {@code subscribe()} method. + * + *

Example


+ * Disposable d =
+ *     Observable.range(1, 5)
+ *     .subscribeWith(new DisposableObserver<Integer>() {
+ *         @Override public void onStart() {
+ *             System.out.println("Start!");
+ *         }
+ *         @Override public void onNext(Integer t) {
+ *             if (t == 3) {
+ *                 dispose();
+ *             }
+ *             System.out.println(t);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ * + * @param the received value type + */ +public abstract class DisposableObserver implements Observer, Disposable { + + final AtomicReference upstream = new AtomicReference(); + + @Override + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { + onStart(); + } + } + + /** + * Called once the single upstream Disposable is set via onSubscribe. + */ + protected void onStart() { + } + + @Override + public final boolean isDisposed() { + return upstream.get() == DisposableHelper.DISPOSED; + } + + @Override + public final void dispose() { + DisposableHelper.dispose(upstream); + } +} diff --git a/src/main/java/io/reactivex/observers/DisposableSingleObserver.java b/src/main/java/io/reactivex/observers/DisposableSingleObserver.java new file mode 100755 index 0000000..38224e0 --- /dev/null +++ b/src/main/java/io/reactivex/observers/DisposableSingleObserver.java @@ -0,0 +1,82 @@ +/** + * 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.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.SingleObserver; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract {@link SingleObserver} that allows asynchronous cancellation by implementing Disposable. + * + *

All pre-implemented final methods are thread-safe. + * + *

Like all other consumers, {@code DisposableSingleObserver} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onSuccess(Object)} and {@link #onError(Throwable)} + * are not allowed to throw any unchecked exceptions. + * + *

Example


+ * Disposable d =
+ *     Single.just(1).delay(1, TimeUnit.SECONDS)
+ *     .subscribeWith(new DisposableSingleObserver<Integer>() {
+ *         @Override public void onStart() {
+ *             System.out.println("Start!");
+ *         }
+ *         @Override public void onSuccess(Integer t) {
+ *             System.out.println(t);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ * + * @param the received value type + */ +public abstract class DisposableSingleObserver implements SingleObserver, Disposable { + + final AtomicReference upstream = new AtomicReference(); + + @Override + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { + onStart(); + } + } + + /** + * Called once the single upstream Disposable is set via onSubscribe. + */ + protected void onStart() { + } + + @Override + public final boolean isDisposed() { + return upstream.get() == DisposableHelper.DISPOSED; + } + + @Override + public final void dispose() { + DisposableHelper.dispose(upstream); + } +} diff --git a/src/main/java/io/reactivex/observers/LambdaConsumerIntrospection.java b/src/main/java/io/reactivex/observers/LambdaConsumerIntrospection.java new file mode 100755 index 0000000..31588ab --- /dev/null +++ b/src/main/java/io/reactivex/observers/LambdaConsumerIntrospection.java @@ -0,0 +1,33 @@ +/** + * 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.observers; + +/** + * An interface that indicates that the implementing type is composed of individual components and exposes information + * about their behavior. + * + *

NOTE: This is considered a read-only public API and is not intended to be implemented externally. + *

History: 2.1.4 - experimental + * @since 2.2 + */ +public interface LambdaConsumerIntrospection { + + /** + * Returns true or false if a custom onError consumer has been provided. + * @return {@code true} if a custom onError consumer implementation was supplied. Returns {@code false} if the + * implementation is missing an error consumer and thus using a throwing default implementation. + */ + boolean hasCustomOnError(); + +} diff --git a/src/main/java/io/reactivex/observers/ResourceCompletableObserver.java b/src/main/java/io/reactivex/observers/ResourceCompletableObserver.java new file mode 100755 index 0000000..af1dae1 --- /dev/null +++ b/src/main/java/io/reactivex/observers/ResourceCompletableObserver.java @@ -0,0 +1,132 @@ +/** + * 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.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.CompletableObserver; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract {@link CompletableObserver} that allows asynchronous cancellation of its subscription and associated resources. + * + *

All pre-implemented final methods are thread-safe. + * + *

Override the protected {@link #onStart()} to perform initialization when this + * {@code ResourceCompletableObserver} is subscribed to a source. + * + *

Use the public {@link #dispose()} method to dispose the sequence externally and release + * all resources. + * + *

To release the associated resources, one has to call {@link #dispose()} + * in {@code onError()} and {@code onComplete()} explicitly. + * + *

Use {@link #add(Disposable)} to associate resources (as {@link Disposable Disposable}s) + * with this {@code ResourceCompletableObserver} that will be cleaned up when {@link #dispose()} is called. + * Removing previously associated resources is not possible but one can create a + * {@link io.reactivex.disposables.CompositeDisposable CompositeDisposable}, associate it with this + * {@code ResourceCompletableObserver} and then add/remove resources to/from the {@code CompositeDisposable} + * freely. + * + *

Like all other consumers, {@code ResourceCompletableObserver} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onError(Throwable)} + * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * + *

Example


+ * Disposable d =
+ *     Completable.complete().delay(1, TimeUnit.SECONDS)
+ *     .subscribeWith(new ResourceCompletableObserver() {
+ *         @Override public void onStart() {
+ *             add(Schedulers.single()
+ *                 .scheduleDirect(() -> System.out.println("Time!"),
+ *                     2, TimeUnit.SECONDS));
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *             dispose();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *             dispose();
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ */ +public abstract class ResourceCompletableObserver implements CompletableObserver, Disposable { + /** The active subscription. */ + private final AtomicReference upstream = new AtomicReference(); + + /** The resource composite, can never be null. */ + private final ListCompositeDisposable resources = new ListCompositeDisposable(); + + /** + * Adds a resource to this ResourceObserver. + * + * @param resource the resource to add + * + * @throws NullPointerException if resource is null + */ + public final void add(@NonNull Disposable resource) { + ObjectHelper.requireNonNull(resource, "resource is null"); + resources.add(resource); + } + + @Override + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { + onStart(); + } + } + + /** + * Called once the upstream sets a Subscription on this ResourceObserver. + * + *

You can perform initialization at this moment. The default + * implementation does nothing. + */ + protected void onStart() { + } + + /** + * Cancels the main disposable (if any) and disposes the resources associated with + * this ResourceObserver (if any). + * + *

This method can be called before the upstream calls onSubscribe at which + * case the main Disposable will be immediately disposed. + */ + @Override + public final void dispose() { + if (DisposableHelper.dispose(upstream)) { + resources.dispose(); + } + } + + /** + * Returns true if this ResourceObserver has been disposed/cancelled. + * @return true if this ResourceObserver has been disposed/cancelled + */ + @Override + public final boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } +} diff --git a/src/main/java/io/reactivex/observers/ResourceMaybeObserver.java b/src/main/java/io/reactivex/observers/ResourceMaybeObserver.java new file mode 100755 index 0000000..9a50a51 --- /dev/null +++ b/src/main/java/io/reactivex/observers/ResourceMaybeObserver.java @@ -0,0 +1,142 @@ +/** + * 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.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.MaybeObserver; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract {@link MaybeObserver} that allows asynchronous cancellation of its subscription and associated resources. + * + *

All pre-implemented final methods are thread-safe. + * + *

Note that {@link #onSuccess(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} are + * exclusive to each other, unlike a regular {@link io.reactivex.Observer Observer}, and + * {@code onComplete()} is never called after an {@code onSuccess()}. + * + *

Override the protected {@link #onStart()} to perform initialization when this + * {@code ResourceMaybeObserver} is subscribed to a source. + * + *

Use the public {@link #dispose()} method to dispose the sequence externally and release + * all resources. + * + *

To release the associated resources, one has to call {@link #dispose()} + * in {@code onSuccess()}, {@code onError()} and {@code onComplete()} explicitly. + * + *

Use {@link #add(Disposable)} to associate resources (as {@link Disposable Disposable}s) + * with this {@code ResourceMaybeObserver} that will be cleaned up when {@link #dispose()} is called. + * Removing previously associated resources is not possible but one can create a + * {@link io.reactivex.disposables.CompositeDisposable CompositeDisposable}, associate it with this + * {@code ResourceMaybeObserver} and then add/remove resources to/from the {@code CompositeDisposable} + * freely. + * + *

Like all other consumers, {@code ResourceMaybeObserver} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onSuccess(Object)}, {@link #onError(Throwable)} + * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * + *

Example


+ * Disposable d =
+ *     Maybe.just(1).delay(1, TimeUnit.SECONDS)
+ *     .subscribeWith(new ResourceMaybeObserver<Integer>() {
+ *         @Override public void onStart() {
+ *             add(Schedulers.single()
+ *                 .scheduleDirect(() -> System.out.println("Time!"),
+ *                     2, TimeUnit.SECONDS));
+ *         }
+ *         @Override public void onSuccess(Integer t) {
+ *             System.out.println(t);
+ *             dispose();
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *             dispose();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *             dispose();
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ * + * @param the value type + */ +public abstract class ResourceMaybeObserver implements MaybeObserver, Disposable { + /** The active subscription. */ + private final AtomicReference upstream = new AtomicReference(); + + /** The resource composite, can never be null. */ + private final ListCompositeDisposable resources = new ListCompositeDisposable(); + + /** + * Adds a resource to this ResourceObserver. + * + * @param resource the resource to add + * + * @throws NullPointerException if resource is null + */ + public final void add(@NonNull Disposable resource) { + ObjectHelper.requireNonNull(resource, "resource is null"); + resources.add(resource); + } + + @Override + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { + onStart(); + } + } + + /** + * Called once the upstream sets a Subscription on this ResourceObserver. + * + *

You can perform initialization at this moment. The default + * implementation does nothing. + */ + protected void onStart() { + } + + /** + * Cancels the main disposable (if any) and disposes the resources associated with + * this ResourceObserver (if any). + * + *

This method can be called before the upstream calls onSubscribe at which + * case the main Disposable will be immediately disposed. + */ + @Override + public final void dispose() { + if (DisposableHelper.dispose(upstream)) { + resources.dispose(); + } + } + + /** + * Returns true if this ResourceObserver has been disposed/cancelled. + * @return true if this ResourceObserver has been disposed/cancelled + */ + @Override + public final boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } +} diff --git a/src/main/java/io/reactivex/observers/ResourceObserver.java b/src/main/java/io/reactivex/observers/ResourceObserver.java new file mode 100755 index 0000000..9cf967b --- /dev/null +++ b/src/main/java/io/reactivex/observers/ResourceObserver.java @@ -0,0 +1,140 @@ +/** + * 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.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Observer; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract {@link Observer} that allows asynchronous cancellation of its subscription and associated resources. + * + *

All pre-implemented final methods are thread-safe. + * + *

To release the associated resources, one has to call {@link #dispose()} + * in {@code onError()} and {@code onComplete()} explicitly. + * + *

Use {@link #add(Disposable)} to associate resources (as {@link Disposable Disposable}s) + * with this {@code ResourceObserver} that will be cleaned up when {@link #dispose()} is called. + * Removing previously associated resources is not possible but one can create a + * {@link io.reactivex.disposables.CompositeDisposable CompositeDisposable}, associate it with this + * {@code ResourceObserver} and then add/remove resources to/from the {@code CompositeDisposable} + * freely. + * + *

Use the {@link #dispose()} to dispose the sequence from within an + * {@code onNext} implementation. + * + *

Like all other consumers, {@code ResourceObserver} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onNext(Object)}, {@link #onError(Throwable)} + * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * If for some reason this can't be avoided, use {@link io.reactivex.Observable#safeSubscribe(Observer)} + * instead of the standard {@code subscribe()} method. + * + *

Example


+ * Disposable d =
+ *     Observable.range(1, 5)
+ *     .subscribeWith(new ResourceObserver<Integer>() {
+ *         @Override public void onStart() {
+ *             add(Schedulers.single()
+ *                 .scheduleDirect(() -> System.out.println("Time!"),
+ *                     2, TimeUnit.SECONDS));
+ *             request(1);
+ *         }
+ *         @Override public void onNext(Integer t) {
+ *             if (t == 3) {
+ *                 dispose();
+ *             }
+ *             System.out.println(t);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *             dispose();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *             dispose();
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ * + * @param the value type + */ +public abstract class ResourceObserver implements Observer, Disposable { + /** The active subscription. */ + private final AtomicReference upstream = new AtomicReference(); + + /** The resource composite, can never be null. */ + private final ListCompositeDisposable resources = new ListCompositeDisposable(); + + /** + * Adds a resource to this ResourceObserver. + * + * @param resource the resource to add + * + * @throws NullPointerException if resource is null + */ + public final void add(@NonNull Disposable resource) { + ObjectHelper.requireNonNull(resource, "resource is null"); + resources.add(resource); + } + + @Override + public final void onSubscribe(Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { + onStart(); + } + } + + /** + * Called once the upstream sets a Subscription on this ResourceObserver. + * + *

You can perform initialization at this moment. The default + * implementation does nothing. + */ + protected void onStart() { + } + + /** + * Cancels the main disposable (if any) and disposes the resources associated with + * this ResourceObserver (if any). + * + *

This method can be called before the upstream calls onSubscribe at which + * case the main Disposable will be immediately disposed. + */ + @Override + public final void dispose() { + if (DisposableHelper.dispose(upstream)) { + resources.dispose(); + } + } + + /** + * Returns true if this ResourceObserver has been disposed/cancelled. + * @return true if this ResourceObserver has been disposed/cancelled + */ + @Override + public final boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } +} diff --git a/src/main/java/io/reactivex/observers/ResourceSingleObserver.java b/src/main/java/io/reactivex/observers/ResourceSingleObserver.java new file mode 100755 index 0000000..3f56a19 --- /dev/null +++ b/src/main/java/io/reactivex/observers/ResourceSingleObserver.java @@ -0,0 +1,135 @@ +/** + * 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.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.SingleObserver; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract {@link SingleObserver} that allows asynchronous cancellation of its subscription + * and the associated resources. + * + *

All pre-implemented final methods are thread-safe. + * + *

Override the protected {@link #onStart()} to perform initialization when this + * {@code ResourceSingleObserver} is subscribed to a source. + * + *

Use the public {@link #dispose()} method to dispose the sequence externally and release + * all resources. + * + *

To release the associated resources, one has to call {@link #dispose()} + * in {@code onSuccess()} and {@code onError()} explicitly. + * + *

Use {@link #add(Disposable)} to associate resources (as {@link Disposable Disposable}s) + * with this {@code ResourceSingleObserver} that will be cleaned up when {@link #dispose()} is called. + * Removing previously associated resources is not possible but one can create a + * {@link io.reactivex.disposables.CompositeDisposable CompositeDisposable}, associate it with this + * {@code ResourceSingleObserver} and then add/remove resources to/from the {@code CompositeDisposable} + * freely. + * + *

Like all other consumers, {@code ResourceSingleObserver} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onSuccess(Object)} and {@link #onError(Throwable)} + * are not allowed to throw any unchecked exceptions. + * + *

Example


+ * Disposable d =
+ *     Single.just(1).delay(1, TimeUnit.SECONDS)
+ *     .subscribeWith(new ResourceSingleObserver<Integer>() {
+ *         @Override public void onStart() {
+ *             add(Schedulers.single()
+ *                 .scheduleDirect(() -> System.out.println("Time!"),
+ *                     2, TimeUnit.SECONDS));
+ *         }
+ *         @Override public void onSuccess(Integer t) {
+ *             System.out.println(t);
+ *             dispose();
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *             dispose();
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ * + * @param the value type + */ +public abstract class ResourceSingleObserver implements SingleObserver, Disposable { + /** The active subscription. */ + private final AtomicReference upstream = new AtomicReference(); + + /** The resource composite, can never be null. */ + private final ListCompositeDisposable resources = new ListCompositeDisposable(); + + /** + * Adds a resource to this ResourceObserver. + * + * @param resource the resource to add + * + * @throws NullPointerException if resource is null + */ + public final void add(@NonNull Disposable resource) { + ObjectHelper.requireNonNull(resource, "resource is null"); + resources.add(resource); + } + + @Override + public final void onSubscribe(@NonNull Disposable d) { + if (EndConsumerHelper.setOnce(this.upstream, d, getClass())) { + onStart(); + } + } + + /** + * Called once the upstream sets a Subscription on this ResourceObserver. + * + *

You can perform initialization at this moment. The default + * implementation does nothing. + */ + protected void onStart() { + } + + /** + * Cancels the main disposable (if any) and disposes the resources associated with + * this ResourceObserver (if any). + * + *

This method can be called before the upstream calls onSubscribe at which + * case the main Disposable will be immediately disposed. + */ + @Override + public final void dispose() { + if (DisposableHelper.dispose(upstream)) { + resources.dispose(); + } + } + + /** + * Returns true if this ResourceObserver has been disposed/cancelled. + * @return true if this ResourceObserver has been disposed/cancelled + */ + @Override + public final boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } +} diff --git a/src/main/java/io/reactivex/observers/SafeObserver.java b/src/main/java/io/reactivex/observers/SafeObserver.java new file mode 100755 index 0000000..77dbfb2 --- /dev/null +++ b/src/main/java/io/reactivex/observers/SafeObserver.java @@ -0,0 +1,220 @@ +/** + * 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.observers; + +import io.reactivex.Observer; +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.internal.disposables.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Wraps another Subscriber and ensures all onXXX methods conform the protocol + * (except the requirement for serialized access). + * + * @param the value type + */ +public final class SafeObserver implements Observer, Disposable { + /** The actual Subscriber. */ + final Observer downstream; + /** The subscription. */ + Disposable upstream; + /** Indicates a terminal state. */ + boolean done; + + /** + * Constructs a SafeObserver by wrapping the given actual Observer. + * @param downstream the actual Observer to wrap, not null (not validated) + */ + public SafeObserver(@NonNull Observer downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(@NonNull Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + try { + downstream.onSubscribe(this); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + done = true; + // can't call onError because the actual's state may be corrupt at this point + try { + d.dispose(); + } catch (Throwable e1) { + Exceptions.throwIfFatal(e1); + RxJavaPlugins.onError(new CompositeException(e, e1)); + return; + } + RxJavaPlugins.onError(e); + } + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(@NonNull T t) { + if (done) { + return; + } + if (upstream == null) { + onNextNoSubscription(); + return; + } + + if (t == null) { + Throwable ex = new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + try { + upstream.dispose(); + } catch (Throwable e1) { + Exceptions.throwIfFatal(e1); + onError(new CompositeException(ex, e1)); + return; + } + onError(ex); + return; + } + + try { + downstream.onNext(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + try { + upstream.dispose(); + } catch (Throwable e1) { + Exceptions.throwIfFatal(e1); + onError(new CompositeException(e, e1)); + return; + } + onError(e); + } + } + + void onNextNoSubscription() { + done = true; + + Throwable ex = new NullPointerException("Subscription not set!"); + + try { + downstream.onSubscribe(EmptyDisposable.INSTANCE); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because the actual's state may be corrupt at this point + RxJavaPlugins.onError(new CompositeException(ex, e)); + return; + } + try { + downstream.onError(ex); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // if onError failed, all that's left is to report the error to plugins + RxJavaPlugins.onError(new CompositeException(ex, e)); + } + } + + @Override + public void onError(@NonNull Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + + if (upstream == null) { + Throwable npe = new NullPointerException("Subscription not set!"); + + try { + downstream.onSubscribe(EmptyDisposable.INSTANCE); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because the actual's state may be corrupt at this point + RxJavaPlugins.onError(new CompositeException(t, npe, e)); + return; + } + try { + downstream.onError(new CompositeException(t, npe)); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // if onError failed, all that's left is to report the error to plugins + RxJavaPlugins.onError(new CompositeException(t, npe, e)); + } + return; + } + + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + + try { + downstream.onError(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + RxJavaPlugins.onError(new CompositeException(t, ex)); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + + done = true; + + if (upstream == null) { + onCompleteNoSubscription(); + return; + } + + try { + downstream.onComplete(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + + void onCompleteNoSubscription() { + + Throwable ex = new NullPointerException("Subscription not set!"); + + try { + downstream.onSubscribe(EmptyDisposable.INSTANCE); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because the actual's state may be corrupt at this point + RxJavaPlugins.onError(new CompositeException(ex, e)); + return; + } + try { + downstream.onError(ex); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // if onError failed, all that's left is to report the error to plugins + RxJavaPlugins.onError(new CompositeException(ex, e)); + } + } + +} diff --git a/src/main/java/io/reactivex/observers/SerializedObserver.java b/src/main/java/io/reactivex/observers/SerializedObserver.java new file mode 100755 index 0000000..31badf7 --- /dev/null +++ b/src/main/java/io/reactivex/observers/SerializedObserver.java @@ -0,0 +1,200 @@ +/** + * 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.observers; + +import io.reactivex.Observer; +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Serializes access to the onNext, onError and onComplete methods of another Observer. + * + *

Note that {@link #onSubscribe(Disposable)} is not serialized in respect of the other methods so + * make sure the {@code onSubscribe()} is called with a non-null {@code Disposable} + * before any of the other methods are called. + * + *

The implementation assumes that the actual Observer's methods don't throw. + * + * @param the value type + */ +public final class SerializedObserver implements Observer, Disposable { + final Observer downstream; + final boolean delayError; + + static final int QUEUE_LINK_SIZE = 4; + + Disposable upstream; + + boolean emitting; + AppendOnlyLinkedArrayList queue; + + volatile boolean done; + + /** + * Construct a SerializedObserver by wrapping the given actual Observer. + * @param downstream the actual Observer, not null (not verified) + */ + public SerializedObserver(@NonNull Observer downstream) { + this(downstream, false); + } + + /** + * Construct a SerializedObserver by wrapping the given actual Observer and + * optionally delaying the errors till all regular values have been emitted + * from the internal buffer. + * @param actual the actual Observer, not null (not verified) + * @param delayError if true, errors are emitted after regular values have been emitted + */ + public SerializedObserver(@NonNull Observer actual, boolean delayError) { + this.downstream = actual; + this.delayError = delayError; + } + + @Override + public void onSubscribe(@NonNull Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void dispose() { + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override + public void onNext(@NonNull T t) { + if (done) { + return; + } + if (t == null) { + upstream.dispose(); + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + synchronized (this) { + if (done) { + return; + } + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(QUEUE_LINK_SIZE); + queue = q; + } + q.add(NotificationLite.next(t)); + return; + } + emitting = true; + } + + downstream.onNext(t); + + emitLoop(); + } + + @Override + public void onError(@NonNull Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + boolean reportError; + synchronized (this) { + if (done) { + reportError = true; + } else + if (emitting) { + done = true; + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(QUEUE_LINK_SIZE); + queue = q; + } + Object err = NotificationLite.error(t); + if (delayError) { + q.add(err); + } else { + q.setFirst(err); + } + return; + } else { + done = true; + emitting = true; + reportError = false; + } + } + + if (reportError) { + RxJavaPlugins.onError(t); + return; + } + + downstream.onError(t); + // no need to loop because this onError is the last event + } + + @Override + public void onComplete() { + if (done) { + return; + } + synchronized (this) { + if (done) { + return; + } + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(QUEUE_LINK_SIZE); + queue = q; + } + q.add(NotificationLite.complete()); + return; + } + done = true; + emitting = true; + } + + downstream.onComplete(); + // no need to loop because this onComplete is the last event + } + + void emitLoop() { + for (;;) { + AppendOnlyLinkedArrayList q; + synchronized (this) { + q = queue; + if (q == null) { + emitting = false; + return; + } + queue = null; + } + + if (q.accept(downstream)) { + return; + } + } + } +} diff --git a/src/main/java/io/reactivex/observers/TestObserver.java b/src/main/java/io/reactivex/observers/TestObserver.java new file mode 100755 index 0000000..3909059 --- /dev/null +++ b/src/main/java/io/reactivex/observers/TestObserver.java @@ -0,0 +1,373 @@ +/** + * 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.observers; + +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.util.ExceptionHelper; + +/** + * An Observer that records events and allows making assertions about them. + * + *

You can override the onSubscribe, onNext, onError, onComplete, onSuccess and + * cancel methods but not the others (this is by design). + * + *

The TestObserver implements Disposable for convenience where dispose calls cancel. + * + * @param the value type + */ +public class TestObserver +extends BaseTestConsumer> +implements Observer, Disposable, MaybeObserver, SingleObserver, CompletableObserver { + /** The actual observer to forward events to. */ + private final Observer downstream; + + /** Holds the current subscription if any. */ + private final AtomicReference upstream = new AtomicReference(); + + private QueueDisposable qd; + + /** + * Constructs a non-forwarding TestObserver. + * @param the value type received + * @return the new TestObserver instance + */ + public static TestObserver create() { + return new TestObserver(); + } + + /** + * Constructs a forwarding TestObserver. + * @param the value type received + * @param delegate the actual Observer to forward events to + * @return the new TestObserver instance + */ + public static TestObserver create(Observer delegate) { + return new TestObserver(delegate); + } + + /** + * Constructs a non-forwarding TestObserver. + */ + public TestObserver() { + this(EmptyObserver.INSTANCE); + } + + /** + * Constructs a forwarding TestObserver. + * @param downstream the actual Observer to forward events to + */ + public TestObserver(Observer downstream) { + this.downstream = downstream; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Disposable d) { + lastThread = Thread.currentThread(); + + if (d == null) { + errors.add(new NullPointerException("onSubscribe received a null Subscription")); + return; + } + if (!upstream.compareAndSet(null, d)) { + d.dispose(); + if (upstream.get() != DisposableHelper.DISPOSED) { + errors.add(new IllegalStateException("onSubscribe received multiple subscriptions: " + d)); + } + return; + } + + if (initialFusionMode != 0) { + if (d instanceof QueueDisposable) { + qd = (QueueDisposable)d; + + int m = qd.requestFusion(initialFusionMode); + establishedFusionMode = m; + + if (m == QueueDisposable.SYNC) { + checkSubscriptionOnce = true; + lastThread = Thread.currentThread(); + try { + T t; + while ((t = qd.poll()) != null) { + values.add(t); + } + completions++; + + upstream.lazySet(DisposableHelper.DISPOSED); + } catch (Throwable ex) { + // Exceptions.throwIfFatal(e); TODO add fatal exceptions? + errors.add(ex); + } + return; + } + } + } + + downstream.onSubscribe(d); + } + + @Override + public void onNext(T t) { + if (!checkSubscriptionOnce) { + checkSubscriptionOnce = true; + if (upstream.get() == null) { + errors.add(new IllegalStateException("onSubscribe not called in proper order")); + } + } + + lastThread = Thread.currentThread(); + + if (establishedFusionMode == QueueDisposable.ASYNC) { + try { + while ((t = qd.poll()) != null) { + values.add(t); + } + } catch (Throwable ex) { + // Exceptions.throwIfFatal(e); TODO add fatal exceptions? + errors.add(ex); + qd.dispose(); + } + return; + } + + values.add(t); + + if (t == null) { + errors.add(new NullPointerException("onNext received a null value")); + } + + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (!checkSubscriptionOnce) { + checkSubscriptionOnce = true; + if (upstream.get() == null) { + errors.add(new IllegalStateException("onSubscribe not called in proper order")); + } + } + + try { + lastThread = Thread.currentThread(); + if (t == null) { + errors.add(new NullPointerException("onError received a null Throwable")); + } else { + errors.add(t); + } + + downstream.onError(t); + } finally { + done.countDown(); + } + } + + @Override + public void onComplete() { + if (!checkSubscriptionOnce) { + checkSubscriptionOnce = true; + if (upstream.get() == null) { + errors.add(new IllegalStateException("onSubscribe not called in proper order")); + } + } + + try { + lastThread = Thread.currentThread(); + completions++; + + downstream.onComplete(); + } finally { + done.countDown(); + } + } + + /** + * Returns true if this TestObserver has been cancelled. + * @return true if this TestObserver has been cancelled + */ + public final boolean isCancelled() { + return isDisposed(); + } + + /** + * Cancels the TestObserver (before or after the subscription happened). + *

This operation is thread-safe. + *

This method is provided as a convenience when converting Flowable tests that cancel. + */ + public final void cancel() { + dispose(); + } + + @Override + public final void dispose() { + DisposableHelper.dispose(upstream); + } + + @Override + public final boolean isDisposed() { + return DisposableHelper.isDisposed(upstream.get()); + } + + // state retrieval methods + /** + * Returns true if this TestObserver received a subscription. + * @return true if this TestObserver received a subscription + */ + public final boolean hasSubscription() { + return upstream.get() != null; + } + + /** + * Assert that the onSubscribe method was called exactly once. + * @return this; + */ + @Override + public final TestObserver assertSubscribed() { + if (upstream.get() == null) { + throw fail("Not subscribed!"); + } + return this; + } + + /** + * Assert that the onSubscribe method hasn't been called at all. + * @return this; + */ + @Override + public final TestObserver assertNotSubscribed() { + if (upstream.get() != null) { + throw fail("Subscribed!"); + } else + if (!errors.isEmpty()) { + throw fail("Not subscribed but errors found"); + } + return this; + } + + /** + * Run a check consumer with this TestObserver instance. + * @param check the check consumer to run + * @return this + */ + public final TestObserver assertOf(Consumer> check) { + try { + check.accept(this); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + return this; + } + + /** + * Sets the initial fusion mode if the upstream supports fusion. + *

Package-private: avoid leaking the now internal fusion properties into the public API. + * Use ObserverFusion to work with such tests. + * @param mode the mode to establish, see the {@link QueueDisposable} constants + * @return this + */ + final TestObserver setInitialFusionMode(int mode) { + this.initialFusionMode = mode; + return this; + } + + /** + * Asserts that the given fusion mode has been established + *

Package-private: avoid leaking the now internal fusion properties into the public API. + * Use ObserverFusion to work with such tests. + * @param mode the expected mode + * @return this + */ + final TestObserver assertFusionMode(int mode) { + int m = establishedFusionMode; + if (m != mode) { + if (qd != null) { + throw new AssertionError("Fusion mode different. Expected: " + fusionModeToString(mode) + + ", actual: " + fusionModeToString(m)); + } else { + throw fail("Upstream is not fuseable"); + } + } + return this; + } + + static String fusionModeToString(int mode) { + switch (mode) { + case QueueFuseable.NONE : return "NONE"; + case QueueFuseable.SYNC : return "SYNC"; + case QueueFuseable.ASYNC : return "ASYNC"; + default: return "Unknown(" + mode + ")"; + } + } + + /** + * Assert that the upstream is a fuseable source. + *

Package-private: avoid leaking the now internal fusion properties into the public API. + * Use ObserverFusion to work with such tests. + * @return this + */ + final TestObserver assertFuseable() { + if (qd == null) { + throw new AssertionError("Upstream is not fuseable."); + } + return this; + } + + /** + * Assert that the upstream is not a fuseable source. + *

Package-private: avoid leaking the now internal fusion properties into the public API. + * Use ObserverFusion to work with such tests. + * @return this + */ + final TestObserver assertNotFuseable() { + if (qd != null) { + throw new AssertionError("Upstream is fuseable."); + } + return this; + } + + @Override + public void onSuccess(T value) { + onNext(value); + onComplete(); + } + + /** + * An observer that ignores all events and does not report errors. + */ + enum EmptyObserver implements Observer { + INSTANCE; + + @Override + public void onSubscribe(Disposable d) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + } +} diff --git a/src/main/java/io/reactivex/observers/package-info.java b/src/main/java/io/reactivex/observers/package-info.java new file mode 100755 index 0000000..d12329a --- /dev/null +++ b/src/main/java/io/reactivex/observers/package-info.java @@ -0,0 +1,24 @@ +/** + * 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. + */ + +/** + * Default wrappers and implementations for Observer-based consumer classes and interfaces, + * including disposable and resource-tracking variants and + * the {@link io.reactivex.observers.TestObserver} that allows unit testing + * {@link io.reactivex.Observable}-, {@link io.reactivex.Single}-, {@link io.reactivex.Maybe}- + * and {@link io.reactivex.Completable}-based flows. + */ +package io.reactivex.observers; diff --git a/src/main/java/io/reactivex/package-info.java b/src/main/java/io/reactivex/package-info.java new file mode 100755 index 0000000..75ceb6c --- /dev/null +++ b/src/main/java/io/reactivex/package-info.java @@ -0,0 +1,54 @@ +/** + * 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. + */ +/** + * Base reactive classes: {@link io.reactivex.Flowable}, {@link io.reactivex.Observable}, + * {@link io.reactivex.Single}, {@link io.reactivex.Maybe} and + * {@link io.reactivex.Completable}; base reactive consumers; + * other common base interfaces. + * + *

A library that enables subscribing to and composing asynchronous events and + * callbacks.

+ *

The Flowable/Subscriber, Observable/Observer, Single/SingleObserver and + * Completable/CompletableObserver interfaces and associated operators (in + * the {@code io.reactivex.internal.operators} package) are inspired by the + * Reactive Rx library in Microsoft .NET but designed and implemented on + * the more advanced Reactive Streams ( http://www.reactivestreams.org ) principles.

+ *

+ * More information can be found at http://msdn.microsoft.com/en-us/data/gg577609. + *

+ * + * + *

Compared with the Microsoft implementation: + *

    + *
  • Observable == IObservable (base type)
  • + *
  • Observer == IObserver (event consumer)
  • + *
  • Disposable == IDisposable (resource/cancellation management)
  • + *
  • Observable == Observable (factory methods)
  • + *
  • Flowable == IAsyncEnumerable (backpressure)
  • + *
  • Subscriber == IAsyncEnumerator
  • + *
+ * The Single and Completable reactive base types have no equivalent in Rx.NET as of 3.x. + * + *

Services which intend on exposing data asynchronously and wish + * to allow reactive processing and composition can implement the + * {@link io.reactivex.Flowable}, {@link io.reactivex.Observable}, {@link io.reactivex.Single}, + * {@link io.reactivex.Maybe} or {@link io.reactivex.Completable} class which then allow + * consumers to subscribe to them and receive events.

+ *

Usage examples can be found on the {@link io.reactivex.Flowable}/{@link io.reactivex.Observable} and {@link org.reactivestreams.Subscriber} classes.

+ */ +package io.reactivex; + diff --git a/src/main/java/io/reactivex/parallel/ParallelFailureHandling.java b/src/main/java/io/reactivex/parallel/ParallelFailureHandling.java new file mode 100755 index 0000000..867c749 --- /dev/null +++ b/src/main/java/io/reactivex/parallel/ParallelFailureHandling.java @@ -0,0 +1,45 @@ +/** + * 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.parallel; + +import io.reactivex.functions.BiFunction; + +/** + * Enumerations for handling failure within a parallel operator. + *

History: 2.0.8 - experimental + * @since 2.2 + */ +public enum ParallelFailureHandling implements BiFunction { + /** + * The current rail is stopped and the error is dropped. + */ + STOP, + /** + * The current rail is stopped and the error is signalled. + */ + ERROR, + /** + * The current value and error is ignored and the rail resumes with the next item. + */ + SKIP, + /** + * Retry the current value. + */ + RETRY; + + @Override + public ParallelFailureHandling apply(Long t1, Throwable t2) { + return this; + } +} diff --git a/src/main/java/io/reactivex/parallel/ParallelFlowable.java b/src/main/java/io/reactivex/parallel/ParallelFlowable.java new file mode 100755 index 0000000..13ebb02 --- /dev/null +++ b/src/main/java/io/reactivex/parallel/ParallelFlowable.java @@ -0,0 +1,947 @@ +/** + * 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.parallel; + +import java.util.*; +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.annotations.*; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.*; +import io.reactivex.internal.operators.parallel.*; +import io.reactivex.internal.subscriptions.EmptySubscription; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; +import org.reactivestreams.*; + +/** + * Abstract base class for Parallel publishers that take an array of Subscribers. + *

+ * Use {@code from()} to start processing a regular Publisher in 'rails'. + * Use {@code runOn()} to introduce where each 'rail' should run on thread-vise. + * Use {@code sequential()} to merge the sources back into a single Flowable. + * + *

History: 2.0.5 - experimental; 2.1 - beta + * @param the value type + * @since 2.2 + */ +public abstract class ParallelFlowable { + + /** + * Subscribes an array of Subscribers to this ParallelFlowable and triggers + * the execution chain for all 'rails'. + * + * @param subscribers the subscribers array to run in parallel, the number + * of items must be equal to the parallelism level of this ParallelFlowable + * @see #parallelism() + */ + public abstract void subscribe(@NonNull Subscriber[] subscribers); + + /** + * Returns the number of expected parallel Subscribers. + * @return the number of expected parallel Subscribers + */ + public abstract int parallelism(); + + /** + * Validates the number of subscribers and returns true if their number + * matches the parallelism level of this ParallelFlowable. + * + * @param subscribers the array of Subscribers + * @return true if the number of subscribers equals to the parallelism level + */ + protected final boolean validate(@NonNull Subscriber[] subscribers) { + int p = parallelism(); + if (subscribers.length != p) { + Throwable iae = new IllegalArgumentException("parallelism = " + p + ", subscribers = " + subscribers.length); + for (Subscriber s : subscribers) { + EmptySubscription.error(iae, s); + } + return false; + } + return true; + } + + /** + * Take a Publisher and prepare to consume it on multiple 'rails' (number of CPUs) + * in a round-robin fashion. + * @param the value type + * @param source the source Publisher + * @return the ParallelFlowable instance + */ + @CheckReturnValue + public static ParallelFlowable from(@NonNull Publisher source) { + return from(source, Runtime.getRuntime().availableProcessors(), Flowable.bufferSize()); + } + + /** + * Take a Publisher and prepare to consume it on parallelism number of 'rails' in a round-robin fashion. + * @param the value type + * @param source the source Publisher + * @param parallelism the number of parallel rails + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + public static ParallelFlowable from(@NonNull Publisher source, int parallelism) { + return from(source, parallelism, Flowable.bufferSize()); + } + + /** + * Take a Publisher and prepare to consume it on parallelism number of 'rails' , + * possibly ordered and round-robin fashion and use custom prefetch amount and queue + * for dealing with the source Publisher's values. + * @param the value type + * @param source the source Publisher + * @param parallelism the number of parallel rails + * @param prefetch the number of values to prefetch from the source + * the source until there is a rail ready to process it. + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public static ParallelFlowable from(@NonNull Publisher source, + int parallelism, int prefetch) { + ObjectHelper.requireNonNull(source, "source"); + ObjectHelper.verifyPositive(parallelism, "parallelism"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + + return RxJavaPlugins.onAssembly(new ParallelFromPublisher(source, parallelism, prefetch)); + } + + /** + * Calls the specified converter function during assembly time and returns its resulting value. + *

+ * This allows fluent conversion to any other type. + *

History: 2.1.7 - experimental + * @param the resulting object type + * @param converter the function that receives the current ParallelFlowable instance and returns a value + * @return the converted value + * @throws NullPointerException if converter is null + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public final R as(@NonNull ParallelFlowableConverter converter) { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } + + /** + * Maps the source values on each 'rail' to another value. + *

+ * Note that the same mapper function may be called from multiple threads concurrently. + * @param the output value type + * @param mapper the mapper function turning Ts into Us. + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable map(@NonNull Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper"); + return RxJavaPlugins.onAssembly(new ParallelMap(this, mapper)); + } + + /** + * Maps the source values on each 'rail' to another value and + * handles errors based on the given {@link ParallelFailureHandling} enumeration value. + *

+ * Note that the same mapper function may be called from multiple threads concurrently. + *

History: 2.0.8 - experimental + * @param the output value type + * @param mapper the mapper function turning Ts into Us. + * @param errorHandler the enumeration that defines how to handle errors thrown + * from the mapper function + * @return the new ParallelFlowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable map(@NonNull Function mapper, @NonNull ParallelFailureHandling errorHandler) { + ObjectHelper.requireNonNull(mapper, "mapper"); + ObjectHelper.requireNonNull(errorHandler, "errorHandler is null"); + return RxJavaPlugins.onAssembly(new ParallelMapTry(this, mapper, errorHandler)); + } + + /** + * Maps the source values on each 'rail' to another value and + * handles errors based on the returned value by the handler function. + *

+ * Note that the same mapper function may be called from multiple threads concurrently. + *

History: 2.0.8 - experimental + * @param the output value type + * @param mapper the mapper function turning Ts into Us. + * @param errorHandler the function called with the current repeat count and + * failure Throwable and should return one of the {@link ParallelFailureHandling} + * enumeration values to indicate how to proceed. + * @return the new ParallelFlowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable map(@NonNull Function mapper, @NonNull BiFunction errorHandler) { + ObjectHelper.requireNonNull(mapper, "mapper"); + ObjectHelper.requireNonNull(errorHandler, "errorHandler is null"); + return RxJavaPlugins.onAssembly(new ParallelMapTry(this, mapper, errorHandler)); + } + + /** + * Filters the source values on each 'rail'. + *

+ * Note that the same predicate may be called from multiple threads concurrently. + * @param predicate the function returning true to keep a value or false to drop a value + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + public final ParallelFlowable filter(@NonNull Predicate predicate) { + ObjectHelper.requireNonNull(predicate, "predicate"); + return RxJavaPlugins.onAssembly(new ParallelFilter(this, predicate)); + } + + /** + * Filters the source values on each 'rail' and + * handles errors based on the given {@link ParallelFailureHandling} enumeration value. + *

+ * Note that the same predicate may be called from multiple threads concurrently. + *

History: 2.0.8 - experimental + * @param predicate the function returning true to keep a value or false to drop a value + * @param errorHandler the enumeration that defines how to handle errors thrown + * from the predicate + * @return the new ParallelFlowable instance + * @since 2.2 + */ + @CheckReturnValue + public final ParallelFlowable filter(@NonNull Predicate predicate, @NonNull ParallelFailureHandling errorHandler) { + ObjectHelper.requireNonNull(predicate, "predicate"); + ObjectHelper.requireNonNull(errorHandler, "errorHandler is null"); + return RxJavaPlugins.onAssembly(new ParallelFilterTry(this, predicate, errorHandler)); + } + + /** + * Filters the source values on each 'rail' and + * handles errors based on the returned value by the handler function. + *

+ * Note that the same predicate may be called from multiple threads concurrently. + *

History: 2.0.8 - experimental + * @param predicate the function returning true to keep a value or false to drop a value + * @param errorHandler the function called with the current repeat count and + * failure Throwable and should return one of the {@link ParallelFailureHandling} + * enumeration values to indicate how to proceed. + * @return the new ParallelFlowable instance + * @since 2.2 + */ + @CheckReturnValue + public final ParallelFlowable filter(@NonNull Predicate predicate, @NonNull BiFunction errorHandler) { + ObjectHelper.requireNonNull(predicate, "predicate"); + ObjectHelper.requireNonNull(errorHandler, "errorHandler is null"); + return RxJavaPlugins.onAssembly(new ParallelFilterTry(this, predicate, errorHandler)); + } + + /** + * Specifies where each 'rail' will observe its incoming values with + * no work-stealing and default prefetch amount. + *

+ * This operator uses the default prefetch size returned by {@code Flowable.bufferSize()}. + *

+ * The operator will call {@code Scheduler.createWorker()} as many + * times as this ParallelFlowable's parallelism level is. + *

+ * No assumptions are made about the Scheduler's parallelism level, + * if the Scheduler's parallelism level is lower than the ParallelFlowable's, + * some rails may end up on the same thread/worker. + *

+ * This operator doesn't require the Scheduler to be trampolining as it + * does its own built-in trampolining logic. + * + * @param scheduler the scheduler to use + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable runOn(@NonNull Scheduler scheduler) { + return runOn(scheduler, Flowable.bufferSize()); + } + + /** + * Specifies where each 'rail' will observe its incoming values with + * possibly work-stealing and a given prefetch amount. + *

+ * This operator uses the default prefetch size returned by {@code Flowable.bufferSize()}. + *

+ * The operator will call {@code Scheduler.createWorker()} as many + * times as this ParallelFlowable's parallelism level is. + *

+ * No assumptions are made about the Scheduler's parallelism level, + * if the Scheduler's parallelism level is lower than the ParallelFlowable's, + * some rails may end up on the same thread/worker. + *

+ * This operator doesn't require the Scheduler to be trampolining as it + * does its own built-in trampolining logic. + * + * @param scheduler the scheduler to use + * that rail's worker has run out of work. + * @param prefetch the number of values to request on each 'rail' from the source + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable runOn(@NonNull Scheduler scheduler, int prefetch) { + ObjectHelper.requireNonNull(scheduler, "scheduler"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ParallelRunOn(this, scheduler, prefetch)); + } + + /** + * Reduces all values within a 'rail' and across 'rails' with a reducer function into a single + * sequential value. + *

+ * Note that the same reducer function may be called from multiple threads concurrently. + * @param reducer the function to reduce two values into one. + * @return the new Flowable instance emitting the reduced value or empty if the ParallelFlowable was empty + */ + @CheckReturnValue + @NonNull + public final Flowable reduce(@NonNull BiFunction reducer) { + ObjectHelper.requireNonNull(reducer, "reducer"); + return RxJavaPlugins.onAssembly(new ParallelReduceFull(this, reducer)); + } + + /** + * Reduces all values within a 'rail' to a single value (with a possibly different type) via + * a reducer function that is initialized on each rail from an initialSupplier value. + *

+ * Note that the same mapper function may be called from multiple threads concurrently. + * @param the reduced output type + * @param initialSupplier the supplier for the initial value + * @param reducer the function to reduce a previous output of reduce (or the initial value supplied) + * with a current source value. + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable reduce(@NonNull Callable initialSupplier, @NonNull BiFunction reducer) { + ObjectHelper.requireNonNull(initialSupplier, "initialSupplier"); + ObjectHelper.requireNonNull(reducer, "reducer"); + return RxJavaPlugins.onAssembly(new ParallelReduce(this, initialSupplier, reducer)); + } + + /** + * Merges the values from each 'rail' in a round-robin or same-order fashion and + * exposes it as a regular Publisher sequence, running with a default prefetch value + * for the rails. + *

+ * This operator uses the default prefetch size returned by {@code Flowable.bufferSize()}. + * + *

+ *
Backpressure:
+ *
The operator honors backpressure.
+ *
Scheduler:
+ *
{@code sequential} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new Flowable instance + * @see ParallelFlowable#sequential(int) + * @see ParallelFlowable#sequentialDelayError() + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + public final Flowable sequential() { + return sequential(Flowable.bufferSize()); + } + + /** + * Merges the values from each 'rail' in a round-robin or same-order fashion and + * exposes it as a regular Publisher sequence, running with a give prefetch value + * for the rails. + * + *
+ *
Backpressure:
+ *
The operator honors backpressure.
+ *
Scheduler:
+ *
{@code sequential} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param prefetch the prefetch amount to use for each rail + * @return the new Flowable instance + * @see ParallelFlowable#sequential() + * @see ParallelFlowable#sequentialDelayError(int) + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @NonNull + public final Flowable sequential(int prefetch) { + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ParallelJoin(this, prefetch, false)); + } + + /** + * Merges the values from each 'rail' in a round-robin or same-order fashion and + * exposes it as a regular Flowable sequence, running with a default prefetch value + * for the rails and delaying errors from all rails till all terminate. + *

+ * This operator uses the default prefetch size returned by {@code Flowable.bufferSize()}. + * + *

+ *
Backpressure:
+ *
The operator honors backpressure.
+ *
Scheduler:
+ *
{@code sequentialDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.7 - experimental + * @return the new Flowable instance + * @see ParallelFlowable#sequentialDelayError(int) + * @see ParallelFlowable#sequential() + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @NonNull + public final Flowable sequentialDelayError() { + return sequentialDelayError(Flowable.bufferSize()); + } + + /** + * Merges the values from each 'rail' in a round-robin or same-order fashion and + * exposes it as a regular Publisher sequence, running with a give prefetch value + * for the rails and delaying errors from all rails till all terminate. + * + *

+ *
Backpressure:
+ *
The operator honors backpressure.
+ *
Scheduler:
+ *
{@code sequentialDelayError} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 2.0.7 - experimental + * @param prefetch the prefetch amount to use for each rail + * @return the new Flowable instance + * @see ParallelFlowable#sequential() + * @see ParallelFlowable#sequentialDelayError() + * @since 2.2 + */ + @BackpressureSupport(BackpressureKind.FULL) + @SchedulerSupport(SchedulerSupport.NONE) + @CheckReturnValue + @NonNull + public final Flowable sequentialDelayError(int prefetch) { + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ParallelJoin(this, prefetch, true)); + } + + /** + * Sorts the 'rails' of this ParallelFlowable and returns a Publisher that sequentially + * picks the smallest next value from the rails. + *

+ * This operator requires a finite source ParallelFlowable. + * + * @param comparator the comparator to use + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + public final Flowable sorted(@NonNull Comparator comparator) { + return sorted(comparator, 16); + } + + /** + * Sorts the 'rails' of this ParallelFlowable and returns a Publisher that sequentially + * picks the smallest next value from the rails. + *

+ * This operator requires a finite source ParallelFlowable. + * + * @param comparator the comparator to use + * @param capacityHint the expected number of total elements + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + public final Flowable sorted(@NonNull Comparator comparator, int capacityHint) { + ObjectHelper.requireNonNull(comparator, "comparator is null"); + ObjectHelper.verifyPositive(capacityHint, "capacityHint"); + int ch = capacityHint / parallelism() + 1; + ParallelFlowable> railReduced = reduce(Functions.createArrayList(ch), ListAddBiConsumer.instance()); + ParallelFlowable> railSorted = railReduced.map(new SorterFunction(comparator)); + + return RxJavaPlugins.onAssembly(new ParallelSortedJoin(railSorted, comparator)); + } + + /** + * Sorts the 'rails' according to the comparator and returns a full sorted list as a Publisher. + *

+ * This operator requires a finite source ParallelFlowable. + * + * @param comparator the comparator to compare elements + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + public final Flowable> toSortedList(@NonNull Comparator comparator) { + return toSortedList(comparator, 16); + } + /** + * Sorts the 'rails' according to the comparator and returns a full sorted list as a Publisher. + *

+ * This operator requires a finite source ParallelFlowable. + * + * @param comparator the comparator to compare elements + * @param capacityHint the expected number of total elements + * @return the new Flowable instance + */ + @CheckReturnValue + @NonNull + public final Flowable> toSortedList(@NonNull Comparator comparator, int capacityHint) { + ObjectHelper.requireNonNull(comparator, "comparator is null"); + ObjectHelper.verifyPositive(capacityHint, "capacityHint"); + + int ch = capacityHint / parallelism() + 1; + ParallelFlowable> railReduced = reduce(Functions.createArrayList(ch), ListAddBiConsumer.instance()); + ParallelFlowable> railSorted = railReduced.map(new SorterFunction(comparator)); + + Flowable> merged = railSorted.reduce(new MergerBiFunction(comparator)); + + return RxJavaPlugins.onAssembly(merged); + } + + /** + * Call the specified consumer with the current element passing through any 'rail'. + * + * @param onNext the callback + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doOnNext(@NonNull Consumer onNext) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + return RxJavaPlugins.onAssembly(new ParallelPeek(this, + onNext, + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, + Functions.emptyConsumer(), + Functions.EMPTY_LONG_CONSUMER, + Functions.EMPTY_ACTION + )); + } + + /** + * Call the specified consumer with the current element passing through any 'rail' and + * handles errors based on the given {@link ParallelFailureHandling} enumeration value. + *

History: 2.0.8 - experimental + * @param onNext the callback + * @param errorHandler the enumeration that defines how to handle errors thrown + * from the onNext consumer + * @return the new ParallelFlowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doOnNext(@NonNull Consumer onNext, @NonNull ParallelFailureHandling errorHandler) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(errorHandler, "errorHandler is null"); + return RxJavaPlugins.onAssembly(new ParallelDoOnNextTry(this, onNext, errorHandler)); + } + + /** + * Call the specified consumer with the current element passing through any 'rail' and + * handles errors based on the returned value by the handler function. + *

History: 2.0.8 - experimental + * @param onNext the callback + * @param errorHandler the function called with the current repeat count and + * failure Throwable and should return one of the {@link ParallelFailureHandling} + * enumeration values to indicate how to proceed. + * @return the new ParallelFlowable instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doOnNext(@NonNull Consumer onNext, @NonNull BiFunction errorHandler) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(errorHandler, "errorHandler is null"); + return RxJavaPlugins.onAssembly(new ParallelDoOnNextTry(this, onNext, errorHandler)); + } + + /** + * Call the specified consumer with the current element passing through any 'rail' + * after it has been delivered to downstream within the rail. + * + * @param onAfterNext the callback + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doAfterNext(@NonNull Consumer onAfterNext) { + ObjectHelper.requireNonNull(onAfterNext, "onAfterNext is null"); + return RxJavaPlugins.onAssembly(new ParallelPeek(this, + Functions.emptyConsumer(), + onAfterNext, + Functions.emptyConsumer(), + Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, + Functions.emptyConsumer(), + Functions.EMPTY_LONG_CONSUMER, + Functions.EMPTY_ACTION + )); + } + + /** + * Call the specified consumer with the exception passing through any 'rail'. + * + * @param onError the callback + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doOnError(@NonNull Consumer onError) { + ObjectHelper.requireNonNull(onError, "onError is null"); + return RxJavaPlugins.onAssembly(new ParallelPeek(this, + Functions.emptyConsumer(), + Functions.emptyConsumer(), + onError, + Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, + Functions.emptyConsumer(), + Functions.EMPTY_LONG_CONSUMER, + Functions.EMPTY_ACTION + )); + } + + /** + * Run the specified Action when a 'rail' completes. + * + * @param onComplete the callback + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doOnComplete(@NonNull Action onComplete) { + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + return RxJavaPlugins.onAssembly(new ParallelPeek(this, + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.emptyConsumer(), + onComplete, + Functions.EMPTY_ACTION, + Functions.emptyConsumer(), + Functions.EMPTY_LONG_CONSUMER, + Functions.EMPTY_ACTION + )); + } + + /** + * Run the specified Action when a 'rail' completes or signals an error. + * + * @param onAfterTerminate the callback + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doAfterTerminated(@NonNull Action onAfterTerminate) { + ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"); + return RxJavaPlugins.onAssembly(new ParallelPeek(this, + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.EMPTY_ACTION, + onAfterTerminate, + Functions.emptyConsumer(), + Functions.EMPTY_LONG_CONSUMER, + Functions.EMPTY_ACTION + )); + } + + /** + * Call the specified callback when a 'rail' receives a Subscription from its upstream. + * + * @param onSubscribe the callback + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doOnSubscribe(@NonNull Consumer onSubscribe) { + ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); + return RxJavaPlugins.onAssembly(new ParallelPeek(this, + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, + onSubscribe, + Functions.EMPTY_LONG_CONSUMER, + Functions.EMPTY_ACTION + )); + } + + /** + * Call the specified consumer with the request amount if any rail receives a request. + * + * @param onRequest the callback + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doOnRequest(@NonNull LongConsumer onRequest) { + ObjectHelper.requireNonNull(onRequest, "onRequest is null"); + return RxJavaPlugins.onAssembly(new ParallelPeek(this, + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, + Functions.emptyConsumer(), + onRequest, + Functions.EMPTY_ACTION + )); + } + + /** + * Run the specified Action when a 'rail' receives a cancellation. + * + * @param onCancel the callback + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable doOnCancel(@NonNull Action onCancel) { + ObjectHelper.requireNonNull(onCancel, "onCancel is null"); + return RxJavaPlugins.onAssembly(new ParallelPeek(this, + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.emptyConsumer(), + Functions.EMPTY_ACTION, + Functions.EMPTY_ACTION, + Functions.emptyConsumer(), + Functions.EMPTY_LONG_CONSUMER, + onCancel + )); + } + + /** + * Collect the elements in each rail into a collection supplied via a collectionSupplier + * and collected into with a collector action, emitting the collection at the end. + * + * @param the collection type + * @param collectionSupplier the supplier of the collection in each rail + * @param collector the collector, taking the per-rail collection and the current item + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable collect(@NonNull Callable collectionSupplier, @NonNull BiConsumer collector) { + ObjectHelper.requireNonNull(collectionSupplier, "collectionSupplier is null"); + ObjectHelper.requireNonNull(collector, "collector is null"); + return RxJavaPlugins.onAssembly(new ParallelCollect(this, collectionSupplier, collector)); + } + + /** + * Wraps multiple Publishers into a ParallelFlowable which runs them + * in parallel and unordered. + * + * @param the value type + * @param publishers the array of publishers + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public static ParallelFlowable fromArray(@NonNull Publisher... publishers) { + if (publishers.length == 0) { + throw new IllegalArgumentException("Zero publishers not supported"); + } + return RxJavaPlugins.onAssembly(new ParallelFromArray(publishers)); + } + + /** + * Perform a fluent transformation to a value via a converter function which + * receives this ParallelFlowable. + * + * @param the output value type + * @param converter the converter function from ParallelFlowable to some type + * @return the value returned by the converter function + */ + @CheckReturnValue + @NonNull + public final U to(@NonNull Function, U> converter) { + try { + return ObjectHelper.requireNonNull(converter, "converter is null").apply(this); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Allows composing operators, in assembly time, on top of this ParallelFlowable + * and returns another ParallelFlowable with composed features. + * + * @param the output value type + * @param composer the composer function from ParallelFlowable (this) to another ParallelFlowable + * @return the ParallelFlowable returned by the function + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable compose(@NonNull ParallelTransformer composer) { + return RxJavaPlugins.onAssembly(ObjectHelper.requireNonNull(composer, "composer is null").apply(this)); + } + + /** + * Generates and flattens Publishers on each 'rail'. + *

+ * Errors are not delayed and uses unbounded concurrency along with default inner prefetch. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable flatMap(@NonNull Function> mapper) { + return flatMap(mapper, false, Integer.MAX_VALUE, Flowable.bufferSize()); + } + + /** + * Generates and flattens Publishers on each 'rail', optionally delaying errors. + *

+ * It uses unbounded concurrency along with default inner prefetch. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param delayError should the errors from the main and the inner sources delayed till everybody terminates? + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable flatMap( + @NonNull Function> mapper, boolean delayError) { + return flatMap(mapper, delayError, Integer.MAX_VALUE, Flowable.bufferSize()); + } + + /** + * Generates and flattens Publishers on each 'rail', optionally delaying errors + * and having a total number of simultaneous subscriptions to the inner Publishers. + *

+ * It uses a default inner prefetch. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param delayError should the errors from the main and the inner sources delayed till everybody terminates? + * @param maxConcurrency the maximum number of simultaneous subscriptions to the generated inner Publishers + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable flatMap( + @NonNull Function> mapper, boolean delayError, int maxConcurrency) { + return flatMap(mapper, delayError, maxConcurrency, Flowable.bufferSize()); + } + + /** + * Generates and flattens Publishers on each 'rail', optionally delaying errors, + * having a total number of simultaneous subscriptions to the inner Publishers + * and using the given prefetch amount for the inner Publishers. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param delayError should the errors from the main and the inner sources delayed till everybody terminates? + * @param maxConcurrency the maximum number of simultaneous subscriptions to the generated inner Publishers + * @param prefetch the number of items to prefetch from each inner Publisher + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable flatMap( + @NonNull Function> mapper, + boolean delayError, int maxConcurrency, int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(maxConcurrency, "maxConcurrency"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ParallelFlatMap(this, mapper, delayError, maxConcurrency, prefetch)); + } + + /** + * Generates and concatenates Publishers on each 'rail', signalling errors immediately + * and generating 2 publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * source and the inner Publishers (immediate, boundary, end) + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable concatMap( + @NonNull Function> mapper) { + return concatMap(mapper, 2); + } + + /** + * Generates and concatenates Publishers on each 'rail', signalling errors immediately + * and using the given prefetch amount for generating Publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param prefetch the number of items to prefetch from each inner Publisher + * source and the inner Publishers (immediate, boundary, end) + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable concatMap( + @NonNull Function> mapper, + int prefetch) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ParallelConcatMap(this, mapper, prefetch, ErrorMode.IMMEDIATE)); + } + + /** + * Generates and concatenates Publishers on each 'rail', optionally delaying errors + * and generating 2 publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param tillTheEnd if true all errors from the upstream and inner Publishers are delayed + * till all of them terminate, if false, the error is emitted when an inner Publisher terminates. + * source and the inner Publishers (immediate, boundary, end) + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable concatMapDelayError( + @NonNull Function> mapper, + boolean tillTheEnd) { + return concatMapDelayError(mapper, 2, tillTheEnd); + } + + /** + * Generates and concatenates Publishers on each 'rail', optionally delaying errors + * and using the given prefetch amount for generating Publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param prefetch the number of items to prefetch from each inner Publisher + * @param tillTheEnd if true all errors from the upstream and inner Publishers are delayed + * till all of them terminate, if false, the error is emitted when an inner Publisher terminates. + * @return the new ParallelFlowable instance + */ + @CheckReturnValue + @NonNull + public final ParallelFlowable concatMapDelayError( + @NonNull Function> mapper, + int prefetch, boolean tillTheEnd) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + ObjectHelper.verifyPositive(prefetch, "prefetch"); + return RxJavaPlugins.onAssembly(new ParallelConcatMap( + this, mapper, prefetch, tillTheEnd ? ErrorMode.END : ErrorMode.BOUNDARY)); + } +} diff --git a/src/main/java/io/reactivex/parallel/ParallelFlowableConverter.java b/src/main/java/io/reactivex/parallel/ParallelFlowableConverter.java new file mode 100755 index 0000000..9d1b287 --- /dev/null +++ b/src/main/java/io/reactivex/parallel/ParallelFlowableConverter.java @@ -0,0 +1,35 @@ +/** + * 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.parallel; + +import io.reactivex.annotations.*; + +/** + * Convenience interface and callback used by the {@link ParallelFlowable#as} operator to turn a ParallelFlowable into + * another value fluently. + *

History: 2.1.7 - experimental + * @param the upstream type + * @param the output type + * @since 2.2 + */ +public interface ParallelFlowableConverter { + /** + * Applies a function to the upstream ParallelFlowable and returns a converted value of type {@code R}. + * + * @param upstream the upstream ParallelFlowable instance + * @return the converted value + */ + @NonNull + R apply(@NonNull ParallelFlowable upstream); +} diff --git a/src/main/java/io/reactivex/parallel/ParallelTransformer.java b/src/main/java/io/reactivex/parallel/ParallelTransformer.java new file mode 100755 index 0000000..9981934 --- /dev/null +++ b/src/main/java/io/reactivex/parallel/ParallelTransformer.java @@ -0,0 +1,34 @@ +/** + * 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.parallel; + +import io.reactivex.annotations.*; + +/** + * Interface to compose ParallelFlowable. + *

History: 2.0.8 - experimental + * @param the upstream value type + * @param the downstream value type + * @since 2.2 + */ +public interface ParallelTransformer { + /** + * Applies a function to the upstream ParallelFlowable and returns a ParallelFlowable with + * optionally different element type. + * @param upstream the upstream ParallelFlowable instance + * @return the transformed ParallelFlowable instance + */ + @NonNull + ParallelFlowable apply(@NonNull ParallelFlowable upstream); +} \ No newline at end of file diff --git a/src/main/java/io/reactivex/parallel/package-info.java b/src/main/java/io/reactivex/parallel/package-info.java new file mode 100755 index 0000000..e7ee257 --- /dev/null +++ b/src/main/java/io/reactivex/parallel/package-info.java @@ -0,0 +1,21 @@ +/** + * 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. + */ + +/** + * Contains the base type {@link io.reactivex.parallel.ParallelFlowable}, + * a sub-DSL for working with {@link io.reactivex.Flowable} sequences in parallel. + */ +package io.reactivex.parallel; \ No newline at end of file diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java new file mode 100755 index 0000000..71f402f --- /dev/null +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -0,0 +1,1327 @@ +/** + * 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.plugins; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.*; + +import org.reactivestreams.Subscriber; + +import io.reactivex.*; +import io.reactivex.annotations.*; +import io.reactivex.exceptions.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.schedulers.*; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.schedulers.Schedulers; +/** + * Utility class to inject handlers to certain standard RxJava operations. + */ +public final class RxJavaPlugins { + @Nullable + static volatile Consumer errorHandler; + + @Nullable + static volatile Function onScheduleHandler; + + @Nullable + static volatile Function, ? extends Scheduler> onInitComputationHandler; + + @Nullable + static volatile Function, ? extends Scheduler> onInitSingleHandler; + + @Nullable + static volatile Function, ? extends Scheduler> onInitIoHandler; + + @Nullable + static volatile Function, ? extends Scheduler> onInitNewThreadHandler; + + @Nullable + static volatile Function onComputationHandler; + + @Nullable + static volatile Function onSingleHandler; + + @Nullable + static volatile Function onIoHandler; + + @Nullable + static volatile Function onNewThreadHandler; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile Function onFlowableAssembly; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile Function onConnectableFlowableAssembly; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile Function onObservableAssembly; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile Function onConnectableObservableAssembly; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile Function onMaybeAssembly; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile Function onSingleAssembly; + + @Nullable + static volatile Function onCompletableAssembly; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile Function onParallelAssembly; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile BiFunction onFlowableSubscribe; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile BiFunction onMaybeSubscribe; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile BiFunction onObservableSubscribe; + + @SuppressWarnings("rawtypes") + @Nullable + static volatile BiFunction onSingleSubscribe; + + @Nullable + static volatile BiFunction onCompletableSubscribe; + + @Nullable + static volatile BooleanSupplier onBeforeBlocking; + + /** Prevents changing the plugins. */ + static volatile boolean lockdown; + + /** + * If true, attempting to run a blockingX operation on a (by default) + * computation or single scheduler will throw an IllegalStateException. + */ + static volatile boolean failNonBlockingScheduler; + + /** + * Prevents changing the plugins from then on. + *

This allows container-like environments to prevent clients + * messing with plugins. + */ + public static void lockdown() { + lockdown = true; + } + + /** + * Returns true if the plugins were locked down. + * @return true if the plugins were locked down + */ + public static boolean isLockdown() { + return lockdown; + } + + /** + * Enables or disables the blockingX operators to fail + * with an IllegalStateException on a non-blocking + * scheduler such as computation or single. + *

History: 2.0.5 - experimental + * @param enable enable or disable the feature + * @since 2.1 + */ + public static void setFailOnNonBlockingScheduler(boolean enable) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + failNonBlockingScheduler = enable; + } + + /** + * Returns true if the blockingX operators fail + * with an IllegalStateException on a non-blocking scheduler + * such as computation or single. + *

History: 2.0.5 - experimental + * @return true if the blockingX operators fail on a non-blocking scheduler + * @since 2.1 + */ + public static boolean isFailOnNonBlockingScheduler() { + return failNonBlockingScheduler; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function getComputationSchedulerHandler() { + return onComputationHandler; + } + + /** + * Returns the a hook consumer. + * @return the hook consumer, may be null + */ + @Nullable + public static Consumer getErrorHandler() { + return errorHandler; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function, ? extends Scheduler> getInitComputationSchedulerHandler() { + return onInitComputationHandler; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function, ? extends Scheduler> getInitIoSchedulerHandler() { + return onInitIoHandler; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function, ? extends Scheduler> getInitNewThreadSchedulerHandler() { + return onInitNewThreadHandler; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function, ? extends Scheduler> getInitSingleSchedulerHandler() { + return onInitSingleHandler; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function getIoSchedulerHandler() { + return onIoHandler; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function getNewThreadSchedulerHandler() { + return onNewThreadHandler; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function getScheduleHandler() { + return onScheduleHandler; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function getSingleSchedulerHandler() { + return onSingleHandler; + } + + /** + * Calls the associated hook function. + * @param defaultScheduler a {@link Callable} which returns the hook's input value + * @return the value returned by the hook, not null + * @throws NullPointerException if the callable parameter or its result are null + */ + @NonNull + public static Scheduler initComputationScheduler(@NonNull Callable defaultScheduler) { + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); + Function, ? extends Scheduler> f = onInitComputationHandler; + if (f == null) { + return callRequireNonNull(defaultScheduler); + } + return applyRequireNonNull(f, defaultScheduler); // JIT will skip this + } + + /** + * Calls the associated hook function. + * @param defaultScheduler a {@link Callable} which returns the hook's input value + * @return the value returned by the hook, not null + * @throws NullPointerException if the callable parameter or its result are null + */ + @NonNull + public static Scheduler initIoScheduler(@NonNull Callable defaultScheduler) { + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); + Function, ? extends Scheduler> f = onInitIoHandler; + if (f == null) { + return callRequireNonNull(defaultScheduler); + } + return applyRequireNonNull(f, defaultScheduler); + } + + /** + * Calls the associated hook function. + * @param defaultScheduler a {@link Callable} which returns the hook's input value + * @return the value returned by the hook, not null + * @throws NullPointerException if the callable parameter or its result are null + */ + @NonNull + public static Scheduler initNewThreadScheduler(@NonNull Callable defaultScheduler) { + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); + Function, ? extends Scheduler> f = onInitNewThreadHandler; + if (f == null) { + return callRequireNonNull(defaultScheduler); + } + return applyRequireNonNull(f, defaultScheduler); + } + + /** + * Calls the associated hook function. + * @param defaultScheduler a {@link Callable} which returns the hook's input value + * @return the value returned by the hook, not null + * @throws NullPointerException if the callable parameter or its result are null + */ + @NonNull + public static Scheduler initSingleScheduler(@NonNull Callable defaultScheduler) { + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); + Function, ? extends Scheduler> f = onInitSingleHandler; + if (f == null) { + return callRequireNonNull(defaultScheduler); + } + return applyRequireNonNull(f, defaultScheduler); + } + + /** + * Calls the associated hook function. + * @param defaultScheduler the hook's input value + * @return the value returned by the hook + */ + @NonNull + public static Scheduler onComputationScheduler(@NonNull Scheduler defaultScheduler) { + Function f = onComputationHandler; + if (f == null) { + return defaultScheduler; + } + return apply(f, defaultScheduler); + } + + /** + * Called when an undeliverable error occurs. + *

+ * Undeliverable errors are those {@code Observer.onError()} invocations that are not allowed to happen on + * the given consumer type ({@code Observer}, {@code Subscriber}, etc.) due to protocol restrictions + * because the consumer has either disposed/cancelled its {@code Disposable}/{@code Subscription} or + * has already terminated with an {@code onError()} or {@code onComplete()} signal. + *

+ * By default, this global error handler prints the stacktrace via {@link Throwable#printStackTrace()} + * and calls {@link UncaughtExceptionHandler#uncaughtException(Thread, Throwable)} + * on the current thread. + *

+ * Note that on some platforms, the platform runtime terminates the current application with an error if such + * uncaught exceptions happen. In this case, it is recommended the application installs a global error + * handler via the {@link #setErrorHandler(Consumer)} plugin method. + * + * @param error the error to report + * @see #getErrorHandler() + * @see #setErrorHandler(Consumer) + * @see Error handling Wiki + */ + public static void onError(@NonNull Throwable error) { + Consumer f = errorHandler; + + if (error == null) { + error = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } else { + if (!isBug(error)) { + error = new UndeliverableException(error); + } + } + + if (f != null) { + try { + f.accept(error); + return; + } catch (Throwable e) { + // Exceptions.throwIfFatal(e); TODO decide + e.printStackTrace(); // NOPMD + uncaught(e); + } + } + + error.printStackTrace(); // NOPMD + uncaught(error); + } + + /** + * Checks if the given error is one of the already named + * bug cases that should pass through {@link #onError(Throwable)} + * as is. + * @param error the error to check + * @return true if the error should pass through, false if + * it may be wrapped into an UndeliverableException + */ + static boolean isBug(Throwable error) { + // user forgot to add the onError handler in subscribe + if (error instanceof OnErrorNotImplementedException) { + return true; + } + // the sender didn't honor the request amount + // it's either due to an operator bug or concurrent onNext + if (error instanceof MissingBackpressureException) { + return true; + } + // general protocol violations + // it's either due to an operator bug or concurrent onNext + if (error instanceof IllegalStateException) { + return true; + } + // nulls are generally not allowed + // likely an operator bug or missing null-check + if (error instanceof NullPointerException) { + return true; + } + // bad arguments, likely invalid user input + if (error instanceof IllegalArgumentException) { + return true; + } + // Crash while handling an exception + if (error instanceof CompositeException) { + return true; + } + // everything else is probably due to lifecycle limits + return false; + } + + static void uncaught(@NonNull Throwable error) { + Thread currentThread = Thread.currentThread(); + UncaughtExceptionHandler handler = currentThread.getUncaughtExceptionHandler(); + handler.uncaughtException(currentThread, error); + } + + /** + * Calls the associated hook function. + * @param defaultScheduler the hook's input value + * @return the value returned by the hook + */ + @NonNull + public static Scheduler onIoScheduler(@NonNull Scheduler defaultScheduler) { + Function f = onIoHandler; + if (f == null) { + return defaultScheduler; + } + return apply(f, defaultScheduler); + } + + /** + * Calls the associated hook function. + * @param defaultScheduler the hook's input value + * @return the value returned by the hook + */ + @NonNull + public static Scheduler onNewThreadScheduler(@NonNull Scheduler defaultScheduler) { + Function f = onNewThreadHandler; + if (f == null) { + return defaultScheduler; + } + return apply(f, defaultScheduler); + } + + /** + * Called when a task is scheduled. + * @param run the runnable instance + * @return the replacement runnable + */ + @NonNull + public static Runnable onSchedule(@NonNull Runnable run) { + ObjectHelper.requireNonNull(run, "run is null"); + + Function f = onScheduleHandler; + if (f == null) { + return run; + } + return apply(f, run); + } + + /** + * Calls the associated hook function. + * @param defaultScheduler the hook's input value + * @return the value returned by the hook + */ + @NonNull + public static Scheduler onSingleScheduler(@NonNull Scheduler defaultScheduler) { + Function f = onSingleHandler; + if (f == null) { + return defaultScheduler; + } + return apply(f, defaultScheduler); + } + + /** + * Removes all handlers and resets to default behavior. + */ + public static void reset() { + setErrorHandler(null); + setScheduleHandler(null); + + setComputationSchedulerHandler(null); + setInitComputationSchedulerHandler(null); + + setIoSchedulerHandler(null); + setInitIoSchedulerHandler(null); + + setSingleSchedulerHandler(null); + setInitSingleSchedulerHandler(null); + + setNewThreadSchedulerHandler(null); + setInitNewThreadSchedulerHandler(null); + + setOnFlowableAssembly(null); + setOnFlowableSubscribe(null); + + setOnObservableAssembly(null); + setOnObservableSubscribe(null); + + setOnSingleAssembly(null); + setOnSingleSubscribe(null); + + setOnCompletableAssembly(null); + setOnCompletableSubscribe(null); + + setOnConnectableFlowableAssembly(null); + setOnConnectableObservableAssembly(null); + + setOnMaybeAssembly(null); + setOnMaybeSubscribe(null); + + setOnParallelAssembly(null); + + setFailOnNonBlockingScheduler(false); + setOnBeforeBlocking(null); + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed + */ + public static void setComputationSchedulerHandler(@Nullable Function handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onComputationHandler = handler; + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed + */ + public static void setErrorHandler(@Nullable Consumer handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + errorHandler = handler; + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed, but the function may not return null + */ + public static void setInitComputationSchedulerHandler(@Nullable Function, ? extends Scheduler> handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onInitComputationHandler = handler; + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed, but the function may not return null + */ + public static void setInitIoSchedulerHandler(@Nullable Function, ? extends Scheduler> handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onInitIoHandler = handler; + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed, but the function may not return null + */ + public static void setInitNewThreadSchedulerHandler(@Nullable Function, ? extends Scheduler> handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onInitNewThreadHandler = handler; + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed, but the function may not return null + */ + public static void setInitSingleSchedulerHandler(@Nullable Function, ? extends Scheduler> handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onInitSingleHandler = handler; + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed + */ + public static void setIoSchedulerHandler(@Nullable Function handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onIoHandler = handler; + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed + */ + public static void setNewThreadSchedulerHandler(@Nullable Function handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onNewThreadHandler = handler; + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed + */ + public static void setScheduleHandler(@Nullable Function handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onScheduleHandler = handler; + } + + /** + * Sets the specific hook function. + * @param handler the hook function to set, null allowed + */ + public static void setSingleSchedulerHandler(@Nullable Function handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onSingleHandler = handler; + } + + /** + * Revokes the lockdown, only for testing purposes. + */ + /* test. */static void unlock() { + lockdown = false; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static Function getOnCompletableAssembly() { + return onCompletableAssembly; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + public static BiFunction getOnCompletableSubscribe() { + return onCompletableSubscribe; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @SuppressWarnings("rawtypes") + @Nullable + public static Function getOnFlowableAssembly() { + return onFlowableAssembly; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @SuppressWarnings("rawtypes") + @Nullable + public static Function getOnConnectableFlowableAssembly() { + return onConnectableFlowableAssembly; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + @SuppressWarnings("rawtypes") + public static BiFunction getOnFlowableSubscribe() { + return onFlowableSubscribe; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + @SuppressWarnings("rawtypes") + public static BiFunction getOnMaybeSubscribe() { + return onMaybeSubscribe; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + @SuppressWarnings("rawtypes") + public static Function getOnMaybeAssembly() { + return onMaybeAssembly; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + @SuppressWarnings("rawtypes") + public static Function getOnSingleAssembly() { + return onSingleAssembly; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + @SuppressWarnings("rawtypes") + public static BiFunction getOnSingleSubscribe() { + return onSingleSubscribe; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + @SuppressWarnings("rawtypes") + public static Function getOnObservableAssembly() { + return onObservableAssembly; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + @SuppressWarnings("rawtypes") + public static Function getOnConnectableObservableAssembly() { + return onConnectableObservableAssembly; + } + + /** + * Returns the current hook function. + * @return the hook function, may be null + */ + @Nullable + @SuppressWarnings("rawtypes") + public static BiFunction getOnObservableSubscribe() { + return onObservableSubscribe; + } + + /** + * Sets the specific hook function. + * @param onCompletableAssembly the hook function to set, null allowed + */ + public static void setOnCompletableAssembly(@Nullable Function onCompletableAssembly) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onCompletableAssembly = onCompletableAssembly; + } + + /** + * Sets the specific hook function. + * @param onCompletableSubscribe the hook function to set, null allowed + */ + public static void setOnCompletableSubscribe( + @Nullable BiFunction onCompletableSubscribe) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onCompletableSubscribe = onCompletableSubscribe; + } + + /** + * Sets the specific hook function. + * @param onFlowableAssembly the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnFlowableAssembly(@Nullable Function onFlowableAssembly) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onFlowableAssembly = onFlowableAssembly; + } + + /** + * Sets the specific hook function. + * @param onMaybeAssembly the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnMaybeAssembly(@Nullable Function onMaybeAssembly) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onMaybeAssembly = onMaybeAssembly; + } + + /** + * Sets the specific hook function. + * @param onConnectableFlowableAssembly the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnConnectableFlowableAssembly(@Nullable Function onConnectableFlowableAssembly) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onConnectableFlowableAssembly = onConnectableFlowableAssembly; + } + + /** + * Sets the specific hook function. + * @param onFlowableSubscribe the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnFlowableSubscribe(@Nullable BiFunction onFlowableSubscribe) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onFlowableSubscribe = onFlowableSubscribe; + } + + /** + * Sets the specific hook function. + * @param onMaybeSubscribe the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnMaybeSubscribe(@Nullable BiFunction onMaybeSubscribe) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onMaybeSubscribe = onMaybeSubscribe; + } + + /** + * Sets the specific hook function. + * @param onObservableAssembly the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnObservableAssembly(@Nullable Function onObservableAssembly) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onObservableAssembly = onObservableAssembly; + } + + /** + * Sets the specific hook function. + * @param onConnectableObservableAssembly the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnConnectableObservableAssembly(@Nullable Function onConnectableObservableAssembly) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onConnectableObservableAssembly = onConnectableObservableAssembly; + } + + /** + * Sets the specific hook function. + * @param onObservableSubscribe the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnObservableSubscribe( + @Nullable BiFunction onObservableSubscribe) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onObservableSubscribe = onObservableSubscribe; + } + + /** + * Sets the specific hook function. + * @param onSingleAssembly the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnSingleAssembly(@Nullable Function onSingleAssembly) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onSingleAssembly = onSingleAssembly; + } + + /** + * Sets the specific hook function. + * @param onSingleSubscribe the hook function to set, null allowed + */ + @SuppressWarnings("rawtypes") + public static void setOnSingleSubscribe(@Nullable BiFunction onSingleSubscribe) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + RxJavaPlugins.onSingleSubscribe = onSingleSubscribe; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @param subscriber the subscriber + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static Subscriber onSubscribe(@NonNull Flowable source, @NonNull Subscriber subscriber) { + BiFunction f = onFlowableSubscribe; + if (f != null) { + return apply(f, source, subscriber); + } + return subscriber; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @param observer the observer + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static Observer onSubscribe(@NonNull Observable source, @NonNull Observer observer) { + BiFunction f = onObservableSubscribe; + if (f != null) { + return apply(f, source, observer); + } + return observer; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @param observer the observer + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static SingleObserver onSubscribe(@NonNull Single source, @NonNull SingleObserver observer) { + BiFunction f = onSingleSubscribe; + if (f != null) { + return apply(f, source, observer); + } + return observer; + } + + /** + * Calls the associated hook function. + * @param source the hook's input value + * @param observer the observer + * @return the value returned by the hook + */ + @NonNull + public static CompletableObserver onSubscribe(@NonNull Completable source, @NonNull CompletableObserver observer) { + BiFunction f = onCompletableSubscribe; + if (f != null) { + return apply(f, source, observer); + } + return observer; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @param observer the subscriber + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static MaybeObserver onSubscribe(@NonNull Maybe source, @NonNull MaybeObserver observer) { + BiFunction f = onMaybeSubscribe; + if (f != null) { + return apply(f, source, observer); + } + return observer; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static Maybe onAssembly(@NonNull Maybe source) { + Function f = onMaybeAssembly; + if (f != null) { + return apply(f, source); + } + return source; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static Flowable onAssembly(@NonNull Flowable source) { + Function f = onFlowableAssembly; + if (f != null) { + return apply(f, source); + } + return source; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static ConnectableFlowable onAssembly(@NonNull ConnectableFlowable source) { + Function f = onConnectableFlowableAssembly; + if (f != null) { + return apply(f, source); + } + return source; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static Observable onAssembly(@NonNull Observable source) { + Function f = onObservableAssembly; + if (f != null) { + return apply(f, source); + } + return source; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static ConnectableObservable onAssembly(@NonNull ConnectableObservable source) { + Function f = onConnectableObservableAssembly; + if (f != null) { + return apply(f, source); + } + return source; + } + + /** + * Calls the associated hook function. + * @param the value type + * @param source the hook's input value + * @return the value returned by the hook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static Single onAssembly(@NonNull Single source) { + Function f = onSingleAssembly; + if (f != null) { + return apply(f, source); + } + return source; + } + + /** + * Calls the associated hook function. + * @param source the hook's input value + * @return the value returned by the hook + */ + @NonNull + public static Completable onAssembly(@NonNull Completable source) { + Function f = onCompletableAssembly; + if (f != null) { + return apply(f, source); + } + return source; + } + + /** + * Sets the specific hook function. + *

History: 2.0.6 - experimental; 2.1 - beta + * @param handler the hook function to set, null allowed + * @since 2.2 + */ + @SuppressWarnings("rawtypes") + public static void setOnParallelAssembly(@Nullable Function handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onParallelAssembly = handler; + } + + /** + * Returns the current hook function. + *

History: 2.0.6 - experimental; 2.1 - beta + * @return the hook function, may be null + * @since 2.2 + */ + @SuppressWarnings("rawtypes") + @Nullable + public static Function getOnParallelAssembly() { + return onParallelAssembly; + } + + /** + * Calls the associated hook function. + *

History: 2.0.6 - experimental; 2.1 - beta + * @param the value type of the source + * @param source the hook's input value + * @return the value returned by the hook + * @since 2.2 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @NonNull + public static ParallelFlowable onAssembly(@NonNull ParallelFlowable source) { + Function f = onParallelAssembly; + if (f != null) { + return apply(f, source); + } + return source; + } + + /** + * Called before an operator attempts a blocking operation + * such as awaiting a condition or signal + * and should return true to indicate the operator + * should not block but throw an IllegalArgumentException. + *

History: 2.0.5 - experimental + * @return true if the blocking should be prevented + * @see #setFailOnNonBlockingScheduler(boolean) + * @since 2.1 + */ + public static boolean onBeforeBlocking() { + BooleanSupplier f = onBeforeBlocking; + if (f != null) { + try { + return f.getAsBoolean(); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + return false; + } + + /** + * Set the handler that is called when an operator attempts a blocking + * await; the handler should return true to prevent the blocking + * and to signal an IllegalStateException instead. + *

History: 2.0.5 - experimental + * @param handler the handler to set, null resets to the default handler + * that always returns false + * @see #onBeforeBlocking() + * @since 2.1 + */ + public static void setOnBeforeBlocking(@Nullable BooleanSupplier handler) { + if (lockdown) { + throw new IllegalStateException("Plugins can't be changed anymore"); + } + onBeforeBlocking = handler; + } + + /** + * Returns the current blocking handler or null if no custom handler + * is set. + *

History: 2.0.5 - experimental + * @return the current blocking handler or null if not specified + * @since 2.1 + */ + @Nullable + public static BooleanSupplier getOnBeforeBlocking() { + return onBeforeBlocking; + } + + /** + * Create an instance of the default {@link Scheduler} used for {@link Schedulers#computation()} + * except using {@code threadFactory} for thread creation. + *

History: 2.0.5 - experimental + * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any + * system properties for configuring new thread creation. Cannot be null. + * @return the created Scheduler instance + * @since 2.1 + */ + @NonNull + public static Scheduler createComputationScheduler(@NonNull ThreadFactory threadFactory) { + return new ComputationScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); + } + + /** + * Create an instance of the default {@link Scheduler} used for {@link Schedulers#io()} + * except using {@code threadFactory} for thread creation. + *

History: 2.0.5 - experimental + * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any + * system properties for configuring new thread creation. Cannot be null. + * @return the created Scheduler instance + * @since 2.1 + */ + @NonNull + public static Scheduler createIoScheduler(@NonNull ThreadFactory threadFactory) { + return new IoScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); + } + + /** + * Create an instance of the default {@link Scheduler} used for {@link Schedulers#newThread()} + * except using {@code threadFactory} for thread creation. + *

History: 2.0.5 - experimental + * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any + * system properties for configuring new thread creation. Cannot be null. + * @return the created Scheduler instance + * @since 2.1 + */ + @NonNull + public static Scheduler createNewThreadScheduler(@NonNull ThreadFactory threadFactory) { + return new NewThreadScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); + } + + /** + * Create an instance of the default {@link Scheduler} used for {@link Schedulers#single()} + * except using {@code threadFactory} for thread creation. + *

History: 2.0.5 - experimental + * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any + * system properties for configuring new thread creation. Cannot be null. + * @return the created Scheduler instance + * @since 2.1 + */ + @NonNull + public static Scheduler createSingleScheduler(@NonNull ThreadFactory threadFactory) { + return new SingleScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); + } + + /** + * Wraps the call to the function in try-catch and propagates thrown + * checked exceptions as RuntimeException. + * @param the input type + * @param the output type + * @param f the function to call, not null (not verified) + * @param t the parameter value to the function + * @return the result of the function call + */ + @NonNull + static R apply(@NonNull Function f, @NonNull T t) { + try { + return f.apply(t); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Wraps the call to the function in try-catch and propagates thrown + * checked exceptions as RuntimeException. + * @param the first input type + * @param the second input type + * @param the output type + * @param f the function to call, not null (not verified) + * @param t the first parameter value to the function + * @param u the second parameter value to the function + * @return the result of the function call + */ + @NonNull + static R apply(@NonNull BiFunction f, @NonNull T t, @NonNull U u) { + try { + return f.apply(t, u); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Wraps the call to the Scheduler creation callable in try-catch and propagates thrown + * checked exceptions as RuntimeException and enforces that result is not null. + * @param s the {@link Callable} which returns a {@link Scheduler}, not null (not verified). Cannot return null + * @return the result of the callable call, not null + * @throws NullPointerException if the callable parameter returns null + */ + @NonNull + static Scheduler callRequireNonNull(@NonNull Callable s) { + try { + return ObjectHelper.requireNonNull(s.call(), "Scheduler Callable result can't be null"); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Wraps the call to the Scheduler creation function in try-catch and propagates thrown + * checked exceptions as RuntimeException and enforces that result is not null. + * @param f the function to call, not null (not verified). Cannot return null + * @param s the parameter value to the function + * @return the result of the function call, not null + * @throws NullPointerException if the function parameter returns null + */ + @NonNull + static Scheduler applyRequireNonNull(@NonNull Function, ? extends Scheduler> f, Callable s) { + return ObjectHelper.requireNonNull(apply(f, s), "Scheduler Callable result can't be null"); + } + + /** Helper class, no instances. */ + private RxJavaPlugins() { + throw new IllegalStateException("No instances!"); + } +} diff --git a/src/main/java/io/reactivex/plugins/package-info.java b/src/main/java/io/reactivex/plugins/package-info.java new file mode 100755 index 0000000..3031129 --- /dev/null +++ b/src/main/java/io/reactivex/plugins/package-info.java @@ -0,0 +1,21 @@ +/** + * 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. + */ + +/** + * Contains the central plugin handler {@link io.reactivex.plugins.RxJavaPlugins} + * class to hook into the lifecycle of the base reactive types and schedulers. + */ +package io.reactivex.plugins; diff --git a/src/main/java/io/reactivex/processors/AsyncProcessor.java b/src/main/java/io/reactivex/processors/AsyncProcessor.java new file mode 100755 index 0000000..70aadfd --- /dev/null +++ b/src/main/java/io/reactivex/processors/AsyncProcessor.java @@ -0,0 +1,405 @@ +/** + * 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.processors; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.*; + +import io.reactivex.annotations.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.DeferredScalarSubscription; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Processor that emits the very last value followed by a completion event or the received error + * to {@link Subscriber}s. + *

+ * + *

+ * This processor does not have a public constructor by design; a new empty instance of this + * {@code AsyncProcessor} can be created via the {@link #create()} method. + *

+ * Since an {@code AsyncProcessor} is a Reactive Streams {@code Processor} type, + * {@code null}s are not allowed (Rule 2.13) + * as parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the processor's state is not changed. + *

+ * {@code AsyncProcessor} is a {@link io.reactivex.Flowable} as well as a {@link FlowableProcessor} and supports backpressure from the downstream but + * its {@link Subscriber}-side consumes items in an unbounded manner. + *

+ * When this {@code AsyncProcessor} is terminated via {@link #onError(Throwable)}, the + * last observed item (if any) is cleared and late {@link Subscriber}s only receive + * the {@code onError} event. + *

+ * The {@code AsyncProcessor} caches the latest item internally and it emits this item only when {@code onComplete} is called. + * Therefore, it is not recommended to use this {@code Processor} with infinite or never-completing sources. + *

+ * Even though {@code AsyncProcessor} implements the {@link Subscriber} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code AsyncProcessor} reached its terminal state will result in the + * given {@link Subscription} being canceled immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + * The implementation of {@code onXXX} methods are technically thread-safe but non-serialized calls + * to them may lead to undefined state in the currently subscribed {@code Subscriber}s. + *

+ * This {@code AsyncProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()} as well as means to read the very last observed value - + * after this {@code AsyncProcessor} has been completed - in a non-blocking and thread-safe + * manner via {@link #hasValue()}, {@link #getValue()}, {@link #getValues()} or {@link #getValues(Object[])}. + *

+ *
Backpressure:
+ *
The {@code AsyncProcessor} honors the backpressure of the downstream {@code Subscriber}s and won't emit + * its single value to a particular {@code Subscriber} until that {@code Subscriber} has requested an item. + * When the {@code AsyncProcessor} is subscribed to a {@link io.reactivex.Flowable}, the processor consumes this + * {@code Flowable} in an unbounded manner (requesting `Long.MAX_VALUE`) as only the very last upstream item is + * retained by it. + *
+ *
Scheduler:
+ *
{@code AsyncProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Subscriber}s get notified on the thread where the terminating {@code onError} or {@code onComplete} + * methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code AsyncProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Subscriber}s. During this emission, + * if one or more {@code Subscriber}s dispose their respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Subscriber}s + * cancel at once). + * If there were no {@code Subscriber}s subscribed to this {@code AsyncProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *


+ * AsyncProcessor<Object> processor = AsyncProcessor.create();
+ *
+ * TestSubscriber<Object> ts1 = processor.test();
+ *
+ * ts1.assertEmpty();
+ *
+ * processor.onNext(1);
+ *
+ * // AsyncProcessor only emits when onComplete was called.
+ * ts1.assertEmpty();
+ *
+ * processor.onNext(2);
+ * processor.onComplete();
+ *
+ * // onComplete triggers the emission of the last cached item and the onComplete event.
+ * ts1.assertResult(2);
+ *
+ * TestSubscriber<Object> ts2 = processor.test();
+ *
+ * // late Subscribers receive the last cached item too
+ * ts2.assertResult(2);
+ * 
+ * @param the value type + */ +public final class AsyncProcessor extends FlowableProcessor { + + @SuppressWarnings("rawtypes") + static final AsyncSubscription[] EMPTY = new AsyncSubscription[0]; + + @SuppressWarnings("rawtypes") + static final AsyncSubscription[] TERMINATED = new AsyncSubscription[0]; + + final AtomicReference[]> subscribers; + + /** Write before updating subscribers, read after reading subscribers as TERMINATED. */ + Throwable error; + + /** Write before updating subscribers, read after reading subscribers as TERMINATED. */ + T value; + + /** + * Creates a new AsyncProcessor. + * @param the value type to be received and emitted + * @return the new AsyncProcessor instance + */ + @CheckReturnValue + @NonNull + public static AsyncProcessor create() { + return new AsyncProcessor(); + } + + /** + * Constructs an AsyncProcessor. + * @since 2.0 + */ + @SuppressWarnings("unchecked") + AsyncProcessor() { + this.subscribers = new AtomicReference[]>(EMPTY); + } + + @Override + public void onSubscribe(Subscription s) { + if (subscribers.get() == TERMINATED) { + s.cancel(); + return; + } + // AsyncProcessor doesn't bother with request coordination. + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + if (subscribers.get() == TERMINATED) { + return; + } + value = t; + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (subscribers.get() == TERMINATED) { + RxJavaPlugins.onError(t); + return; + } + value = null; + error = t; + for (AsyncSubscription as : subscribers.getAndSet(TERMINATED)) { + as.onError(t); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + if (subscribers.get() == TERMINATED) { + return; + } + T v = value; + AsyncSubscription[] array = subscribers.getAndSet(TERMINATED); + if (v == null) { + for (AsyncSubscription as : array) { + as.onComplete(); + } + } else { + for (AsyncSubscription as : array) { + as.complete(v); + } + } + } + + @Override + public boolean hasSubscribers() { + return subscribers.get().length != 0; + } + + @Override + public boolean hasThrowable() { + return subscribers.get() == TERMINATED && error != null; + } + + @Override + public boolean hasComplete() { + return subscribers.get() == TERMINATED && error == null; + } + + @Override + @Nullable + public Throwable getThrowable() { + return subscribers.get() == TERMINATED ? error : null; + } + + @Override + protected void subscribeActual(Subscriber s) { + AsyncSubscription as = new AsyncSubscription(s, this); + s.onSubscribe(as); + if (add(as)) { + if (as.isCancelled()) { + remove(as); + } + } else { + Throwable ex = error; + if (ex != null) { + s.onError(ex); + } else { + T v = value; + if (v != null) { + as.complete(v); + } else { + as.onComplete(); + } + } + } + } + + /** + * Tries to add the given subscriber to the subscribers array atomically + * or returns false if the processor has terminated. + * @param ps the subscriber to add + * @return true if successful, false if the processor has terminated + */ + boolean add(AsyncSubscription ps) { + for (;;) { + AsyncSubscription[] a = subscribers.get(); + if (a == TERMINATED) { + return false; + } + + int n = a.length; + @SuppressWarnings("unchecked") + AsyncSubscription[] b = new AsyncSubscription[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = ps; + + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + /** + * Atomically removes the given subscriber if it is subscribed to this processor. + * @param ps the subscriber's subscription wrapper to remove + */ + @SuppressWarnings("unchecked") + void remove(AsyncSubscription ps) { + for (;;) { + AsyncSubscription[] a = subscribers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == ps) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + AsyncSubscription[] b; + + if (n == 1) { + b = EMPTY; + } else { + b = new AsyncSubscription[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (subscribers.compareAndSet(a, b)) { + return; + } + } + } + + /** + * Returns true if this processor has any value. + *

The method is thread-safe. + * @return true if this processor has any value + */ + public boolean hasValue() { + return subscribers.get() == TERMINATED && value != null; + } + + /** + * Returns a single value this processor currently has or null if no such value exists. + *

The method is thread-safe. + * @return a single value this processor currently has or null if no such value exists + */ + @Nullable + public T getValue() { + return subscribers.get() == TERMINATED ? value : null; + } + + /** + * Returns an Object array containing snapshot all values of this processor. + *

The method is thread-safe. + * @return the array containing the snapshot of all values of this processor + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x + */ + @Deprecated + public Object[] getValues() { + T v = getValue(); + return v != null ? new Object[] { v } : new Object[0]; + } + + /** + * Returns a typed array containing a snapshot of all values of this processor. + *

The method follows the conventions of Collection.toArray by setting the array element + * after the last value to null (if the capacity permits). + *

The method is thread-safe. + * @param array the target array to copy values into if it fits + * @return the given array if the values fit into it or a new array containing all values + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x + */ + @Deprecated + public T[] getValues(T[] array) { + T v = getValue(); + if (v == null) { + if (array.length != 0) { + array[0] = null; + } + return array; + } + if (array.length == 0) { + array = Arrays.copyOf(array, 1); + } + array[0] = v; + if (array.length != 1) { + array[1] = null; + } + return array; + } + + static final class AsyncSubscription extends DeferredScalarSubscription { + private static final long serialVersionUID = 5629876084736248016L; + + final AsyncProcessor parent; + + AsyncSubscription(Subscriber actual, AsyncProcessor parent) { + super(actual); + this.parent = parent; + } + + @Override + public void cancel() { + if (super.tryCancel()) { + parent.remove(this); + } + } + + void onComplete() { + if (!isCancelled()) { + downstream.onComplete(); + } + } + + void onError(Throwable t) { + if (isCancelled()) { + RxJavaPlugins.onError(t); + } else { + downstream.onError(t); + } + } + } +} diff --git a/src/main/java/io/reactivex/processors/BehaviorProcessor.java b/src/main/java/io/reactivex/processors/BehaviorProcessor.java new file mode 100755 index 0000000..bf970ee --- /dev/null +++ b/src/main/java/io/reactivex/processors/BehaviorProcessor.java @@ -0,0 +1,674 @@ +/** + * 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.processors; + +import java.lang.reflect.Array; +import java.util.concurrent.atomic.*; +import java.util.concurrent.locks.*; + +import org.reactivestreams.*; + +import io.reactivex.annotations.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.internal.util.AppendOnlyLinkedArrayList.NonThrowingPredicate; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Processor that emits the most recent item it has observed and all subsequent observed items to each subscribed + * {@link Subscriber}. + *

+ * + *

+ * This processor does not have a public constructor by design; a new empty instance of this + * {@code BehaviorProcessor} can be created via the {@link #create()} method and + * a new non-empty instance can be created via {@link #createDefault(Object)} (named as such to avoid + * overload resolution conflict with {@code Flowable.create} that creates a Flowable, not a {@code BehaviorProcessor}). + *

+ * In accordance with the Reactive Streams specification (Rule 2.13) + * {@code null}s are not allowed as default initial values in {@link #createDefault(Object)} or as parameters to {@link #onNext(Object)} and + * {@link #onError(Throwable)}. + *

+ * When this {@code BehaviorProcessor} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, the + * last observed item (if any) is cleared and late {@link org.reactivestreams.Subscriber}s only receive + * the respective terminal event. + *

+ * The {@code BehaviorProcessor} does not support clearing its cached value (to appear empty again), however, the + * effect can be achieved by using a special item and making sure {@code Subscriber}s subscribe through a + * filter whose predicate filters out this special item: + *


+ * BehaviorProcessor<Integer> processor = BehaviorProcessor.create();
+ *
+ * final Integer EMPTY = Integer.MIN_VALUE;
+ *
+ * Flowable<Integer> flowable = processor.filter(v -> v != EMPTY);
+ *
+ * TestSubscriber<Integer> ts1 = flowable.test();
+ *
+ * processor.onNext(1);
+ * // this will "clear" the cache
+ * processor.onNext(EMPTY);
+ *
+ * TestSubscriber<Integer> ts2 = flowable.test();
+ *
+ * processor.onNext(2);
+ * processor.onComplete();
+ *
+ * // ts1 received both non-empty items
+ * ts1.assertResult(1, 2);
+ *
+ * // ts2 received only 2 even though the current item was EMPTY
+ * // when it got subscribed
+ * ts2.assertResult(2);
+ *
+ * // Subscribers coming after the processor was terminated receive
+ * // no items and only the onComplete event in this case.
+ * flowable.test().assertResult();
+ * 
+ *

+ * Even though {@code BehaviorProcessor} implements the {@code Subscriber} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code BehaviorProcessor} reached its terminal state will result in the + * given {@code Subscription} being cancelled immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #offer(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + * Note that serializing over {@link #offer(Object)} is not supported through {@code toSerialized()} because it is a method + * available on the {@code PublishProcessor} and {@code BehaviorProcessor} classes only. + *

+ * This {@code BehaviorProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()} as well as means to read the latest observed value + * in a non-blocking and thread-safe manner via {@link #hasValue()}, {@link #getValue()}, + * {@link #getValues()} or {@link #getValues(Object[])}. + *

+ * Note that this processor signals {@code MissingBackpressureException} if a particular {@code Subscriber} is not + * ready to receive {@code onNext} events. To avoid this exception being signaled, use {@link #offer(Object)} to only + * try to emit an item when all {@code Subscriber}s have requested item(s). + *

+ *
Backpressure:
+ *
The {@code BehaviorProcessor} does not coordinate requests of its downstream {@code Subscriber}s and + * expects each individual {@code Subscriber} is ready to receive {@code onNext} items when {@link #onNext(Object)} + * is called. If a {@code Subscriber} is not ready, a {@code MissingBackpressureException} is signalled to it. + * To avoid overflowing the current {@code Subscriber}s, the conditional {@link #offer(Object)} method is available + * that returns true if any of the {@code Subscriber}s is not ready to receive {@code onNext} events. If + * there are no {@code Subscriber}s to the processor, {@code offer()} always succeeds. + * If the {@code BehaviorProcessor} is (optionally) subscribed to another {@code Publisher}, this upstream + * {@code Publisher} is consumed in an unbounded fashion (requesting {@code Long.MAX_VALUE}).
+ *
Scheduler:
+ *
{@code BehaviorProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Subscriber}s get notified on the thread the respective {@code onXXX} methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code BehaviorProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Subscriber}s. During this emission, + * if one or more {@code Subscriber}s cancel their respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Subscriber}s + * cancel at once). + * If there were no {@code Subscriber}s subscribed to this {@code BehaviorProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *

 {@code
+
+  // subscriber will receive all events.
+  BehaviorProcessor processor = BehaviorProcessor.create("default");
+  processor.subscribe(subscriber);
+  processor.onNext("one");
+  processor.onNext("two");
+  processor.onNext("three");
+
+  // subscriber will receive the "one", "two" and "three" events, but not "zero"
+  BehaviorProcessor processor = BehaviorProcessor.create("default");
+  processor.onNext("zero");
+  processor.onNext("one");
+  processor.subscribe(subscriber);
+  processor.onNext("two");
+  processor.onNext("three");
+
+  // subscriber will receive only onComplete
+  BehaviorProcessor processor = BehaviorProcessor.create("default");
+  processor.onNext("zero");
+  processor.onNext("one");
+  processor.onComplete();
+  processor.subscribe(subscriber);
+
+  // subscriber will receive only onError
+  BehaviorProcessor processor = BehaviorProcessor.create("default");
+  processor.onNext("zero");
+  processor.onNext("one");
+  processor.onError(new RuntimeException("error"));
+  processor.subscribe(subscriber);
+  } 
+ *
+ * @param 
+ *          the type of item expected to be observed and emitted by the Processor
+ */
+public final class BehaviorProcessor extends FlowableProcessor {
+    final AtomicReference[]> subscribers;
+
+    static final Object[] EMPTY_ARRAY = new Object[0];
+
+    @SuppressWarnings("rawtypes")
+    static final BehaviorSubscription[] EMPTY = new BehaviorSubscription[0];
+
+    @SuppressWarnings("rawtypes")
+    static final BehaviorSubscription[] TERMINATED = new BehaviorSubscription[0];
+
+    final ReadWriteLock lock;
+    final Lock readLock;
+    final Lock writeLock;
+
+    final AtomicReference value;
+
+    final AtomicReference terminalEvent;
+
+    long index;
+
+    /**
+     * Creates a {@link BehaviorProcessor} without a default item.
+     *
+     * @param 
+     *            the type of item the BehaviorProcessor will emit
+     * @return the constructed {@link BehaviorProcessor}
+     */
+    @CheckReturnValue
+    @NonNull
+    public static  BehaviorProcessor create() {
+        return new BehaviorProcessor();
+    }
+
+    /**
+     * Creates a {@link BehaviorProcessor} that emits the last item it observed and all subsequent items to each
+     * {@link Subscriber} that subscribes to it.
+     *
+     * @param 
+     *            the type of item the BehaviorProcessor will emit
+     * @param defaultValue
+     *            the item that will be emitted first to any {@link Subscriber} as long as the
+     *            {@link BehaviorProcessor} has not yet observed any items from its source {@code Observable}
+     * @return the constructed {@link BehaviorProcessor}
+     */
+    @CheckReturnValue
+    @NonNull
+    public static  BehaviorProcessor createDefault(T defaultValue) {
+        ObjectHelper.requireNonNull(defaultValue, "defaultValue is null");
+        return new BehaviorProcessor(defaultValue);
+    }
+
+    /**
+     * Constructs an empty BehaviorProcessor.
+     * @since 2.0
+     */
+    @SuppressWarnings("unchecked")
+    BehaviorProcessor() {
+        this.value = new AtomicReference();
+        this.lock = new ReentrantReadWriteLock();
+        this.readLock = lock.readLock();
+        this.writeLock = lock.writeLock();
+        this.subscribers = new AtomicReference[]>(EMPTY);
+        this.terminalEvent = new AtomicReference();
+    }
+
+    /**
+     * Constructs a BehaviorProcessor with the given initial value.
+     * @param defaultValue the initial value, not null (verified)
+     * @throws NullPointerException if {@code defaultValue} is null
+     * @since 2.0
+     */
+    BehaviorProcessor(T defaultValue) {
+        this();
+        this.value.lazySet(ObjectHelper.requireNonNull(defaultValue, "defaultValue is null"));
+    }
+
+    @Override
+    protected void subscribeActual(Subscriber s) {
+        BehaviorSubscription bs = new BehaviorSubscription(s, this);
+        s.onSubscribe(bs);
+        if (add(bs)) {
+            if (bs.cancelled) {
+                remove(bs);
+            } else {
+                bs.emitFirst();
+            }
+        } else {
+            Throwable ex = terminalEvent.get();
+            if (ex == ExceptionHelper.TERMINATED) {
+                s.onComplete();
+            } else {
+                s.onError(ex);
+            }
+        }
+    }
+
+    @Override
+    public void onSubscribe(Subscription s) {
+        if (terminalEvent.get() != null) {
+            s.cancel();
+            return;
+        }
+        s.request(Long.MAX_VALUE);
+    }
+
+    @Override
+    public void onNext(T t) {
+        ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources.");
+
+        if (terminalEvent.get() != null) {
+            return;
+        }
+        Object o = NotificationLite.next(t);
+        setCurrent(o);
+        for (BehaviorSubscription bs : subscribers.get()) {
+            bs.emitNext(o, index);
+        }
+    }
+
+    @Override
+    public void onError(Throwable t) {
+        ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources.");
+        if (!terminalEvent.compareAndSet(null, t)) {
+            RxJavaPlugins.onError(t);
+            return;
+        }
+        Object o = NotificationLite.error(t);
+        for (BehaviorSubscription bs : terminate(o)) {
+            bs.emitNext(o, index);
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        if (!terminalEvent.compareAndSet(null, ExceptionHelper.TERMINATED)) {
+            return;
+        }
+        Object o = NotificationLite.complete();
+        for (BehaviorSubscription bs : terminate(o)) {
+            bs.emitNext(o, index);  // relaxed read okay since this is the only mutator thread
+        }
+    }
+
+    /**
+     * Tries to emit the item to all currently subscribed Subscribers if all of them
+     * has requested some value, returns false otherwise.
+     * 

+ * This method should be called in a sequential manner just like the onXXX methods + * of the PublishProcessor. + *

+ * Calling with null will terminate the PublishProcessor and a NullPointerException + * is signalled to the Subscribers. + *

History: 2.0.8 - experimental + * @param t the item to emit, not null + * @return true if the item was emitted to all Subscribers + * @since 2.2 + */ + public boolean offer(T t) { + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return true; + } + BehaviorSubscription[] array = subscribers.get(); + + for (BehaviorSubscription s : array) { + if (s.isFull()) { + return false; + } + } + + Object o = NotificationLite.next(t); + setCurrent(o); + for (BehaviorSubscription bs : array) { + bs.emitNext(o, index); + } + return true; + } + + @Override + public boolean hasSubscribers() { + return subscribers.get().length != 0; + } + + /* test support*/ int subscriberCount() { + return subscribers.get().length; + } + + @Override + @Nullable + public Throwable getThrowable() { + Object o = value.get(); + if (NotificationLite.isError(o)) { + return NotificationLite.getError(o); + } + return null; + } + + /** + * Returns a single value the BehaviorProcessor currently has or null if no such value exists. + *

The method is thread-safe. + * @return a single value the BehaviorProcessor currently has or null if no such value exists + */ + @Nullable + public T getValue() { + Object o = value.get(); + if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { + return null; + } + return NotificationLite.getValue(o); + } + + /** + * Returns an Object array containing snapshot all values of the BehaviorProcessor. + *

The method is thread-safe. + * @return the array containing the snapshot of all values of the BehaviorProcessor + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x + */ + @Deprecated + public Object[] getValues() { + @SuppressWarnings("unchecked") + T[] a = (T[])EMPTY_ARRAY; + T[] b = getValues(a); + if (b == EMPTY_ARRAY) { + return new Object[0]; + } + return b; + + } + + /** + * Returns a typed array containing a snapshot of all values of the BehaviorProcessor. + *

The method follows the conventions of Collection.toArray by setting the array element + * after the last value to null (if the capacity permits). + *

The method is thread-safe. + * @param array the target array to copy values into if it fits + * @return the given array if the values fit into it or a new array containing all values + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x + */ + @Deprecated + @SuppressWarnings("unchecked") + public T[] getValues(T[] array) { + Object o = value.get(); + if (o == null || NotificationLite.isComplete(o) || NotificationLite.isError(o)) { + if (array.length != 0) { + array[0] = null; + } + return array; + } + T v = NotificationLite.getValue(o); + if (array.length != 0) { + array[0] = v; + if (array.length != 1) { + array[1] = null; + } + } else { + array = (T[])Array.newInstance(array.getClass().getComponentType(), 1); + array[0] = v; + } + return array; + } + + @Override + public boolean hasComplete() { + Object o = value.get(); + return NotificationLite.isComplete(o); + } + + @Override + public boolean hasThrowable() { + Object o = value.get(); + return NotificationLite.isError(o); + } + + /** + * Returns true if the BehaviorProcessor has any value. + *

The method is thread-safe. + * @return true if the BehaviorProcessor has any value + */ + public boolean hasValue() { + Object o = value.get(); + return o != null && !NotificationLite.isComplete(o) && !NotificationLite.isError(o); + } + + boolean add(BehaviorSubscription rs) { + for (;;) { + BehaviorSubscription[] a = subscribers.get(); + if (a == TERMINATED) { + return false; + } + int len = a.length; + @SuppressWarnings("unchecked") + BehaviorSubscription[] b = new BehaviorSubscription[len + 1]; + System.arraycopy(a, 0, b, 0, len); + b[len] = rs; + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(BehaviorSubscription rs) { + for (;;) { + BehaviorSubscription[] a = subscribers.get(); + int len = a.length; + if (len == 0) { + return; + } + int j = -1; + for (int i = 0; i < len; i++) { + if (a[i] == rs) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + BehaviorSubscription[] b; + if (len == 1) { + b = EMPTY; + } else { + b = new BehaviorSubscription[len - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, len - j - 1); + } + if (subscribers.compareAndSet(a, b)) { + return; + } + } + } + + @SuppressWarnings("unchecked") + BehaviorSubscription[] terminate(Object terminalValue) { + + BehaviorSubscription[] a = subscribers.get(); + if (a != TERMINATED) { + a = subscribers.getAndSet(TERMINATED); + if (a != TERMINATED) { + // either this or atomics with lots of allocation + setCurrent(terminalValue); + } + } + + return a; + } + + void setCurrent(Object o) { + Lock wl = writeLock; + wl.lock(); + index++; + value.lazySet(o); + wl.unlock(); + } + + static final class BehaviorSubscription extends AtomicLong implements Subscription, NonThrowingPredicate { + + private static final long serialVersionUID = 3293175281126227086L; + + final Subscriber downstream; + final BehaviorProcessor state; + + boolean next; + boolean emitting; + AppendOnlyLinkedArrayList queue; + + boolean fastPath; + + volatile boolean cancelled; + + long index; + + BehaviorSubscription(Subscriber actual, BehaviorProcessor state) { + this.downstream = actual; + this.state = state; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(this, n); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + state.remove(this); + } + } + + void emitFirst() { + if (cancelled) { + return; + } + Object o; + synchronized (this) { + if (cancelled) { + return; + } + if (next) { + return; + } + + BehaviorProcessor s = state; + + Lock readLock = s.readLock; + readLock.lock(); + index = s.index; + o = s.value.get(); + readLock.unlock(); + + emitting = o != null; + next = true; + } + + if (o != null) { + if (test(o)) { + return; + } + + emitLoop(); + } + } + + void emitNext(Object value, long stateIndex) { + if (cancelled) { + return; + } + if (!fastPath) { + synchronized (this) { + if (cancelled) { + return; + } + if (index == stateIndex) { + return; + } + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.add(value); + return; + } + next = true; + } + fastPath = true; + } + + test(value); + } + + @Override + public boolean test(Object o) { + if (cancelled) { + return true; + } + + if (NotificationLite.isComplete(o)) { + downstream.onComplete(); + return true; + } else + if (NotificationLite.isError(o)) { + downstream.onError(NotificationLite.getError(o)); + return true; + } + + long r = get(); + if (r != 0L) { + downstream.onNext(NotificationLite.getValue(o)); + if (r != Long.MAX_VALUE) { + decrementAndGet(); + } + return false; + } + cancel(); + downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests")); + return true; + } + + void emitLoop() { + for (;;) { + if (cancelled) { + return; + } + AppendOnlyLinkedArrayList q; + synchronized (this) { + q = queue; + if (q == null) { + emitting = false; + return; + } + queue = null; + } + + q.forEachWhile(this); + } + } + + public boolean isFull() { + return get() == 0L; + } + } +} diff --git a/src/main/java/io/reactivex/processors/FlowableProcessor.java b/src/main/java/io/reactivex/processors/FlowableProcessor.java new file mode 100755 index 0000000..8135afa --- /dev/null +++ b/src/main/java/io/reactivex/processors/FlowableProcessor.java @@ -0,0 +1,79 @@ +/** + * 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.processors; + +import org.reactivestreams.Processor; + +import io.reactivex.*; +import io.reactivex.annotations.*; + +/** + * Represents a Subscriber and a Flowable (Publisher) at the same time, allowing + * multicasting events from a single source to multiple child Subscribers. + *

All methods except the onSubscribe, onNext, onError and onComplete are thread-safe. + * Use {@link #toSerialized()} to make these methods thread-safe as well. + * + * @param the item value type + */ +public abstract class FlowableProcessor extends Flowable implements Processor, FlowableSubscriber { + + /** + * Returns true if the FlowableProcessor has subscribers. + *

The method is thread-safe. + * @return true if the FlowableProcessor has subscribers + */ + public abstract boolean hasSubscribers(); + + /** + * Returns true if the FlowableProcessor has reached a terminal state through an error event. + *

The method is thread-safe. + * @return true if the FlowableProcessor has reached a terminal state through an error event + * @see #getThrowable() + * @see #hasComplete() + */ + public abstract boolean hasThrowable(); + + /** + * Returns true if the FlowableProcessor has reached a terminal state through a complete event. + *

The method is thread-safe. + * @return true if the FlowableProcessor has reached a terminal state through a complete event + * @see #hasThrowable() + */ + public abstract boolean hasComplete(); + + /** + * Returns the error that caused the FlowableProcessor to terminate or null if the FlowableProcessor + * hasn't terminated yet. + *

The method is thread-safe. + * @return the error that caused the FlowableProcessor to terminate or null if the FlowableProcessor + * hasn't terminated yet + */ + @Nullable + public abstract Throwable getThrowable(); + + /** + * Wraps this FlowableProcessor and serializes the calls to the onSubscribe, onNext, onError and + * onComplete methods, making them thread-safe. + *

The method is thread-safe. + * @return the wrapped and serialized FlowableProcessor + */ + @NonNull + @CheckReturnValue + public final FlowableProcessor toSerialized() { + if (this instanceof SerializedProcessor) { + return this; + } + return new SerializedProcessor(this); + } +} diff --git a/src/main/java/io/reactivex/processors/MulticastProcessor.java b/src/main/java/io/reactivex/processors/MulticastProcessor.java new file mode 100755 index 0000000..31b7a64 --- /dev/null +++ b/src/main/java/io/reactivex/processors/MulticastProcessor.java @@ -0,0 +1,641 @@ +/** + * 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.processors; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.annotations.*; +import io.reactivex.exceptions.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * A {@link FlowableProcessor} implementation that coordinates downstream requests through + * a front-buffer and stable-prefetching, optionally canceling the upstream if all + * subscribers have cancelled. + *

+ * + *

+ * This processor does not have a public constructor by design; a new empty instance of this + * {@code MulticastProcessor} can be created via the following {@code create} methods that + * allow configuring it: + *

    + *
  • {@link #create()}: create an empty {@code MulticastProcessor} with + * {@link io.reactivex.Flowable#bufferSize() Flowable.bufferSize()} prefetch amount + * and no reference counting behavior.
  • + *
  • {@link #create(int)}: create an empty {@code MulticastProcessor} with + * the given prefetch amount and no reference counting behavior.
  • + *
  • {@link #create(boolean)}: create an empty {@code MulticastProcessor} with + * {@link io.reactivex.Flowable#bufferSize() Flowable.bufferSize()} prefetch amount + * and an optional reference counting behavior.
  • + *
  • {@link #create(int, boolean)}: create an empty {@code MulticastProcessor} with + * the given prefetch amount and an optional reference counting behavior.
  • + *
+ *

+ * When the reference counting behavior is enabled, the {@code MulticastProcessor} cancels its + * upstream when all {@link Subscriber}s have cancelled. Late {@code Subscriber}s will then be + * immediately completed. + *

+ * Because {@code MulticastProcessor} implements the {@link Subscriber} interface, calling + * {@code onSubscribe} is mandatory (Rule 2.12). + * If {@code MulticastProcessor} should run standalone, i.e., without subscribing the {@code MulticastProcessor} to another {@link Publisher}, + * use {@link #start()} or {@link #startUnbounded()} methods to initialize the internal buffer. + * Failing to do so will lead to a {@link NullPointerException} at runtime. + *

+ * Use {@link #offer(Object)} to try and offer/emit items but don't fail if the + * internal buffer is full. + *

+ * A {@code MulticastProcessor} is a {@link Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (Rule 2.13) as + * parameters to {@link #onSubscribe(Subscription)}, {@link #offer(Object)}, {@link #onNext(Object)} and {@link #onError(Throwable)}. + * Such calls will result in a {@link NullPointerException} being thrown and the processor's state is not changed. + *

+ * Since a {@code MulticastProcessor} is a {@link io.reactivex.Flowable}, it supports backpressure. + * The backpressure from the currently subscribed {@link Subscriber}s are coordinated by emitting upstream + * items only if all of those {@code Subscriber}s have requested at least one item. This behavior + * is also called lockstep-mode because even if some {@code Subscriber}s can take any number + * of items, other {@code Subscriber}s requesting less or infrequently will slow down the overall + * throughput of the flow. + *

+ * Calling {@link #onNext(Object)}, {@link #offer(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@link FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + *

+ * This {@code MulticastProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()}. This processor doesn't allow peeking into its buffer. + *

+ * When this {@code MulticastProcessor} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * all previously signaled but not yet consumed items will be still available to {@code Subscriber}s and the respective + * terminal even is only emitted when all previous items have been successfully delivered to {@code Subscriber}s. + * If there are no {@code Subscriber}s, the remaining items will be buffered indefinitely. + *

+ * The {@code MulticastProcessor} does not support clearing its cached events (to appear empty again). + *

+ *
Backpressure:
+ *
The backpressure from the currently subscribed {@code Subscriber}s are coordinated by emitting upstream + * items only if all of those {@code Subscriber}s have requested at least one item. This behavior + * is also called lockstep-mode because even if some {@code Subscriber}s can take any number + * of items, other {@code Subscriber}s requesting less or infrequently will slow down the overall + * throughput of the flow.
+ *
Scheduler:
+ *
{@code MulticastProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Subscriber}s get notified on an arbitrary thread in a serialized fashion.
+ *
+ *

+ * Example: + *


+    MulticastProcessor<Integer> mp = Flowable.range(1, 10)
+    .subscribeWith(MulticastProcessor.create());
+
+    mp.test().assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+
+    // --------------------
+
+    MulticastProcessor<Integer> mp2 = MulticastProcessor.create(4);
+    mp2.start();
+
+    assertTrue(mp2.offer(1));
+    assertTrue(mp2.offer(2));
+    assertTrue(mp2.offer(3));
+    assertTrue(mp2.offer(4));
+
+    assertFalse(mp2.offer(5));
+
+    mp2.onComplete();
+
+    mp2.test().assertResult(1, 2, 3, 4);
+ * 
+ *

History: 2.1.14 - experimental + * @param the input and output value type + * @since 2.2 + */ +@BackpressureSupport(BackpressureKind.FULL) +@SchedulerSupport(SchedulerSupport.NONE) +public final class MulticastProcessor extends FlowableProcessor { + + final AtomicInteger wip; + + final AtomicReference upstream; + + final AtomicReference[]> subscribers; + + final AtomicBoolean once; + + final int bufferSize; + + final int limit; + + final boolean refcount; + + volatile SimpleQueue queue; + + volatile boolean done; + volatile Throwable error; + + int consumed; + + int fusionMode; + + @SuppressWarnings("rawtypes") + static final MulticastSubscription[] EMPTY = new MulticastSubscription[0]; + + @SuppressWarnings("rawtypes") + static final MulticastSubscription[] TERMINATED = new MulticastSubscription[0]; + + /** + * Constructs a fresh instance with the default Flowable.bufferSize() prefetch + * amount and no refCount-behavior. + * @param the input and output value type + * @return the new MulticastProcessor instance + */ + @CheckReturnValue + @NonNull + public static MulticastProcessor create() { + return new MulticastProcessor(bufferSize(), false); + } + + /** + * Constructs a fresh instance with the default Flowable.bufferSize() prefetch + * amount and the optional refCount-behavior. + * @param the input and output value type + * @param refCount if true and if all Subscribers have canceled, the upstream + * is cancelled + * @return the new MulticastProcessor instance + */ + @CheckReturnValue + @NonNull + public static MulticastProcessor create(boolean refCount) { + return new MulticastProcessor(bufferSize(), refCount); + } + + /** + * Constructs a fresh instance with the given prefetch amount and no refCount behavior. + * @param bufferSize the prefetch amount + * @param the input and output value type + * @return the new MulticastProcessor instance + */ + @CheckReturnValue + @NonNull + public static MulticastProcessor create(int bufferSize) { + return new MulticastProcessor(bufferSize, false); + } + + /** + * Constructs a fresh instance with the given prefetch amount and the optional + * refCount-behavior. + * @param bufferSize the prefetch amount + * @param refCount if true and if all Subscribers have canceled, the upstream + * is cancelled + * @param the input and output value type + * @return the new MulticastProcessor instance + */ + @CheckReturnValue + @NonNull + public static MulticastProcessor create(int bufferSize, boolean refCount) { + return new MulticastProcessor(bufferSize, refCount); + } + + /** + * Constructs a fresh instance with the given prefetch amount and the optional + * refCount-behavior. + * @param bufferSize the prefetch amount + * @param refCount if true and if all Subscribers have canceled, the upstream + * is cancelled + */ + @SuppressWarnings("unchecked") + MulticastProcessor(int bufferSize, boolean refCount) { + ObjectHelper.verifyPositive(bufferSize, "bufferSize"); + this.bufferSize = bufferSize; + this.limit = bufferSize - (bufferSize >> 2); + this.wip = new AtomicInteger(); + this.subscribers = new AtomicReference[]>(EMPTY); + this.upstream = new AtomicReference(); + this.refcount = refCount; + this.once = new AtomicBoolean(); + } + + /** + * Initializes this Processor by setting an upstream Subscription that + * ignores request amounts, uses a fixed buffer + * and allows using the onXXX and offer methods + * afterwards. + */ + public void start() { + if (SubscriptionHelper.setOnce(upstream, EmptySubscription.INSTANCE)) { + queue = new SpscArrayQueue(bufferSize); + } + } + + /** + * Initializes this Processor by setting an upstream Subscription that + * ignores request amounts, uses an unbounded buffer + * and allows using the onXXX and offer methods + * afterwards. + */ + public void startUnbounded() { + if (SubscriptionHelper.setOnce(upstream, EmptySubscription.INSTANCE)) { + queue = new SpscLinkedArrayQueue(bufferSize); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(upstream, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription)s; + + int m = qs.requestFusion(QueueSubscription.ANY); + if (m == QueueSubscription.SYNC) { + fusionMode = m; + queue = qs; + done = true; + drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + fusionMode = m; + queue = qs; + + s.request(bufferSize); + return; + } + } + + queue = new SpscArrayQueue(bufferSize); + + s.request(bufferSize); + } + } + + @Override + public void onNext(T t) { + if (once.get()) { + return; + } + if (fusionMode == QueueSubscription.NONE) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + if (!queue.offer(t)) { + SubscriptionHelper.cancel(upstream); + onError(new MissingBackpressureException()); + return; + } + } + drain(); + } + + /** + * Tries to offer an item into the internal queue and returns false + * if the queue is full. + * @param t the item to offer, not null + * @return true if successful, false if the queue is full + */ + public boolean offer(T t) { + if (once.get()) { + return false; + } + ObjectHelper.requireNonNull(t, "offer called with null. Null values are generally not allowed in 2.x operators and sources."); + if (fusionMode == QueueSubscription.NONE) { + if (queue.offer(t)) { + drain(); + return true; + } + } + return false; + } + + @Override + public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (once.compareAndSet(false, true)) { + error = t; + done = true; + drain(); + } else { + RxJavaPlugins.onError(t); + } + } + + @Override + public void onComplete() { + if (once.compareAndSet(false, true)) { + done = true; + drain(); + } + } + + @Override + public boolean hasSubscribers() { + return subscribers.get().length != 0; + } + + @Override + public boolean hasThrowable() { + return once.get() && error != null; + } + + @Override + public boolean hasComplete() { + return once.get() && error == null; + } + + @Override + public Throwable getThrowable() { + return once.get() ? error : null; + } + + @Override + protected void subscribeActual(Subscriber s) { + MulticastSubscription ms = new MulticastSubscription(s, this); + s.onSubscribe(ms); + if (add(ms)) { + if (ms.get() == Long.MIN_VALUE) { + remove(ms); + } else { + drain(); + } + } else { + if (once.get() || !refcount) { + Throwable ex = error; + if (ex != null) { + s.onError(ex); + return; + } + } + s.onComplete(); + } + } + + boolean add(MulticastSubscription inner) { + for (;;) { + MulticastSubscription[] a = subscribers.get(); + if (a == TERMINATED) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + MulticastSubscription[] b = new MulticastSubscription[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(MulticastSubscription inner) { + for (;;) { + MulticastSubscription[] a = subscribers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + break; + } + + if (n == 1) { + if (refcount) { + if (subscribers.compareAndSet(a, TERMINATED)) { + SubscriptionHelper.cancel(upstream); + once.set(true); + break; + } + } else { + if (subscribers.compareAndSet(a, EMPTY)) { + break; + } + } + } else { + MulticastSubscription[] b = new MulticastSubscription[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + if (subscribers.compareAndSet(a, b)) { + break; + } + } + } + } + + @SuppressWarnings("unchecked") + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + AtomicReference[]> subs = subscribers; + int c = consumed; + int lim = limit; + int fm = fusionMode; + + outer: + for (;;) { + + SimpleQueue q = queue; + + if (q != null) { + MulticastSubscription[] as = subs.get(); + int n = as.length; + + if (n != 0) { + long r = -1L; + + for (MulticastSubscription a : as) { + long ra = a.get(); + if (ra >= 0L) { + if (r == -1L) { + r = ra - a.emitted; + } else { + r = Math.min(r, ra - a.emitted); + } + } + } + + while (r > 0L) { + MulticastSubscription[] bs = subs.get(); + + if (bs == TERMINATED) { + q.clear(); + return; + } + + if (as != bs) { + continue outer; + } + + boolean d = done; + + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + SubscriptionHelper.cancel(upstream); + d = true; + v = null; + error = ex; + done = true; + } + boolean empty = v == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + for (MulticastSubscription inner : subs.getAndSet(TERMINATED)) { + inner.onError(ex); + } + } else { + for (MulticastSubscription inner : subs.getAndSet(TERMINATED)) { + inner.onComplete(); + } + } + return; + } + + if (empty) { + break; + } + + for (MulticastSubscription inner : as) { + inner.onNext(v); + } + + r--; + + if (fm != QueueSubscription.SYNC) { + if (++c == lim) { + c = 0; + upstream.get().request(lim); + } + } + } + + if (r == 0) { + MulticastSubscription[] bs = subs.get(); + + if (bs == TERMINATED) { + q.clear(); + return; + } + + if (as != bs) { + continue outer; + } + + if (done && q.isEmpty()) { + Throwable ex = error; + if (ex != null) { + for (MulticastSubscription inner : subs.getAndSet(TERMINATED)) { + inner.onError(ex); + } + } else { + for (MulticastSubscription inner : subs.getAndSet(TERMINATED)) { + inner.onComplete(); + } + } + return; + } + } + } + } + + consumed = c; + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + static final class MulticastSubscription extends AtomicLong implements Subscription { + + private static final long serialVersionUID = -363282618957264509L; + + final Subscriber downstream; + + final MulticastProcessor parent; + + long emitted; + + MulticastSubscription(Subscriber actual, MulticastProcessor parent) { + this.downstream = actual; + this.parent = parent; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + for (;;) { + long r = get(); + if (r == Long.MIN_VALUE || r == Long.MAX_VALUE) { + break; + } + long u = r + n; + if (u < 0L) { + u = Long.MAX_VALUE; + } + if (compareAndSet(r, u)) { + parent.drain(); + break; + } + } + } + } + + @Override + public void cancel() { + if (getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + } + } + + void onNext(T t) { + if (get() != Long.MIN_VALUE) { + emitted++; + downstream.onNext(t); + } + } + + void onError(Throwable t) { + if (get() != Long.MIN_VALUE) { + downstream.onError(t); + } + } + + void onComplete() { + if (get() != Long.MIN_VALUE) { + downstream.onComplete(); + } + } + } +} diff --git a/src/main/java/io/reactivex/processors/PublishProcessor.java b/src/main/java/io/reactivex/processors/PublishProcessor.java new file mode 100755 index 0000000..e1eaeeb --- /dev/null +++ b/src/main/java/io/reactivex/processors/PublishProcessor.java @@ -0,0 +1,404 @@ +/** + * 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.processors; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.annotations.*; +import io.reactivex.exceptions.MissingBackpressureException; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Processor that multicasts all subsequently observed items to its current {@link Subscriber}s. + * + *

+ * + *

+ * This processor does not have a public constructor by design; a new empty instance of this + * {@code PublishProcessor} can be created via the {@link #create()} method. + *

+ * Since a {@code PublishProcessor} is a Reactive Streams {@code Processor} type, + * {@code null}s are not allowed (Rule 2.13) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the processor's state is not changed. + *

+ * {@code PublishProcessor} is a {@link io.reactivex.Flowable} as well as a {@link FlowableProcessor}, + * however, it does not coordinate backpressure between different subscribers and between an + * upstream source and a subscriber. If an upstream item is received via {@link #onNext(Object)}, if + * a subscriber is not ready to receive an item, that subscriber is terminated via a {@link MissingBackpressureException}. + * To avoid this case, use {@link #offer(Object)} and retry sometime later if it returned false. + * The {@code PublishProcessor}'s {@link Subscriber}-side consumes items in an unbounded manner. + *

+ * For a multicasting processor type that also coordinates between the downstream {@code Subscriber}s and the upstream + * source as well, consider using {@link MulticastProcessor}. + *

+ * When this {@code PublishProcessor} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * late {@link Subscriber}s only receive the respective terminal event. + *

+ * Unlike a {@link BehaviorProcessor}, a {@code PublishProcessor} doesn't retain/cache items, therefore, a new + * {@code Subscriber} won't receive any past items. + *

+ * Even though {@code PublishProcessor} implements the {@link Subscriber} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code PublishProcessor} reached its terminal state will result in the + * given {@link Subscription} being canceled immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #offer(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@link FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + * Note that serializing over {@link #offer(Object)} is not supported through {@code toSerialized()} because it is a method + * available on the {@code PublishProcessor} and {@code BehaviorProcessor} classes only. + *

+ * This {@code PublishProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()}. + *

+ *
Backpressure:
+ *
The processor does not coordinate backpressure for its subscribers and implements a weaker {@code onSubscribe} which + * calls requests Long.MAX_VALUE from the incoming Subscriptions. This makes it possible to subscribe the {@code PublishProcessor} + * to multiple sources (note on serialization though) unlike the standard {@code Subscriber} contract. Child subscribers, however, are not overflown but receive an + * {@link IllegalStateException} in case their requested amount is zero.
+ *
Scheduler:
+ *
{@code PublishProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Subscriber}s get notified on the thread the respective {@code onXXX} methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code PublishProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Subscriber}s. During this emission, + * if one or more {@code Subscriber}s cancel their respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Subscriber}s + * cancel at once). + * If there were no {@code Subscriber}s subscribed to this {@code PublishProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ * + * Example usage: + *
 {@code
+
+  PublishProcessor processor = PublishProcessor.create();
+  // subscriber1 will receive all onNext and onComplete events
+  processor.subscribe(subscriber1);
+  processor.onNext("one");
+  processor.onNext("two");
+  // subscriber2 will only receive "three" and onComplete
+  processor.subscribe(subscriber2);
+  processor.onNext("three");
+  processor.onComplete();
+
+  } 
+ * @param  the value type multicasted to Subscribers.
+ * @see MulticastProcessor
+ */
+public final class PublishProcessor extends FlowableProcessor {
+    /** The terminated indicator for the subscribers array. */
+    @SuppressWarnings("rawtypes")
+    static final PublishSubscription[] TERMINATED = new PublishSubscription[0];
+    /** An empty subscribers array to avoid allocating it all the time. */
+    @SuppressWarnings("rawtypes")
+    static final PublishSubscription[] EMPTY = new PublishSubscription[0];
+
+    /** The array of currently subscribed subscribers. */
+    final AtomicReference[]> subscribers;
+
+    /** The error, write before terminating and read after checking subscribers. */
+    Throwable error;
+
+    /**
+     * Constructs a PublishProcessor.
+     * @param  the value type
+     * @return the new PublishProcessor
+     */
+    @CheckReturnValue
+    @NonNull
+    public static  PublishProcessor create() {
+        return new PublishProcessor();
+    }
+
+    /**
+     * Constructs a PublishProcessor.
+     * @since 2.0
+     */
+    @SuppressWarnings("unchecked")
+    PublishProcessor() {
+        subscribers = new AtomicReference[]>(EMPTY);
+    }
+
+    @Override
+    protected void subscribeActual(Subscriber t) {
+        PublishSubscription ps = new PublishSubscription(t, this);
+        t.onSubscribe(ps);
+        if (add(ps)) {
+            // if cancellation happened while a successful add, the remove() didn't work
+            // so we need to do it again
+            if (ps.isCancelled()) {
+                remove(ps);
+            }
+        } else {
+            Throwable ex = error;
+            if (ex != null) {
+                t.onError(ex);
+            } else {
+                t.onComplete();
+            }
+        }
+    }
+
+    /**
+     * Tries to add the given subscriber to the subscribers array atomically
+     * or returns false if this processor has terminated.
+     * @param ps the subscriber to add
+     * @return true if successful, false if this processor has terminated
+     */
+    boolean add(PublishSubscription ps) {
+        for (;;) {
+            PublishSubscription[] a = subscribers.get();
+            if (a == TERMINATED) {
+                return false;
+            }
+
+            int n = a.length;
+            @SuppressWarnings("unchecked")
+            PublishSubscription[] b = new PublishSubscription[n + 1];
+            System.arraycopy(a, 0, b, 0, n);
+            b[n] = ps;
+
+            if (subscribers.compareAndSet(a, b)) {
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Atomically removes the given subscriber if it is subscribed to this processor.
+     * @param ps the subscription wrapping a subscriber to remove
+     */
+    @SuppressWarnings("unchecked")
+    void remove(PublishSubscription ps) {
+        for (;;) {
+            PublishSubscription[] a = subscribers.get();
+            if (a == TERMINATED || a == EMPTY) {
+                return;
+            }
+
+            int n = a.length;
+            int j = -1;
+            for (int i = 0; i < n; i++) {
+                if (a[i] == ps) {
+                    j = i;
+                    break;
+                }
+            }
+
+            if (j < 0) {
+                return;
+            }
+
+            PublishSubscription[] b;
+
+            if (n == 1) {
+                b = EMPTY;
+            } else {
+                b = new PublishSubscription[n - 1];
+                System.arraycopy(a, 0, b, 0, j);
+                System.arraycopy(a, j + 1, b, j, n - j - 1);
+            }
+            if (subscribers.compareAndSet(a, b)) {
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void onSubscribe(Subscription s) {
+        if (subscribers.get() == TERMINATED) {
+            s.cancel();
+            return;
+        }
+        // PublishProcessor doesn't bother with request coordination.
+        s.request(Long.MAX_VALUE);
+    }
+
+    @Override
+    public void onNext(T t) {
+        ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources.");
+        for (PublishSubscription s : subscribers.get()) {
+            s.onNext(t);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void onError(Throwable t) {
+        ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources.");
+        if (subscribers.get() == TERMINATED) {
+            RxJavaPlugins.onError(t);
+            return;
+        }
+        error = t;
+
+        for (PublishSubscription s : subscribers.getAndSet(TERMINATED)) {
+            s.onError(t);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void onComplete() {
+        if (subscribers.get() == TERMINATED) {
+            return;
+        }
+        for (PublishSubscription s : subscribers.getAndSet(TERMINATED)) {
+            s.onComplete();
+        }
+    }
+
+    /**
+     * Tries to emit the item to all currently subscribed Subscribers if all of them
+     * has requested some value, returns false otherwise.
+     * 

+ * This method should be called in a sequential manner just like the onXXX methods + * of the PublishProcessor. + *

+ * Calling with null will terminate the PublishProcessor and a NullPointerException + * is signalled to the Subscribers. + *

History: 2.0.8 - experimental + * @param t the item to emit, not null + * @return true if the item was emitted to all Subscribers + * @since 2.2 + */ + public boolean offer(T t) { + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return true; + } + PublishSubscription[] array = subscribers.get(); + + for (PublishSubscription s : array) { + if (s.isFull()) { + return false; + } + } + + for (PublishSubscription s : array) { + s.onNext(t); + } + return true; + } + + @Override + public boolean hasSubscribers() { + return subscribers.get().length != 0; + } + + @Override + @Nullable + public Throwable getThrowable() { + if (subscribers.get() == TERMINATED) { + return error; + } + return null; + } + + @Override + public boolean hasThrowable() { + return subscribers.get() == TERMINATED && error != null; + } + + @Override + public boolean hasComplete() { + return subscribers.get() == TERMINATED && error == null; + } + + /** + * Wraps the actual subscriber, tracks its requests and makes cancellation + * to remove itself from the current subscribers array. + * + * @param the value type + */ + static final class PublishSubscription extends AtomicLong implements Subscription { + + private static final long serialVersionUID = 3562861878281475070L; + /** The actual subscriber. */ + final Subscriber downstream; + /** The parent processor servicing this subscriber. */ + final PublishProcessor parent; + + /** + * Constructs a PublishSubscriber, wraps the actual subscriber and the state. + * @param actual the actual subscriber + * @param parent the parent PublishProcessor + */ + PublishSubscription(Subscriber actual, PublishProcessor parent) { + this.downstream = actual; + this.parent = parent; + } + + public void onNext(T t) { + long r = get(); + if (r == Long.MIN_VALUE) { + return; + } + if (r != 0L) { + downstream.onNext(t); + BackpressureHelper.producedCancel(this, 1); + } else { + cancel(); + downstream.onError(new MissingBackpressureException("Could not emit value due to lack of requests")); + } + } + + public void onError(Throwable t) { + if (get() != Long.MIN_VALUE) { + downstream.onError(t); + } else { + RxJavaPlugins.onError(t); + } + } + + public void onComplete() { + if (get() != Long.MIN_VALUE) { + downstream.onComplete(); + } + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.addCancel(this, n); + } + } + + @Override + public void cancel() { + if (getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + } + } + + public boolean isCancelled() { + return get() == Long.MIN_VALUE; + } + + boolean isFull() { + return get() == 0L; + } + } +} diff --git a/src/main/java/io/reactivex/processors/ReplayProcessor.java b/src/main/java/io/reactivex/processors/ReplayProcessor.java new file mode 100755 index 0000000..d79b6c7 --- /dev/null +++ b/src/main/java/io/reactivex/processors/ReplayProcessor.java @@ -0,0 +1,1338 @@ +/** + * 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.processors; + +import java.lang.reflect.Array; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.*; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Replays events to Subscribers. + *

+ * The {@code ReplayProcessor} supports the following item retainment strategies: + *

    + *
  • {@link #create()} and {@link #create(int)}: retains and replays all events to current and + * future {@code Subscriber}s. + *

    + * + *

    + * + *

  • + *
  • {@link #createWithSize(int)}: retains at most the given number of items and replays only these + * latest items to new {@code Subscriber}s. + *

    + * + *

  • + *
  • {@link #createWithTime(long, TimeUnit, Scheduler)}: retains items no older than the specified time + * and replays them to new {@code Subscriber}s (which could mean all items age out). + *

    + * + *

  • + *
  • {@link #createWithTimeAndSize(long, TimeUnit, Scheduler, int)}: retains no more than the given number of items + * which are also no older than the specified time and replays them to new {@code Subscriber}s (which could mean all items age out). + *

    + * + *

  • + *
+ *

+ * The {@code ReplayProcessor} can be created in bounded and unbounded mode. It can be bounded by + * size (maximum number of elements retained at most) and/or time (maximum age of elements replayed). + *

+ * Since a {@code ReplayProcessor} is a Reactive Streams {@code Processor}, + * {@code null}s are not allowed (Rule 2.13) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the processor's state is not changed. + *

+ * This {@code ReplayProcessor} respects the individual backpressure behavior of its {@code Subscriber}s but + * does not coordinate their request amounts towards the upstream (because there might not be any) and + * consumes the upstream in an unbounded manner (requesting {@code Long.MAX_VALUE}). + * Note that {@code Subscriber}s receive a continuous sequence of values after they subscribed even + * if an individual item gets delayed due to backpressure. + * Due to concurrency requirements, a size-bounded {@code ReplayProcessor} may hold strong references to more source + * emissions than specified. + *

+ * When this {@code ReplayProcessor} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * late {@link Subscriber}s will receive the retained/cached items first (if any) followed by the respective + * terminal event. If the {@code ReplayProcessor} has a time-bound, the age of the retained/cached items are still considered + * when replaying and thus it may result in no items being emitted before the terminal event. + *

+ * Once an {@code Subscriber} has subscribed, it will receive items continuously from that point on. Bounds only affect how + * many past items a new {@code Subscriber} will receive before it catches up with the live event feed. + *

+ * Even though {@code ReplayProcessor} implements the {@code Subscriber} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code ReplayProcessor} reached its terminal state will result in the + * given {@code Subscription} being canceled immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + *

+ * This {@code ReplayProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()} as well as means to read the retained/cached items + * in a non-blocking and thread-safe manner via {@link #hasValue()}, {@link #getValue()}, + * {@link #getValues()} or {@link #getValues(Object[])}. + *

+ * Note that due to concurrency requirements, a size- and time-bounded {@code ReplayProcessor} may hold strong references to more + * source emissions than specified while it isn't terminated yet. Use the {@link #cleanupBuffer()} to allow + * such inaccessible items to be cleaned up by GC once no consumer references them anymore. + *

+ *
Backpressure:
+ *
This {@code ReplayProcessor} respects the individual backpressure behavior of its {@code Subscriber}s but + * does not coordinate their request amounts towards the upstream (because there might not be any) and + * consumes the upstream in an unbounded manner (requesting {@code Long.MAX_VALUE}). + * Note that {@code Subscriber}s receive a continuous sequence of values after they subscribed even + * if an individual item gets delayed due to backpressure.
+ *
Scheduler:
+ *
{@code ReplayProcessor} does not operate by default on a particular {@link Scheduler} and + * the {@code Subscriber}s get notified on the thread the respective {@code onXXX} methods were invoked. + * Time-bound {@code ReplayProcessor}s use the given {@code Scheduler} in their {@code create} methods + * as time source to timestamp of items received for the age checks.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code ReplayProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Subscriber}s. During this emission, + * if one or more {@code Subscriber}s cancel their respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Subscriber}s + * cancel at once). + * If there were no {@code Subscriber}s subscribed to this {@code ReplayProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *

 {@code
+
+  ReplayProcessor processor = new ReplayProcessor();
+  processor.onNext("one");
+  processor.onNext("two");
+  processor.onNext("three");
+  processor.onComplete();
+
+  // both of the following will get the onNext/onComplete calls from above
+  processor.subscribe(subscriber1);
+  processor.subscribe(subscriber2);
+
+  } 
+ *
+ * @param  the value type
+ */
+public final class ReplayProcessor extends FlowableProcessor {
+    /** An empty array to avoid allocation in getValues(). */
+    private static final Object[] EMPTY_ARRAY = new Object[0];
+
+    final ReplayBuffer buffer;
+
+    boolean done;
+
+    final AtomicReference[]> subscribers;
+
+    @SuppressWarnings("rawtypes")
+    static final ReplaySubscription[] EMPTY = new ReplaySubscription[0];
+
+    @SuppressWarnings("rawtypes")
+    static final ReplaySubscription[] TERMINATED = new ReplaySubscription[0];
+
+    /**
+     * Creates an unbounded ReplayProcessor.
+     * 

+ * The internal buffer is backed by an {@link ArrayList} and starts with an initial capacity of 16. Once the + * number of items reaches this capacity, it will grow as necessary (usually by 50%). However, as the + * number of items grows, this causes frequent array reallocation and copying, and may hurt performance + * and latency. This can be avoided with the {@link #create(int)} overload which takes an initial capacity + * parameter and can be tuned to reduce the array reallocation frequency as needed. + * + * @param + * the type of items observed and emitted by the ReplayProcessor + * @return the created ReplayProcessor + */ + @CheckReturnValue + @NonNull + public static ReplayProcessor create() { + return new ReplayProcessor(new UnboundedReplayBuffer(16)); + } + + /** + * Creates an unbounded ReplayProcessor with the specified initial buffer capacity. + *

+ * Use this method to avoid excessive array reallocation while the internal buffer grows to accommodate new + * items. For example, if you know that the buffer will hold 32k items, you can ask the + * {@code ReplayProcessor} to preallocate its internal array with a capacity to hold that many items. Once + * the items start to arrive, the internal array won't need to grow, creating less garbage and no overhead + * due to frequent array-copying. + * + * @param + * the type of items observed and emitted by this type of processor + * @param capacityHint + * the initial buffer capacity + * @return the created processor + */ + @CheckReturnValue + @NonNull + public static ReplayProcessor create(int capacityHint) { + return new ReplayProcessor(new UnboundedReplayBuffer(capacityHint)); + } + + /** + * Creates a size-bounded ReplayProcessor. + *

+ * In this setting, the {@code ReplayProcessor} holds at most {@code size} items in its internal buffer and + * discards the oldest item. + *

+ * When {@code Subscriber}s subscribe to a terminated {@code ReplayProcessor}, they are guaranteed to see at most + * {@code size} {@code onNext} events followed by a termination event. + *

+ * If a {@code Subscriber} subscribes while the {@code ReplayProcessor} is active, it will observe all items in the + * buffer at that point in time and each item observed afterwards, even if the buffer evicts items due to + * the size constraint in the mean time. In other words, once a {@code Subscriber} subscribes, it will receive items + * without gaps in the sequence. + * + * @param + * the type of items observed and emitted by this type of processor + * @param maxSize + * the maximum number of buffered items + * @return the created processor + */ + @CheckReturnValue + @NonNull + public static ReplayProcessor createWithSize(int maxSize) { + return new ReplayProcessor(new SizeBoundReplayBuffer(maxSize)); + } + + /** + * Creates an unbounded ReplayProcessor with the bounded-implementation for testing purposes. + *

+ * This variant behaves like the regular unbounded {@code ReplayProcessor} created via {@link #create()} but + * uses the structures of the bounded-implementation. This is by no means intended for the replacement of + * the original, array-backed and unbounded {@code ReplayProcessor} due to the additional overhead of the + * linked-list based internal buffer. The sole purpose is to allow testing and reasoning about the behavior + * of the bounded implementations without the interference of the eviction policies. + * + * @param + * the type of items observed and emitted by this type of processor + * @return the created processor + */ + /* test */ static ReplayProcessor createUnbounded() { + return new ReplayProcessor(new SizeBoundReplayBuffer(Integer.MAX_VALUE)); + } + + /** + * Creates a time-bounded ReplayProcessor. + *

+ * In this setting, the {@code ReplayProcessor} internally tags each observed item with a timestamp value + * supplied by the {@link Scheduler} and keeps only those whose age is less than the supplied time value + * converted to milliseconds. For example, an item arrives at T=0 and the max age is set to 5; at T>=5 + * this first item is then evicted by any subsequent item or termination event, leaving the buffer empty. + *

+ * Once the processor is terminated, {@code Subscriber}s subscribing to it will receive items that remained in the + * buffer after the terminal event, regardless of their age. + *

+ * If a {@code Subscriber} subscribes while the {@code ReplayProcessor} is active, it will observe only those items + * from within the buffer that have an age less than the specified time, and each item observed thereafter, + * even if the buffer evicts items due to the time constraint in the mean time. In other words, once a + * {@code Subscriber} subscribes, it observes items without gaps in the sequence except for any outdated items at the + * beginning of the sequence. + *

+ * Note that terminal notifications ({@code onError} and {@code onComplete}) trigger eviction as well. For + * example, with a max age of 5, the first item is observed at T=0, then an {@code onComplete} notification + * arrives at T=10. If a {@code Subscriber} subscribes at T=11, it will find an empty {@code ReplayProcessor} with just + * an {@code onComplete} notification. + * + * @param + * the type of items observed and emitted by this type of processor + * @param maxAge + * the maximum age of the contained items + * @param unit + * the time unit of {@code time} + * @param scheduler + * the {@link Scheduler} that provides the current time + * @return the created processor + */ + @CheckReturnValue + @NonNull + public static ReplayProcessor createWithTime(long maxAge, TimeUnit unit, Scheduler scheduler) { + return new ReplayProcessor(new SizeAndTimeBoundReplayBuffer(Integer.MAX_VALUE, maxAge, unit, scheduler)); + } + + /** + * Creates a time- and size-bounded ReplayProcessor. + *

+ * In this setting, the {@code ReplayProcessor} internally tags each received item with a timestamp value + * supplied by the {@link Scheduler} and holds at most {@code size} items in its internal buffer. It evicts + * items from the start of the buffer if their age becomes less-than or equal to the supplied age in + * milliseconds or the buffer reaches its {@code size} limit. + *

+ * When {@code Subscriber}s subscribe to a terminated {@code ReplayProcessor}, they observe the items that remained in + * the buffer after the terminal notification, regardless of their age, but at most {@code size} items. + *

+ * If a {@code Subscriber} subscribes while the {@code ReplayProcessor} is active, it will observe only those items + * from within the buffer that have age less than the specified time and each subsequent item, even if the + * buffer evicts items due to the time constraint in the mean time. In other words, once a {@code Subscriber} + * subscribes, it observes items without gaps in the sequence except for the outdated items at the beginning + * of the sequence. + *

+ * Note that terminal notifications ({@code onError} and {@code onComplete}) trigger eviction as well. For + * example, with a max age of 5, the first item is observed at T=0, then an {@code onComplete} notification + * arrives at T=10. If a {@code Subscriber} subscribes at T=11, it will find an empty {@code ReplayProcessor} with just + * an {@code onComplete} notification. + * + * @param + * the type of items observed and emitted by this type of processor + * @param maxAge + * the maximum age of the contained items + * @param unit + * the time unit of {@code time} + * @param maxSize + * the maximum number of buffered items + * @param scheduler + * the {@link Scheduler} that provides the current time + * @return the created processor + */ + @CheckReturnValue + @NonNull + public static ReplayProcessor createWithTimeAndSize(long maxAge, TimeUnit unit, Scheduler scheduler, int maxSize) { + return new ReplayProcessor(new SizeAndTimeBoundReplayBuffer(maxSize, maxAge, unit, scheduler)); + } + + /** + * Constructs a ReplayProcessor with the given custom ReplayBuffer instance. + * @param buffer the ReplayBuffer instance, not null (not verified) + */ + @SuppressWarnings("unchecked") + ReplayProcessor(ReplayBuffer buffer) { + this.buffer = buffer; + this.subscribers = new AtomicReference[]>(EMPTY); + } + + @Override + protected void subscribeActual(Subscriber s) { + ReplaySubscription rs = new ReplaySubscription(s, this); + s.onSubscribe(rs); + + if (add(rs)) { + if (rs.cancelled) { + remove(rs); + return; + } + } + buffer.replay(rs); + } + + @Override + public void onSubscribe(Subscription s) { + if (done) { + s.cancel(); + return; + } + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + + if (done) { + return; + } + + ReplayBuffer b = buffer; + b.next(t); + + for (ReplaySubscription rs : subscribers.get()) { + b.replay(rs); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + + ReplayBuffer b = buffer; + b.error(t); + + for (ReplaySubscription rs : subscribers.getAndSet(TERMINATED)) { + b.replay(rs); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + ReplayBuffer b = buffer; + + b.complete(); + + for (ReplaySubscription rs : subscribers.getAndSet(TERMINATED)) { + b.replay(rs); + } + } + + @Override + public boolean hasSubscribers() { + return subscribers.get().length != 0; + } + + /* test */ int subscriberCount() { + return subscribers.get().length; + } + + @Override + @Nullable + public Throwable getThrowable() { + ReplayBuffer b = buffer; + if (b.isDone()) { + return b.getError(); + } + return null; + } + + /** + * Makes sure the item cached by the head node in a bounded + * ReplayProcessor is released (as it is never part of a replay). + *

+ * By default, live bounded buffers will remember one item before + * the currently receivable one to ensure subscribers can always + * receive a continuous sequence of items. A terminated ReplayProcessor + * automatically releases this inaccessible item. + *

+ * The method must be called sequentially, similar to the standard + * {@code onXXX} methods. + *

History: 2.1.11 - experimental + * @since 2.2 + */ + public void cleanupBuffer() { + buffer.trimHead(); + } + + /** + * Returns the latest value this processor has or null if no such value exists. + *

The method is thread-safe. + * @return the latest value this processor currently has or null if no such value exists + */ + public T getValue() { + return buffer.getValue(); + } + + /** + * Returns an Object array containing snapshot all values of this processor. + *

The method is thread-safe. + * @return the array containing the snapshot of all values of this processor + */ + public Object[] getValues() { + @SuppressWarnings("unchecked") + T[] a = (T[])EMPTY_ARRAY; + T[] b = getValues(a); + if (b == EMPTY_ARRAY) { + return new Object[0]; + } + return b; + + } + + /** + * Returns a typed array containing a snapshot of all values of this processor. + *

The method follows the conventions of Collection.toArray by setting the array element + * after the last value to null (if the capacity permits). + *

The method is thread-safe. + * @param array the target array to copy values into if it fits + * @return the given array if the values fit into it or a new array containing all values + */ + public T[] getValues(T[] array) { + return buffer.getValues(array); + } + + @Override + public boolean hasComplete() { + ReplayBuffer b = buffer; + return b.isDone() && b.getError() == null; + } + + @Override + public boolean hasThrowable() { + ReplayBuffer b = buffer; + return b.isDone() && b.getError() != null; + } + + /** + * Returns true if this processor has any value. + *

The method is thread-safe. + * @return true if the processor has any value + */ + public boolean hasValue() { + return buffer.size() != 0; // NOPMD + } + + /* test*/ int size() { + return buffer.size(); + } + + boolean add(ReplaySubscription rs) { + for (;;) { + ReplaySubscription[] a = subscribers.get(); + if (a == TERMINATED) { + return false; + } + int len = a.length; + @SuppressWarnings("unchecked") + ReplaySubscription[] b = new ReplaySubscription[len + 1]; + System.arraycopy(a, 0, b, 0, len); + b[len] = rs; + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(ReplaySubscription rs) { + for (;;) { + ReplaySubscription[] a = subscribers.get(); + if (a == TERMINATED || a == EMPTY) { + return; + } + int len = a.length; + int j = -1; + for (int i = 0; i < len; i++) { + if (a[i] == rs) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + ReplaySubscription[] b; + if (len == 1) { + b = EMPTY; + } else { + b = new ReplaySubscription[len - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, len - j - 1); + } + if (subscribers.compareAndSet(a, b)) { + return; + } + } + } + + /** + * Abstraction over a buffer that receives events and replays them to + * individual Subscribers. + * + * @param the value type + */ + interface ReplayBuffer { + + void next(T value); + + void error(Throwable ex); + + void complete(); + + void replay(ReplaySubscription rs); + + int size(); + + @Nullable + T getValue(); + + T[] getValues(T[] array); + + boolean isDone(); + + Throwable getError(); + + /** + * Make sure an old inaccessible head value is released + * in a bounded buffer. + */ + void trimHead(); + } + + static final class ReplaySubscription extends AtomicInteger implements Subscription { + + private static final long serialVersionUID = 466549804534799122L; + final Subscriber downstream; + final ReplayProcessor state; + + Object index; + + final AtomicLong requested; + + volatile boolean cancelled; + + long emitted; + + ReplaySubscription(Subscriber actual, ReplayProcessor state) { + this.downstream = actual; + this.state = state; + this.requested = new AtomicLong(); + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + state.buffer.replay(this); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + state.remove(this); + } + } + } + + static final class UnboundedReplayBuffer + implements ReplayBuffer { + + final List buffer; + + Throwable error; + volatile boolean done; + + volatile int size; + + UnboundedReplayBuffer(int capacityHint) { + this.buffer = new ArrayList(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); + } + + @Override + public void next(T value) { + buffer.add(value); + size++; + } + + @Override + public void error(Throwable ex) { + error = ex; + done = true; + } + + @Override + public void complete() { + done = true; + } + + @Override + public void trimHead() { + // not applicable for an unbounded buffer + } + + @Override + @Nullable + public T getValue() { + int s = size; + if (s == 0) { + return null; + } + return buffer.get(s - 1); + } + + @Override + @SuppressWarnings("unchecked") + public T[] getValues(T[] array) { + int s = size; + if (s == 0) { + if (array.length != 0) { + array[0] = null; + } + return array; + } + List b = buffer; + + if (array.length < s) { + array = (T[])Array.newInstance(array.getClass().getComponentType(), s); + } + for (int i = 0; i < s; i++) { + array[i] = b.get(i); + } + if (array.length > s) { + array[s] = null; + } + + return array; + } + + @Override + public void replay(ReplaySubscription rs) { + if (rs.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final List b = buffer; + final Subscriber a = rs.downstream; + + Integer indexObject = (Integer)rs.index; + int index; + if (indexObject != null) { + index = indexObject; + } else { + index = 0; + rs.index = 0; + } + long e = rs.emitted; + + for (;;) { + + long r = rs.requested.get(); + + while (e != r) { + if (rs.cancelled) { + rs.index = null; + return; + } + + boolean d = done; + int s = size; + + if (d && index == s) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; + } + + if (index == s) { + break; + } + + a.onNext(b.get(index)); + + index++; + e++; + } + + if (e == r) { + if (rs.cancelled) { + rs.index = null; + return; + } + + boolean d = done; + int s = size; + + if (d && index == s) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; + } + } + + rs.index = index; + rs.emitted = e; + missed = rs.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isDone() { + return done; + } + + @Override + public Throwable getError() { + return error; + } + } + + static final class Node extends AtomicReference> { + + private static final long serialVersionUID = 6404226426336033100L; + + final T value; + + Node(T value) { + this.value = value; + } + } + + static final class TimedNode extends AtomicReference> { + + private static final long serialVersionUID = 6404226426336033100L; + + final T value; + final long time; + + TimedNode(T value, long time) { + this.value = value; + this.time = time; + } + } + + static final class SizeBoundReplayBuffer + implements ReplayBuffer { + + final int maxSize; + int size; + + volatile Node head; + + Node tail; + + Throwable error; + volatile boolean done; + + SizeBoundReplayBuffer(int maxSize) { + this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize"); + Node h = new Node(null); + this.tail = h; + this.head = h; + } + + void trim() { + if (size > maxSize) { + size--; + Node h = head; + head = h.get(); + } + } + + @Override + public void next(T value) { + Node n = new Node(value); + Node t = tail; + + tail = n; + size++; + t.set(n); // releases both the tail and size + + trim(); + } + + @Override + public void error(Throwable ex) { + error = ex; + trimHead(); + done = true; + } + + @Override + public void complete() { + trimHead(); + done = true; + } + + @Override + public void trimHead() { + if (head.value != null) { + Node n = new Node(null); + n.lazySet(head.get()); + head = n; + } + } + + @Override + public boolean isDone() { + return done; + } + + @Override + public Throwable getError() { + return error; + } + + @Override + public T getValue() { + Node h = head; + for (;;) { + Node n = h.get(); + if (n == null) { + return h.value; + } + h = n; + } + } + + @Override + @SuppressWarnings("unchecked") + public T[] getValues(T[] array) { + int s = 0; + Node h = head; + Node h0 = h; + for (;;) { + Node next = h0.get(); + if (next == null) { + break; + } + s++; + h0 = next; + } + if (array.length < s) { + array = (T[])Array.newInstance(array.getClass().getComponentType(), s); + } + + for (int j = 0; j < s; j++) { + h = h.get(); + array[j] = h.value; + } + + if (array.length > s) { + array[s] = null; + } + return array; + } + + @Override + @SuppressWarnings("unchecked") + public void replay(ReplaySubscription rs) { + if (rs.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final Subscriber a = rs.downstream; + + Node index = (Node)rs.index; + if (index == null) { + index = head; + } + + long e = rs.emitted; + + for (;;) { + + long r = rs.requested.get(); + + while (e != r) { + if (rs.cancelled) { + rs.index = null; + return; + } + + boolean d = done; + Node next = index.get(); + boolean empty = next == null; + + if (d && empty) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; + } + + if (empty) { + break; + } + + a.onNext(next.value); + e++; + index = next; + } + + if (e == r) { + if (rs.cancelled) { + rs.index = null; + return; + } + + boolean d = done; + + if (d && index.get() == null) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; + } + } + + rs.index = index; + rs.emitted = e; + + missed = rs.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public int size() { + int s = 0; + Node h = head; + while (s != Integer.MAX_VALUE) { + Node next = h.get(); + if (next == null) { + break; + } + s++; + h = next; + } + + return s; + } + } + + static final class SizeAndTimeBoundReplayBuffer + implements ReplayBuffer { + + final int maxSize; + final long maxAge; + final TimeUnit unit; + final Scheduler scheduler; + int size; + + volatile TimedNode head; + + TimedNode tail; + + Throwable error; + volatile boolean done; + + SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) { + this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize"); + this.maxAge = ObjectHelper.verifyPositive(maxAge, "maxAge"); + this.unit = ObjectHelper.requireNonNull(unit, "unit is null"); + this.scheduler = ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + TimedNode h = new TimedNode(null, 0L); + this.tail = h; + this.head = h; + } + + void trim() { + if (size > maxSize) { + size--; + TimedNode h = head; + head = h.get(); + } + long limit = scheduler.now(unit) - maxAge; + + TimedNode h = head; + + for (;;) { + if (size <= 1) { + head = h; + break; + } + TimedNode next = h.get(); + if (next == null) { + head = h; + break; + } + + if (next.time > limit) { + head = h; + break; + } + + h = next; + size--; + } + + } + + void trimFinal() { + long limit = scheduler.now(unit) - maxAge; + + TimedNode h = head; + + for (;;) { + TimedNode next = h.get(); + if (next == null) { + if (h.value != null) { + head = new TimedNode(null, 0L); + } else { + head = h; + } + break; + } + + if (next.time > limit) { + if (h.value != null) { + TimedNode n = new TimedNode(null, 0L); + n.lazySet(h.get()); + head = n; + } else { + head = h; + } + break; + } + + h = next; + } + } + + @Override + public void trimHead() { + if (head.value != null) { + TimedNode n = new TimedNode(null, 0L); + n.lazySet(head.get()); + head = n; + } + } + + @Override + public void next(T value) { + TimedNode n = new TimedNode(value, scheduler.now(unit)); + TimedNode t = tail; + + tail = n; + size++; + t.set(n); // releases both the tail and size + + trim(); + } + + @Override + public void error(Throwable ex) { + trimFinal(); + error = ex; + done = true; + } + + @Override + public void complete() { + trimFinal(); + done = true; + } + + @Override + @Nullable + public T getValue() { + TimedNode h = head; + + for (;;) { + TimedNode next = h.get(); + if (next == null) { + break; + } + h = next; + } + + long limit = scheduler.now(unit) - maxAge; + if (h.time < limit) { + return null; + } + + return h.value; + } + + @Override + @SuppressWarnings("unchecked") + public T[] getValues(T[] array) { + TimedNode h = getHead(); + int s = size(h); + + if (s == 0) { + if (array.length != 0) { + array[0] = null; + } + } else { + if (array.length < s) { + array = (T[])Array.newInstance(array.getClass().getComponentType(), s); + } + + int i = 0; + while (i != s) { + TimedNode next = h.get(); + array[i] = next.value; + i++; + h = next; + } + if (array.length > s) { + array[s] = null; + } + } + + return array; + } + + TimedNode getHead() { + TimedNode index = head; + // skip old entries + long limit = scheduler.now(unit) - maxAge; + TimedNode next = index.get(); + while (next != null) { + long ts = next.time; + if (ts > limit) { + break; + } + index = next; + next = index.get(); + } + return index; + } + + @Override + @SuppressWarnings("unchecked") + public void replay(ReplaySubscription rs) { + if (rs.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final Subscriber a = rs.downstream; + + TimedNode index = (TimedNode)rs.index; + if (index == null) { + index = getHead(); + } + + long e = rs.emitted; + + for (;;) { + + long r = rs.requested.get(); + + while (e != r) { + if (rs.cancelled) { + rs.index = null; + return; + } + + boolean d = done; + TimedNode next = index.get(); + boolean empty = next == null; + + if (d && empty) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; + } + + if (empty) { + break; + } + + a.onNext(next.value); + e++; + index = next; + } + + if (e == r) { + if (rs.cancelled) { + rs.index = null; + return; + } + + boolean d = done; + + if (d && index.get() == null) { + rs.index = null; + rs.cancelled = true; + Throwable ex = error; + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; + } + } + + rs.index = index; + rs.emitted = e; + + missed = rs.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public int size() { + return size(getHead()); + } + + int size(TimedNode h) { + int s = 0; + while (s != Integer.MAX_VALUE) { + TimedNode next = h.get(); + if (next == null) { + break; + } + s++; + h = next; + } + + return s; + } + + @Override + public Throwable getError() { + return error; + } + + @Override + public boolean isDone() { + return done; + } + } +} diff --git a/src/main/java/io/reactivex/processors/SerializedProcessor.java b/src/main/java/io/reactivex/processors/SerializedProcessor.java new file mode 100755 index 0000000..8c0a37e --- /dev/null +++ b/src/main/java/io/reactivex/processors/SerializedProcessor.java @@ -0,0 +1,200 @@ +/** + * 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.processors; + +import io.reactivex.annotations.Nullable; +import org.reactivestreams.*; + +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Serializes calls to the Subscriber methods. + *

All other Publisher and Subject methods are thread-safe by design. + * + * @param the item value type + */ +/* public */ final class SerializedProcessor extends FlowableProcessor { + /** The actual subscriber to serialize Subscriber calls to. */ + final FlowableProcessor actual; + /** Indicates an emission is going on, guarded by this. */ + boolean emitting; + /** If not null, it holds the missed NotificationLite events. */ + AppendOnlyLinkedArrayList queue; + /** Indicates a terminal event has been received and all further events will be dropped. */ + volatile boolean done; + + /** + * Constructor that wraps an actual subject. + * @param actual the subject wrapped + */ + SerializedProcessor(final FlowableProcessor actual) { + this.actual = actual; + } + + @Override + protected void subscribeActual(Subscriber s) { + actual.subscribe(s); + } + + @Override + public void onSubscribe(Subscription s) { + boolean cancel; + if (!done) { + synchronized (this) { + if (done) { + cancel = true; + } else { + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.add(NotificationLite.subscription(s)); + return; + } + emitting = true; + cancel = false; + } + } + } else { + cancel = true; + } + if (cancel) { + s.cancel(); + } else { + actual.onSubscribe(s); + emitLoop(); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + synchronized (this) { + if (done) { + return; + } + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.add(NotificationLite.next(t)); + return; + } + emitting = true; + } + actual.onNext(t); + emitLoop(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + boolean reportError; + synchronized (this) { + if (done) { + reportError = true; + } else { + done = true; + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.setFirst(NotificationLite.error(t)); + return; + } + reportError = false; + emitting = true; + } + } + if (reportError) { + RxJavaPlugins.onError(t); + return; + } + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + synchronized (this) { + if (done) { + return; + } + done = true; + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.add(NotificationLite.complete()); + return; + } + emitting = true; + } + actual.onComplete(); + } + + /** Loops until all notifications in the queue has been processed. */ + void emitLoop() { + for (;;) { + AppendOnlyLinkedArrayList q; + synchronized (this) { + q = queue; + if (q == null) { + emitting = false; + return; + } + queue = null; + } + + q.accept(actual); + } + } + + @Override + public boolean hasSubscribers() { + return actual.hasSubscribers(); + } + + @Override + public boolean hasThrowable() { + return actual.hasThrowable(); + } + + @Override + @Nullable + public Throwable getThrowable() { + return actual.getThrowable(); + } + + @Override + public boolean hasComplete() { + return actual.hasComplete(); + } +} diff --git a/src/main/java/io/reactivex/processors/UnicastProcessor.java b/src/main/java/io/reactivex/processors/UnicastProcessor.java new file mode 100755 index 0000000..68e15fb --- /dev/null +++ b/src/main/java/io/reactivex/processors/UnicastProcessor.java @@ -0,0 +1,585 @@ +/** + * 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.processors; + +import io.reactivex.annotations.CheckReturnValue; +import java.util.concurrent.atomic.*; + +import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; +import org.reactivestreams.*; + +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.internal.util.BackpressureHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * A {@link FlowableProcessor} variant that queues up events until a single {@link Subscriber} subscribes to it, replays + * those events to it until the {@code Subscriber} catches up and then switches to relaying events live to + * this single {@code Subscriber} until this {@code UnicastProcessor} terminates or the {@code Subscriber} cancels + * its subscription. + *

+ * + *

+ * This processor does not have a public constructor by design; a new empty instance of this + * {@code UnicastProcessor} can be created via the following {@code create} methods that + * allow specifying the retention policy for items: + *

    + *
  • {@link #create()} - creates an empty, unbounded {@code UnicastProcessor} that + * caches all items and the terminal event it receives.
  • + *
  • {@link #create(int)} - creates an empty, unbounded {@code UnicastProcessor} + * with a hint about how many total items one expects to retain.
  • + *
  • {@link #create(boolean)} - creates an empty, unbounded {@code UnicastProcessor} that + * optionally delays an error it receives and replays it after the regular items have been emitted.
  • + *
  • {@link #create(int, Runnable)} - creates an empty, unbounded {@code UnicastProcessor} + * with a hint about how many total items one expects to retain and a callback that will be + * called exactly once when the {@code UnicastProcessor} gets terminated or the single {@code Subscriber} cancels.
  • + *
  • {@link #create(int, Runnable, boolean)} - creates an empty, unbounded {@code UnicastProcessor} + * with a hint about how many total items one expects to retain and a callback that will be + * called exactly once when the {@code UnicastProcessor} gets terminated or the single {@code Subscriber} cancels + * and optionally delays an error it receives and replays it after the regular items have been emitted.
  • + *
+ *

+ * If more than one {@code Subscriber} attempts to subscribe to this Processor, they + * will receive an {@link IllegalStateException} if this {@link UnicastProcessor} hasn't terminated yet, + * or the Subscribers receive the terminal event (error or completion) if this + * Processor has terminated. + *

+ * The {@code UnicastProcessor} buffers notifications and replays them to the single {@code Subscriber} as requested, + * for which it holds upstream items an unbounded internal buffer until they can be emitted. + *

+ * Since a {@code UnicastProcessor} is a Reactive Streams {@code Processor}, + * {@code null}s are not allowed (Rule 2.13) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the processor's state is not changed. + *

+ * Since a {@code UnicastProcessor} is a {@link io.reactivex.Flowable} as well as a {@link FlowableProcessor}, it + * honors the downstream backpressure but consumes an upstream source in an unbounded manner (requesting {@code Long.MAX_VALUE}). + *

+ * When this {@code UnicastProcessor} is terminated via {@link #onError(Throwable)} the current or late single {@code Subscriber} + * may receive the {@code Throwable} before any available items could be emitted. To make sure an {@code onError} event is delivered + * to the {@code Subscriber} after the normal items, create a {@code UnicastProcessor} with the {@link #create(boolean)} or + * {@link #create(int, Runnable, boolean)} factory methods. + *

+ * Even though {@code UnicastProcessor} implements the {@code Subscriber} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the processor is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code UnicastProcessor} reached its terminal state will result in the + * given {@code Subscription} being canceled immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@link FlowableProcessor}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Subscriber} + * consuming this processor also wants to call {@link #onNext(Object)} on this processor recursively). + *

+ * This {@code UnicastProcessor} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasSubscribers()}. + *

+ *
Backpressure:
+ *
{@code UnicastProcessor} honors the downstream backpressure but consumes an upstream source + * (if any) in an unbounded manner (requesting {@code Long.MAX_VALUE}).
+ *
Scheduler:
+ *
{@code UnicastProcessor} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the single {@code Subscriber} gets notified on the thread the respective {@code onXXX} methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code UnicastProcessor} enters into a terminal state + * and emits the same {@code Throwable} instance to the current single {@code Subscriber}. During this emission, + * if the single {@code Subscriber}s cancels its respective {@code Subscription}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)}. + * If there were no {@code Subscriber}s subscribed to this {@code UnicastProcessor} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *


+ * UnicastProcessor<Integer> processor = UnicastProcessor.create();
+ *
+ * TestSubscriber<Integer> ts1 = processor.test();
+ *
+ * // fresh UnicastProcessors are empty
+ * ts1.assertEmpty();
+ *
+ * TestSubscriber<Integer> ts2 = processor.test();
+ *
+ * // A UnicastProcessor only allows one Subscriber during its lifetime
+ * ts2.assertFailure(IllegalStateException.class);
+ *
+ * processor.onNext(1);
+ * ts1.assertValue(1);
+ *
+ * processor.onNext(2);
+ * ts1.assertValues(1, 2);
+ *
+ * processor.onComplete();
+ * ts1.assertResult(1, 2);
+ *
+ * // ----------------------------------------------------
+ *
+ * UnicastProcessor<Integer> processor2 = UnicastProcessor.create();
+ *
+ * // a UnicastProcessor caches events until its single Subscriber subscribes
+ * processor2.onNext(1);
+ * processor2.onNext(2);
+ * processor2.onComplete();
+ *
+ * TestSubscriber<Integer> ts3 = processor2.test();
+ *
+ * // the cached events are emitted in order
+ * ts3.assertResult(1, 2);
+ * 
+ * + * @param the value type received and emitted by this Processor subclass + * @since 2.0 + */ +public final class UnicastProcessor extends FlowableProcessor { + + final SpscLinkedArrayQueue queue; + + final AtomicReference onTerminate; + + final boolean delayError; + + volatile boolean done; + + Throwable error; + + final AtomicReference> downstream; + + volatile boolean cancelled; + + final AtomicBoolean once; + + final BasicIntQueueSubscription wip; + + final AtomicLong requested; + + boolean enableOperatorFusion; + + /** + * Creates an UnicastSubject with an internal buffer capacity hint 16. + * @param the value type + * @return an UnicastSubject instance + */ + @CheckReturnValue + @NonNull + public static UnicastProcessor create() { + return new UnicastProcessor(bufferSize()); + } + + /** + * Creates an UnicastProcessor with the given internal buffer capacity hint. + * @param the value type + * @param capacityHint the hint to size the internal unbounded buffer + * @return an UnicastProcessor instance + */ + @CheckReturnValue + @NonNull + public static UnicastProcessor create(int capacityHint) { + return new UnicastProcessor(capacityHint); + } + + /** + * Creates an UnicastProcessor with default internal buffer capacity hint and delay error flag. + *

History: 2.0.8 - experimental + * @param the value type + * @param delayError deliver pending onNext events before onError + * @return an UnicastProcessor instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public static UnicastProcessor create(boolean delayError) { + return new UnicastProcessor(bufferSize(), null, delayError); + } + + /** + * Creates an UnicastProcessor with the given internal buffer capacity hint and a callback for + * the case when the single Subscriber cancels its subscription. + * + *

The callback, if not null, is called exactly once and + * non-overlapped with any active replay. + * + * @param the value type + * @param capacityHint the hint to size the internal unbounded buffer + * @param onCancelled the non null callback + * @return an UnicastProcessor instance + */ + @CheckReturnValue + @NonNull + public static UnicastProcessor create(int capacityHint, Runnable onCancelled) { + ObjectHelper.requireNonNull(onCancelled, "onTerminate"); + return new UnicastProcessor(capacityHint, onCancelled); + } + + /** + * Creates an UnicastProcessor with the given internal buffer capacity hint, delay error flag and a callback for + * the case when the single Subscriber cancels its subscription. + * + *

The callback, if not null, is called exactly once and + * non-overlapped with any active replay. + *

History: 2.0.8 - experimental + * @param the value type + * @param capacityHint the hint to size the internal unbounded buffer + * @param onCancelled the non null callback + * @param delayError deliver pending onNext events before onError + * @return an UnicastProcessor instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public static UnicastProcessor create(int capacityHint, Runnable onCancelled, boolean delayError) { + ObjectHelper.requireNonNull(onCancelled, "onTerminate"); + return new UnicastProcessor(capacityHint, onCancelled, delayError); + } + + /** + * Creates an UnicastProcessor with the given capacity hint. + * @param capacityHint the capacity hint for the internal, unbounded queue + * @since 2.0 + */ + UnicastProcessor(int capacityHint) { + this(capacityHint, null, true); + } + + /** + * Creates an UnicastProcessor with the given capacity hint and callback + * for when the Processor is terminated normally or its single Subscriber cancels. + * @param capacityHint the capacity hint for the internal, unbounded queue + * @param onTerminate the callback to run when the Processor is terminated or cancelled, null not allowed + * @since 2.0 + */ + UnicastProcessor(int capacityHint, Runnable onTerminate) { + this(capacityHint, onTerminate, true); + } + + /** + * Creates an UnicastProcessor with the given capacity hint and callback + * for when the Processor is terminated normally or its single Subscriber cancels. + *

History: 2.0.8 - experimental + * @param capacityHint the capacity hint for the internal, unbounded queue + * @param onTerminate the callback to run when the Processor is terminated or cancelled, null not allowed + * @param delayError deliver pending onNext events before onError + * @since 2.2 + */ + UnicastProcessor(int capacityHint, Runnable onTerminate, boolean delayError) { + this.queue = new SpscLinkedArrayQueue(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); + this.onTerminate = new AtomicReference(onTerminate); + this.delayError = delayError; + this.downstream = new AtomicReference>(); + this.once = new AtomicBoolean(); + this.wip = new UnicastQueueSubscription(); + this.requested = new AtomicLong(); + } + + void doTerminate() { + Runnable r = onTerminate.getAndSet(null); + if (r != null) { + r.run(); + } + } + + void drainRegular(Subscriber a) { + int missed = 1; + + final SpscLinkedArrayQueue q = queue; + final boolean failFast = !delayError; + for (;;) { + + long r = requested.get(); + long e = 0L; + + while (r != e) { + boolean d = done; + + T t = q.poll(); + boolean empty = t == null; + + if (checkTerminated(failFast, d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + e++; + } + + if (r == e && checkTerminated(failFast, done, q.isEmpty(), a, q)) { + return; + } + + if (e != 0 && r != Long.MAX_VALUE) { + requested.addAndGet(-e); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void drainFused(Subscriber a) { + int missed = 1; + + final SpscLinkedArrayQueue q = queue; + final boolean failFast = !delayError; + for (;;) { + + if (cancelled) { + downstream.lazySet(null); + return; + } + + boolean d = done; + + if (failFast && d && error != null) { + q.clear(); + downstream.lazySet(null); + a.onError(error); + return; + } + a.onNext(null); + + if (d) { + downstream.lazySet(null); + + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + return; + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + + Subscriber a = downstream.get(); + for (;;) { + if (a != null) { + + if (enableOperatorFusion) { + drainFused(a); + } else { + drainRegular(a); + } + return; + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + a = downstream.get(); + } + } + + boolean checkTerminated(boolean failFast, boolean d, boolean empty, Subscriber a, SpscLinkedArrayQueue q) { + if (cancelled) { + q.clear(); + downstream.lazySet(null); + return true; + } + + if (d) { + if (failFast && error != null) { + q.clear(); + downstream.lazySet(null); + a.onError(error); + return true; + } + if (empty) { + Throwable e = error; + downstream.lazySet(null); + if (e != null) { + a.onError(e); + } else { + a.onComplete(); + } + return true; + } + } + + return false; + } + + @Override + public void onSubscribe(Subscription s) { + if (done || cancelled) { + s.cancel(); + } else { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + + if (done || cancelled) { + return; + } + + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + + if (done || cancelled) { + RxJavaPlugins.onError(t); + return; + } + + error = t; + done = true; + + doTerminate(); + + drain(); + } + + @Override + public void onComplete() { + if (done || cancelled) { + return; + } + + done = true; + + doTerminate(); + + drain(); + } + + @Override + protected void subscribeActual(Subscriber s) { + if (!once.get() && once.compareAndSet(false, true)) { + + s.onSubscribe(wip); + downstream.set(s); + if (cancelled) { + downstream.lazySet(null); + } else { + drain(); + } + } else { + EmptySubscription.error(new IllegalStateException("This processor allows only a single Subscriber"), s); + } + } + + final class UnicastQueueSubscription extends BasicIntQueueSubscription { + + private static final long serialVersionUID = -4896760517184205454L; + + @Nullable + @Override + public T poll() { + return queue.poll(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & QueueSubscription.ASYNC) != 0) { + enableOperatorFusion = true; + return QueueSubscription.ASYNC; + } + return QueueSubscription.NONE; + } + + @Override + public void request(long n) { + if (SubscriptionHelper.validate(n)) { + BackpressureHelper.add(requested, n); + drain(); + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + + doTerminate(); + + downstream.lazySet(null); + if (wip.getAndIncrement() == 0) { + downstream.lazySet(null); + if (!enableOperatorFusion) { + queue.clear(); + } + } + } + } + + @Override + public boolean hasSubscribers() { + return downstream.get() != null; + } + + @Override + @Nullable + public Throwable getThrowable() { + if (done) { + return error; + } + return null; + } + + @Override + public boolean hasComplete() { + return done && error == null; + } + + @Override + public boolean hasThrowable() { + return done && error != null; + } +} diff --git a/src/main/java/io/reactivex/processors/package-info.java b/src/main/java/io/reactivex/processors/package-info.java new file mode 100755 index 0000000..b6a6193 --- /dev/null +++ b/src/main/java/io/reactivex/processors/package-info.java @@ -0,0 +1,39 @@ +/** + * 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. + */ + +/** + * Classes representing so-called hot backpressure-aware sources, aka processors, + * that implement the {@link io.reactivex.processors.FlowableProcessor FlowableProcessor} class, + * the Reactive Streams {@link org.reactivestreams.Processor Processor} interface + * to allow forms of multicasting events to one or more subscribers as well as consuming another + * Reactive Streams {@link org.reactivestreams.Publisher Publisher}. + *

+ * Available processor implementations: + *
+ *

    + *
  • {@link io.reactivex.processors.AsyncProcessor AsyncProcessor} - replays the very last item
  • + *
  • {@link io.reactivex.processors.BehaviorProcessor BehaviorProcessor} - remembers the latest item
  • + *
  • {@link io.reactivex.processors.MulticastProcessor MulticastProcessor} - coordinates its source with its consumers
  • + *
  • {@link io.reactivex.processors.PublishProcessor PublishProcessor} - dispatches items to current consumers
  • + *
  • {@link io.reactivex.processors.ReplayProcessor ReplayProcessor} - remembers some or all items and replays them to consumers
  • + *
  • {@link io.reactivex.processors.UnicastProcessor UnicastProcessor} - remembers or relays items to a single consumer
  • + *
+ *

+ * The non-backpressured variants of the {@code FlowableProcessor} class are called + * {@link io.reactivex.subjects.Subject}s and reside in the {@code io.reactivex.subjects} package. + * @see io.reactivex.subjects + */ +package io.reactivex.processors; diff --git a/src/main/java/io/reactivex/schedulers/SchedulerRunnableIntrospection.java b/src/main/java/io/reactivex/schedulers/SchedulerRunnableIntrospection.java new file mode 100755 index 0000000..aa930e4 --- /dev/null +++ b/src/main/java/io/reactivex/schedulers/SchedulerRunnableIntrospection.java @@ -0,0 +1,39 @@ +/** + * 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.schedulers; + +import io.reactivex.annotations.*; +import io.reactivex.functions.Function; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Interface to indicate the implementor class wraps a {@code Runnable} that can + * be accessed via {@link #getWrappedRunnable()}. + *

+ * You can check if a {@link Runnable} task submitted to a {@link io.reactivex.Scheduler Scheduler} (or its + * {@link io.reactivex.Scheduler.Worker Scheduler.Worker}) implements this interface and unwrap the + * original {@code Runnable} instance. This could help to avoid hooking the same underlying {@code Runnable} + * task in a custom {@link RxJavaPlugins#onSchedule(Runnable)} hook set via + * the {@link RxJavaPlugins#setScheduleHandler(Function)} method multiple times due to internal delegation + * of the default {@code Scheduler.scheduleDirect} or {@code Scheduler.Worker.schedule} methods. + *

History: 2.1.7 - experimental + * @since 2.2 + */ +public interface SchedulerRunnableIntrospection { + + /** + * Returns the wrapped action. + * + * @return the wrapped action. Cannot be null. + */ + @NonNull + Runnable getWrappedRunnable(); +} diff --git a/src/main/java/io/reactivex/schedulers/Schedulers.java b/src/main/java/io/reactivex/schedulers/Schedulers.java new file mode 100755 index 0000000..fcd0a93 --- /dev/null +++ b/src/main/java/io/reactivex/schedulers/Schedulers.java @@ -0,0 +1,483 @@ +/** + * 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.schedulers; + +import java.util.concurrent.*; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.*; +import io.reactivex.internal.schedulers.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Static factory methods for returning standard Scheduler instances. + *

+ * The initial and runtime values of the various scheduler types can be overridden via the + * {@code RxJavaPlugins.setInit(scheduler name)SchedulerHandler()} and + * {@code RxJavaPlugins.set(scheduler name)SchedulerHandler()} respectively. + *

+ * Supported system properties ({@code System.getProperty()}): + *

    + *
  • {@code rx2.io-keep-alive-time} (long): sets the keep-alive time of the {@link #io()} Scheduler workers, default is {@link IoScheduler#KEEP_ALIVE_TIME_DEFAULT}
  • + *
  • {@code rx2.io-priority} (int): sets the thread priority of the {@link #io()} Scheduler, default is {@link Thread#NORM_PRIORITY}
  • + *
  • {@code rx2.io-scheduled-release} (boolean): {@code true} sets the worker release mode of the + * {@link #io()} Scheduler to scheduled, default is {@code false} for eager mode.
  • + *
  • {@code rx2.computation-threads} (int): sets the number of threads in the {@link #computation()} Scheduler, default is the number of available CPUs
  • + *
  • {@code rx2.computation-priority} (int): sets the thread priority of the {@link #computation()} Scheduler, default is {@link Thread#NORM_PRIORITY}
  • + *
  • {@code rx2.newthread-priority} (int): sets the thread priority of the {@link #newThread()} Scheduler, default is {@link Thread#NORM_PRIORITY}
  • + *
  • {@code rx2.single-priority} (int): sets the thread priority of the {@link #single()} Scheduler, default is {@link Thread#NORM_PRIORITY}
  • + *
  • {@code rx2.purge-enabled} (boolean): enables periodic purging of all Scheduler's backing thread pools, default is false
  • + *
  • {@code rx2.purge-period-seconds} (int): specifies the periodic purge interval of all Scheduler's backing thread pools, default is 1 second
  • + *
  • {@code rx2.scheduler.use-nanotime} (boolean): {@code true} instructs {@code Scheduler} to use {@link System#nanoTime()} for {@link Scheduler#now(TimeUnit)}, + * instead of default {@link System#currentTimeMillis()} ({@code false})
  • + *
+ */ +public final class Schedulers { + @NonNull + static final Scheduler SINGLE; + + @NonNull + static final Scheduler COMPUTATION; + + @NonNull + static final Scheduler IO; + + @NonNull + static final Scheduler TRAMPOLINE; + + @NonNull + static final Scheduler NEW_THREAD; + + static final class SingleHolder { + static final Scheduler DEFAULT = new SingleScheduler(); + } + + static final class ComputationHolder { + static final Scheduler DEFAULT = new ComputationScheduler(); + } + + static final class IoHolder { + static final Scheduler DEFAULT = new IoScheduler(); + } + + static final class NewThreadHolder { + static final Scheduler DEFAULT = new NewThreadScheduler(); + } + + static { + SINGLE = RxJavaPlugins.initSingleScheduler(new SingleTask()); + + COMPUTATION = RxJavaPlugins.initComputationScheduler(new ComputationTask()); + + IO = RxJavaPlugins.initIoScheduler(new IOTask()); + + TRAMPOLINE = TrampolineScheduler.instance(); + + NEW_THREAD = RxJavaPlugins.initNewThreadScheduler(new NewThreadTask()); + } + + /** Utility class. */ + private Schedulers() { + throw new IllegalStateException("No instances!"); + } + + /** + * Returns a default, shared {@link Scheduler} instance intended for computational work. + *

+ * This can be used for event-loops, processing callbacks and other computational work. + *

+ * It is not recommended to perform blocking, IO-bound work on this scheduler. Use {@link #io()} instead. + *

+ * The default instance has a backing pool of single-threaded {@link ScheduledExecutorService} instances equal to + * the number of available processors ({@link Runtime#availableProcessors()}) to the Java VM. + *

+ * Unhandled errors will be delivered to the scheduler Thread's {@link Thread.UncaughtExceptionHandler}. + *

+ * This type of scheduler is less sensitive to leaking {@link Scheduler.Worker} instances, although + * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or + * execute those tasks "unexpectedly". + *

+ * If the {@link RxJavaPlugins#setFailOnNonBlockingScheduler(boolean)} is set to true, attempting to execute + * operators that block while running on this scheduler will throw an {@link IllegalStateException}. + *

+ * You can control certain properties of this standard scheduler via system properties that have to be set + * before the {@link Schedulers} class is referenced in your code. + *

Supported system properties ({@code System.getProperty()}): + *

    + *
  • {@code rx2.computation-threads} (int): sets the number of threads in the {@link #computation()} Scheduler, default is the number of available CPUs
  • + *
  • {@code rx2.computation-priority} (int): sets the thread priority of the {@link #computation()} Scheduler, default is {@link Thread#NORM_PRIORITY}
  • + *
  • {@code rx2.io-scheduled-release} (boolean): {@code true} sets the worker release mode of the + * {@code #io()} Scheduler to scheduled, default is {@code false} for eager mode.
  • + *
+ *

+ * The default value of this scheduler can be overridden at initialization time via the + * {@link RxJavaPlugins#setInitComputationSchedulerHandler(io.reactivex.functions.Function)} plugin method. + * Note that due to possible initialization cycles, using any of the other scheduler-returning methods will + * result in a {@code NullPointerException}. + * Once the {@link Schedulers} class has been initialized, you can override the returned {@link Scheduler} instance + * via the {@link RxJavaPlugins#setComputationSchedulerHandler(io.reactivex.functions.Function)} method. + *

+ * It is possible to create a fresh instance of this scheduler with a custom ThreadFactory, via the + * {@link RxJavaPlugins#createComputationScheduler(ThreadFactory)} method. Note that such custom + * instances require a manual call to {@link Scheduler#shutdown()} to allow the JVM to exit or the + * (J2EE) container to unload properly. + *

Operators on the base reactive classes that use this scheduler are marked with the + * @{@link SchedulerSupport SchedulerSupport}({@link SchedulerSupport#COMPUTATION COMPUTATION}) + * annotation. + *

+ * When the {@link Scheduler.Worker} is disposed, the underlying worker can be released to the cached worker pool in two modes: + *

    + *
  • In eager mode (default), the underlying worker is returned immediately to the cached worker pool + * and can be reused much quicker by operators. The drawback is that if the currently running task doesn't + * respond to interruption in time or at all, this may lead to delays or deadlock with the reuse use of the + * underlying worker. + *
  • + *
  • In scheduled mode (enabled via the system parameter {@code rx2.io-scheduled-release} + * set to {@code true}), the underlying worker is returned to the cached worker pool only after the currently running task + * has finished. This can help prevent premature reuse of the underlying worker and likely won't lead to delays or + * deadlock with such reuses. The drawback is that the delay in release may lead to an excess amount of underlying + * workers being created. + *
  • + *
+ * @return a {@link Scheduler} meant for computation-bound work + */ + @NonNull + public static Scheduler computation() { + return RxJavaPlugins.onComputationScheduler(COMPUTATION); + } + + /** + * Returns a default, shared {@link Scheduler} instance intended for IO-bound work. + *

+ * This can be used for asynchronously performing blocking IO. + *

+ * The implementation is backed by a pool of single-threaded {@link ScheduledExecutorService} instances + * that will try to reuse previously started instances used by the worker + * returned by {@link Scheduler#createWorker()} but otherwise will start a new backing + * {@link ScheduledExecutorService} instance. Note that this scheduler may create an unbounded number + * of worker threads that can result in system slowdowns or {@code OutOfMemoryError}. Therefore, for casual uses + * or when implementing an operator, the Worker instances must be disposed via {@link Scheduler.Worker#dispose()}. + *

+ * It is not recommended to perform computational work on this scheduler. Use {@link #computation()} instead. + *

+ * Unhandled errors will be delivered to the scheduler Thread's {@link Thread.UncaughtExceptionHandler}. + *

+ * You can control certain properties of this standard scheduler via system properties that have to be set + * before the {@link Schedulers} class is referenced in your code. + *

Supported system properties ({@code System.getProperty()}): + *

    + *
  • {@code rx2.io-keep-alive-time} (long): sets the keep-alive time of the {@link #io()} Scheduler workers, default is {@link IoScheduler#KEEP_ALIVE_TIME_DEFAULT}
  • + *
  • {@code rx2.io-priority} (int): sets the thread priority of the {@link #io()} Scheduler, default is {@link Thread#NORM_PRIORITY}
  • + *
+ *

+ * The default value of this scheduler can be overridden at initialization time via the + * {@link RxJavaPlugins#setInitIoSchedulerHandler(io.reactivex.functions.Function)} plugin method. + * Note that due to possible initialization cycles, using any of the other scheduler-returning methods will + * result in a {@code NullPointerException}. + * Once the {@link Schedulers} class has been initialized, you can override the returned {@link Scheduler} instance + * via the {@link RxJavaPlugins#setIoSchedulerHandler(io.reactivex.functions.Function)} method. + *

+ * It is possible to create a fresh instance of this scheduler with a custom ThreadFactory, via the + * {@link RxJavaPlugins#createIoScheduler(ThreadFactory)} method. Note that such custom + * instances require a manual call to {@link Scheduler#shutdown()} to allow the JVM to exit or the + * (J2EE) container to unload properly. + *

Operators on the base reactive classes that use this scheduler are marked with the + * @{@link SchedulerSupport SchedulerSupport}({@link SchedulerSupport#IO IO}) + * annotation. + * @return a {@link Scheduler} meant for IO-bound work + */ + @NonNull + public static Scheduler io() { + return RxJavaPlugins.onIoScheduler(IO); + } + + /** + * Returns a default, shared {@link Scheduler} instance whose {@link Scheduler.Worker} + * instances queue work and execute them in a FIFO manner on one of the participating threads. + *

+ * The default implementation's {@link Scheduler#scheduleDirect(Runnable)} methods execute the tasks on the current thread + * without any queueing and the timed overloads use blocking sleep as well. + *

+ * Note that this scheduler can't be reliably used to return the execution of + * tasks to the "main" thread. Such behavior requires a blocking-queueing scheduler currently not provided + * by RxJava itself but may be found in external libraries. + *

+ * This scheduler can't be overridden via an {@link RxJavaPlugins} method. + * @return a {@link Scheduler} that queues work on the current thread + */ + @NonNull + public static Scheduler trampoline() { + return TRAMPOLINE; + } + + /** + * Returns a default, shared {@link Scheduler} instance that creates a new {@link Thread} for each unit of work. + *

+ * The default implementation of this scheduler creates a new, single-threaded {@link ScheduledExecutorService} for + * each invocation of the {@link Scheduler#scheduleDirect(Runnable)} (plus its overloads) and {@link Scheduler#createWorker()} + * methods, thus an unbounded number of worker threads may be created that can + * result in system slowdowns or {@code OutOfMemoryError}. Therefore, for casual uses or when implementing an operator, + * the Worker instances must be disposed via {@link Scheduler.Worker#dispose()}. + *

+ * Unhandled errors will be delivered to the scheduler Thread's {@link Thread.UncaughtExceptionHandler}. + *

+ * You can control certain properties of this standard scheduler via system properties that have to be set + * before the {@link Schedulers} class is referenced in your code. + *

Supported system properties ({@code System.getProperty()}): + *

    + *
  • {@code rx2.newthread-priority} (int): sets the thread priority of the {@link #newThread()} Scheduler, default is {@link Thread#NORM_PRIORITY}
  • + *
+ *

+ * The default value of this scheduler can be overridden at initialization time via the + * {@link RxJavaPlugins#setInitNewThreadSchedulerHandler(io.reactivex.functions.Function)} plugin method. + * Note that due to possible initialization cycles, using any of the other scheduler-returning methods will + * result in a {@code NullPointerException}. + * Once the {@link Schedulers} class has been initialized, you can override the returned {@link Scheduler} instance + * via the {@link RxJavaPlugins#setNewThreadSchedulerHandler(io.reactivex.functions.Function)} method. + *

+ * It is possible to create a fresh instance of this scheduler with a custom ThreadFactory, via the + * {@link RxJavaPlugins#createNewThreadScheduler(ThreadFactory)} method. Note that such custom + * instances require a manual call to {@link Scheduler#shutdown()} to allow the JVM to exit or the + * (J2EE) container to unload properly. + *

Operators on the base reactive classes that use this scheduler are marked with the + * @{@link SchedulerSupport SchedulerSupport}({@link SchedulerSupport#NEW_THREAD NEW_TRHEAD}) + * annotation. + * @return a {@link Scheduler} that creates new threads + */ + @NonNull + public static Scheduler newThread() { + return RxJavaPlugins.onNewThreadScheduler(NEW_THREAD); + } + + /** + * Returns a default, shared, single-thread-backed {@link Scheduler} instance for work + * requiring strongly-sequential execution on the same background thread. + *

+ * Uses: + *

    + *
  • event loop
  • + *
  • support Schedulers.from(Executor) and from(ExecutorService) with delayed scheduling
  • + *
  • support benchmarks that pipeline data from some thread to another thread and + * avoid core-bashing of computation's round-robin nature
  • + *
+ *

+ * Unhandled errors will be delivered to the scheduler Thread's {@link Thread.UncaughtExceptionHandler}. + *

+ * This type of scheduler is less sensitive to leaking {@link Scheduler.Worker} instances, although + * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or + * execute those tasks "unexpectedly". + *

+ * If the {@link RxJavaPlugins#setFailOnNonBlockingScheduler(boolean)} is set to true, attempting to execute + * operators that block while running on this scheduler will throw an {@link IllegalStateException}. + *

+ * You can control certain properties of this standard scheduler via system properties that have to be set + * before the {@link Schedulers} class is referenced in your code. + *

Supported system properties ({@code System.getProperty()}): + *

    + *
  • {@code rx2.single-priority} (int): sets the thread priority of the {@link #single()} Scheduler, default is {@link Thread#NORM_PRIORITY}
  • + *
+ *

+ * The default value of this scheduler can be overridden at initialization time via the + * {@link RxJavaPlugins#setInitSingleSchedulerHandler(io.reactivex.functions.Function)} plugin method. + * Note that due to possible initialization cycles, using any of the other scheduler-returning methods will + * result in a {@code NullPointerException}. + * Once the {@link Schedulers} class has been initialized, you can override the returned {@link Scheduler} instance + * via the {@link RxJavaPlugins#setSingleSchedulerHandler(io.reactivex.functions.Function)} method. + *

+ * It is possible to create a fresh instance of this scheduler with a custom ThreadFactory, via the + * {@link RxJavaPlugins#createSingleScheduler(ThreadFactory)} method. Note that such custom + * instances require a manual call to {@link Scheduler#shutdown()} to allow the JVM to exit or the + * (J2EE) container to unload properly. + *

Operators on the base reactive classes that use this scheduler are marked with the + * @{@link SchedulerSupport SchedulerSupport}({@link SchedulerSupport#SINGLE SINGLE}) + * annotation. + * @return a {@link Scheduler} that shares a single backing thread. + * @since 2.0 + */ + @NonNull + public static Scheduler single() { + return RxJavaPlugins.onSingleScheduler(SINGLE); + } + + /** + * Wraps an {@link Executor} into a new Scheduler instance and delegates {@code schedule()} + * calls to it. + *

+ * If the provided executor doesn't support any of the more specific standard Java executor + * APIs, cancelling tasks scheduled by this scheduler can't be interrupted when they are + * executing but only prevented from running prior to that. In addition, tasks scheduled with + * a time delay or periodically will use the {@link #single()} scheduler for the timed waiting + * before posting the actual task to the given executor. + *

+ * Tasks submitted to the {@link Scheduler.Worker Scheduler.Worker} of this {@code Scheduler} are also not interruptible. Use the + * {@link #from(Executor, boolean)} overload to enable task interruption via this wrapper. + *

+ * If the provided executor supports the standard Java {@link ExecutorService} API, + * cancelling tasks scheduled by this scheduler can be cancelled/interrupted by calling + * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with + * a time delay or periodically will use the {@link #single()} scheduler for the timed waiting + * before posting the actual task to the given executor. + *

+ * If the provided executor supports the standard Java {@link ScheduledExecutorService} API, + * cancelling tasks scheduled by this scheduler can be cancelled/interrupted by calling + * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with + * a time delay or periodically will use the provided executor. Note, however, if the provided + * {@code ScheduledExecutorService} instance is not single threaded, tasks scheduled + * with a time delay close to each other may end up executing in different order than + * the original schedule() call was issued. This limitation may be lifted in a future patch. + *

+ * Starting, stopping and restarting this scheduler is not supported (no-op) and the provided + * executor's lifecycle must be managed externally: + *


+     * ExecutorService exec = Executors.newSingleThreadedExecutor();
+     * try {
+     *     Scheduler scheduler = Schedulers.from(exec);
+     *     Flowable.just(1)
+     *        .subscribeOn(scheduler)
+     *        .map(v -> v + 1)
+     *        .observeOn(scheduler)
+     *        .blockingSubscribe(System.out::println);
+     * } finally {
+     *     exec.shutdown();
+     * }
+     * 
+ *

+ * This type of scheduler is less sensitive to leaking {@link Scheduler.Worker Scheduler.Worker} instances, although + * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or + * execute those tasks "unexpectedly". + *

+ * Note that this method returns a new {@link Scheduler} instance, even for the same {@link Executor} instance. + * @param executor + * the executor to wrap + * @return the new Scheduler wrapping the Executor + */ + @NonNull + public static Scheduler from(@NonNull Executor executor) { + return new ExecutorScheduler(executor, false); + } + + /** + * Wraps an {@link Executor} into a new Scheduler instance and delegates {@code schedule()} + * calls to it. + *

+ * The tasks scheduled by the returned {@link Scheduler} and its {@link Scheduler.Worker Scheduler.Worker} + * can be optionally interrupted. + *

+ * If the provided executor doesn't support any of the more specific standard Java executor + * APIs, tasks scheduled with a time delay or periodically will use the + * {@link #single()} scheduler for the timed waiting + * before posting the actual task to the given executor. + *

+ * If the provided executor supports the standard Java {@link ExecutorService} API, + * canceling tasks scheduled by this scheduler can be cancelled/interrupted by calling + * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with + * a time delay or periodically will use the {@link #single()} scheduler for the timed waiting + * before posting the actual task to the given executor. + *

+ * If the provided executor supports the standard Java {@link ScheduledExecutorService} API, + * canceling tasks scheduled by this scheduler can be cancelled/interrupted by calling + * {@link io.reactivex.disposables.Disposable#dispose()}. In addition, tasks scheduled with + * a time delay or periodically will use the provided executor. Note, however, if the provided + * {@code ScheduledExecutorService} instance is not single threaded, tasks scheduled + * with a time delay close to each other may end up executing in different order than + * the original schedule() call was issued. This limitation may be lifted in a future patch. + *

+ * Starting, stopping and restarting this scheduler is not supported (no-op) and the provided + * executor's lifecycle must be managed externally: + *


+     * ExecutorService exec = Executors.newSingleThreadedExecutor();
+     * try {
+     *     Scheduler scheduler = Schedulers.from(exec, true);
+     *     Flowable.just(1)
+     *        .subscribeOn(scheduler)
+     *        .map(v -> v + 1)
+     *        .observeOn(scheduler)
+     *        .blockingSubscribe(System.out::println);
+     * } finally {
+     *     exec.shutdown();
+     * }
+     * 
+ *

+ * This type of scheduler is less sensitive to leaking {@link Scheduler.Worker Scheduler.Worker} instances, although + * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or + * execute those tasks "unexpectedly". + *

+ * Note that this method returns a new {@link Scheduler} instance, even for the same {@link Executor} instance. + * @param executor + * the executor to wrap + * @param interruptibleWorker if {@code true} the tasks submitted to the {@link Scheduler.Worker Scheduler.Worker} will + * be interrupted when the task is disposed. + * @return the new Scheduler wrapping the Executor + * @since 2.2.6 - experimental + */ + @NonNull + @Experimental + public static Scheduler from(@NonNull Executor executor, boolean interruptibleWorker) { + return new ExecutorScheduler(executor, interruptibleWorker); + } + + /** + * Shuts down the standard Schedulers. + *

The operation is idempotent and thread-safe. + */ + public static void shutdown() { + computation().shutdown(); + io().shutdown(); + newThread().shutdown(); + single().shutdown(); + trampoline().shutdown(); + SchedulerPoolFactory.shutdown(); + } + + /** + * Starts the standard Schedulers. + *

The operation is idempotent and thread-safe. + */ + public static void start() { + computation().start(); + io().start(); + newThread().start(); + single().start(); + trampoline().start(); + SchedulerPoolFactory.start(); + } + + static final class IOTask implements Callable { + @Override + public Scheduler call() throws Exception { + return IoHolder.DEFAULT; + } + } + + static final class NewThreadTask implements Callable { + @Override + public Scheduler call() throws Exception { + return NewThreadHolder.DEFAULT; + } + } + + static final class SingleTask implements Callable { + @Override + public Scheduler call() throws Exception { + return SingleHolder.DEFAULT; + } + } + + static final class ComputationTask implements Callable { + @Override + public Scheduler call() throws Exception { + return ComputationHolder.DEFAULT; + } + } +} diff --git a/src/main/java/io/reactivex/schedulers/TestScheduler.java b/src/main/java/io/reactivex/schedulers/TestScheduler.java new file mode 100755 index 0000000..44272ff --- /dev/null +++ b/src/main/java/io/reactivex/schedulers/TestScheduler.java @@ -0,0 +1,202 @@ +/** + * 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.schedulers; + +import java.util.Queue; +import java.util.concurrent.*; + +import io.reactivex.Scheduler; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.*; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * A special, non thread-safe scheduler for testing operators that require + * a scheduler without introducing real concurrency and allows manually advancing + * a virtual time. + */ +public final class TestScheduler extends Scheduler { + /** The ordered queue for the runnable tasks. */ + final Queue queue = new PriorityBlockingQueue(11); + /** The per-scheduler global order counter. */ + long counter; + // Storing time in nanoseconds internally. + volatile long time; + + /** + * Creates a new TestScheduler with initial virtual time of zero. + */ + public TestScheduler() { + // No-op. + } + + /** + * Creates a new TestScheduler with the specified initial virtual time. + * + * @param delayTime + * the point in time to move the Scheduler's clock to + * @param unit + * the units of time that {@code delayTime} is expressed in + */ + public TestScheduler(long delayTime, TimeUnit unit) { + time = unit.toNanos(delayTime); + } + + static final class TimedRunnable implements Comparable { + + final long time; + final Runnable run; + final TestWorker scheduler; + final long count; // for differentiating tasks at same time + + TimedRunnable(TestWorker scheduler, long time, Runnable run, long count) { + this.time = time; + this.run = run; + this.scheduler = scheduler; + this.count = count; + } + + @Override + public String toString() { + return String.format("TimedRunnable(time = %d, run = %s)", time, run.toString()); + } + + @Override + public int compareTo(TimedRunnable o) { + if (time == o.time) { + return ObjectHelper.compare(count, o.count); + } + return ObjectHelper.compare(time, o.time); + } + } + + @Override + public long now(@NonNull TimeUnit unit) { + return unit.convert(time, TimeUnit.NANOSECONDS); + } + + /** + * Moves the Scheduler's clock forward by a specified amount of time. + * + * @param delayTime + * the amount of time to move the Scheduler's clock forward + * @param unit + * the units of time that {@code delayTime} is expressed in + */ + public void advanceTimeBy(long delayTime, TimeUnit unit) { + advanceTimeTo(time + unit.toNanos(delayTime), TimeUnit.NANOSECONDS); + } + + /** + * Moves the Scheduler's clock to a particular moment in time. + * + * @param delayTime + * the point in time to move the Scheduler's clock to + * @param unit + * the units of time that {@code delayTime} is expressed in + */ + public void advanceTimeTo(long delayTime, TimeUnit unit) { + long targetTime = unit.toNanos(delayTime); + triggerActions(targetTime); + } + + /** + * Triggers any actions that have not yet been triggered and that are scheduled to be triggered at or + * before this Scheduler's present time. + */ + public void triggerActions() { + triggerActions(time); + } + + private void triggerActions(long targetTimeInNanoseconds) { + for (;;) { + TimedRunnable current = queue.peek(); + if (current == null || current.time > targetTimeInNanoseconds) { + break; + } + // if scheduled time is 0 (immediate) use current virtual time + time = current.time == 0 ? time : current.time; + queue.remove(current); + + // Only execute if not unsubscribed + if (!current.scheduler.disposed) { + current.run.run(); + } + } + time = targetTimeInNanoseconds; + } + + @NonNull + @Override + public Worker createWorker() { + return new TestWorker(); + } + + final class TestWorker extends Worker { + + volatile boolean disposed; + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable run, long delayTime, @NonNull TimeUnit unit) { + if (disposed) { + return EmptyDisposable.INSTANCE; + } + final TimedRunnable timedAction = new TimedRunnable(this, time + unit.toNanos(delayTime), run, counter++); + queue.add(timedAction); + + return Disposables.fromRunnable(new QueueRemove(timedAction)); + } + + @NonNull + @Override + public Disposable schedule(@NonNull Runnable run) { + if (disposed) { + return EmptyDisposable.INSTANCE; + } + final TimedRunnable timedAction = new TimedRunnable(this, 0, run, counter++); + queue.add(timedAction); + return Disposables.fromRunnable(new QueueRemove(timedAction)); + } + + @Override + public long now(@NonNull TimeUnit unit) { + return TestScheduler.this.now(unit); + } + + final class QueueRemove implements Runnable { + final TimedRunnable timedAction; + + QueueRemove(TimedRunnable timedAction) { + this.timedAction = timedAction; + } + + @Override + public void run() { + queue.remove(timedAction); + } + } + } +} diff --git a/src/main/java/io/reactivex/schedulers/Timed.java b/src/main/java/io/reactivex/schedulers/Timed.java new file mode 100755 index 0000000..a1bd20e --- /dev/null +++ b/src/main/java/io/reactivex/schedulers/Timed.java @@ -0,0 +1,102 @@ +/** + * 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.schedulers; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.annotations.NonNull; +import io.reactivex.internal.functions.ObjectHelper; + +/** + * Holds onto a value along with time information. + * + * @param the value type + */ +public final class Timed { + final T value; + final long time; + final TimeUnit unit; + + /** + * Constructs a Timed instance with the given value and time information. + * @param value the value to hold + * @param time the time to hold + * @param unit the time unit, not null + * @throws NullPointerException if unit is null + */ + public Timed(@NonNull T value, long time, @NonNull TimeUnit unit) { + this.value = value; + this.time = time; + this.unit = ObjectHelper.requireNonNull(unit, "unit is null"); + } + + /** + * Returns the contained value. + * @return the contained value + */ + @NonNull + public T value() { + return value; + } + + /** + * Returns the time unit of the contained time. + * @return the time unit of the contained time + */ + @NonNull + public TimeUnit unit() { + return unit; + } + + /** + * Returns the time value. + * @return the time value + */ + public long time() { + return time; + } + + /** + * Returns the contained time value in the time unit specified. + * @param unit the time unt + * @return the converted time + */ + public long time(@NonNull TimeUnit unit) { + return unit.convert(time, this.unit); + } + + @Override + public boolean equals(Object other) { + if (other instanceof Timed) { + Timed o = (Timed) other; + return ObjectHelper.equals(value, o.value) + && time == o.time + && ObjectHelper.equals(unit, o.unit); + } + return false; + } + + @Override + public int hashCode() { + int h = value != null ? value.hashCode() : 0; + h = h * 31 + (int)((time >>> 31) ^ time); + h = h * 31 + unit.hashCode(); + return h; + } + + @Override + public String toString() { + return "Timed[time=" + time + ", unit=" + unit + ", value=" + value + "]"; + } +} diff --git a/src/main/java/io/reactivex/schedulers/package-info.java b/src/main/java/io/reactivex/schedulers/package-info.java new file mode 100755 index 0000000..7ba9d63 --- /dev/null +++ b/src/main/java/io/reactivex/schedulers/package-info.java @@ -0,0 +1,22 @@ +/** + * 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. + */ +/** + * Contains notably the factory class of {@link io.reactivex.schedulers.Schedulers Schedulers} providing methods for + * retrieving the standard scheduler instances, the {@link io.reactivex.schedulers.TestScheduler TestScheduler} for testing flows + * with scheduling in a controlled manner and the class {@link io.reactivex.schedulers.Timed Timed} that can hold + * a value and a timestamp associated with it. + */ +package io.reactivex.schedulers; diff --git a/src/main/java/io/reactivex/subjects/AsyncSubject.java b/src/main/java/io/reactivex/subjects/AsyncSubject.java new file mode 100755 index 0000000..8cb488c --- /dev/null +++ b/src/main/java/io/reactivex/subjects/AsyncSubject.java @@ -0,0 +1,395 @@ +/** + * 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.subjects; + +import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.Observer; +import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.observers.DeferredScalarDisposable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * A Subject that emits the very last value followed by a completion event or the received error to Observers. + *

+ * + *

+ * This subject does not have a public constructor by design; a new empty instance of this + * {@code AsyncSubject} can be created via the {@link #create()} method. + *

+ * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (Rule 2.13) + * as parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + *

+ * Since an {@code AsyncSubject} is an {@link io.reactivex.Observable}, it does not support backpressure. + *

+ * When this {@code AsyncSubject} is terminated via {@link #onError(Throwable)}, the + * last observed item (if any) is cleared and late {@link Observer}s only receive + * the {@code onError} event. + *

+ * The {@code AsyncSubject} caches the latest item internally and it emits this item only when {@code onComplete} is called. + * Therefore, it is not recommended to use this {@code Subject} with infinite or never-completing sources. + *

+ * Even though {@code AsyncSubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code AsyncSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + * The implementation of onXXX methods are technically thread-safe but non-serialized calls + * to them may lead to undefined state in the currently subscribed Observers. + *

+ * This {@code AsyncSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read the very last observed value - + * after this {@code AsyncSubject} has been completed - in a non-blocking and thread-safe + * manner via {@link #hasValue()}, {@link #getValue()}, {@link #getValues()} or {@link #getValues(Object[])}. + *

+ *
Scheduler:
+ *
{@code AsyncSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Observer}s get notified on the thread where the terminating {@code onError} or {@code onComplete} + * methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code AsyncSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Observer}s. During this emission, + * if one or more {@code Observer}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Observer}s + * cancel at once). + * If there were no {@code Observer}s subscribed to this {@code AsyncSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *


+ * AsyncSubject<Object> subject = AsyncSubject.create();
+ *
+ * TestObserver<Object> to1 = subject.test();
+ *
+ * to1.assertEmpty();
+ *
+ * subject.onNext(1);
+ *
+ * // AsyncSubject only emits when onComplete was called.
+ * to1.assertEmpty();
+ *
+ * subject.onNext(2);
+ * subject.onComplete();
+ *
+ * // onComplete triggers the emission of the last cached item and the onComplete event.
+ * to1.assertResult(2);
+ *
+ * TestObserver<Object> to2 = subject.test();
+ *
+ * // late Observers receive the last cached item too
+ * to2.assertResult(2);
+ * 
+ * @param the value type + */ +public final class AsyncSubject extends Subject { + + @SuppressWarnings("rawtypes") + static final AsyncDisposable[] EMPTY = new AsyncDisposable[0]; + + @SuppressWarnings("rawtypes") + static final AsyncDisposable[] TERMINATED = new AsyncDisposable[0]; + + final AtomicReference[]> subscribers; + + /** Write before updating subscribers, read after reading subscribers as TERMINATED. */ + Throwable error; + + /** Write before updating subscribers, read after reading subscribers as TERMINATED. */ + T value; + + /** + * Creates a new AsyncProcessor. + * @param the value type to be received and emitted + * @return the new AsyncProcessor instance + */ + @CheckReturnValue + @NonNull + public static AsyncSubject create() { + return new AsyncSubject(); + } + + /** + * Constructs an AsyncSubject. + * @since 2.0 + */ + @SuppressWarnings("unchecked") + AsyncSubject() { + this.subscribers = new AtomicReference[]>(EMPTY); + } + + @Override + public void onSubscribe(Disposable d) { + if (subscribers.get() == TERMINATED) { + d.dispose(); + } + } + + @Override + public void onNext(T t) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + if (subscribers.get() == TERMINATED) { + return; + } + value = t; + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (subscribers.get() == TERMINATED) { + RxJavaPlugins.onError(t); + return; + } + value = null; + error = t; + for (AsyncDisposable as : subscribers.getAndSet(TERMINATED)) { + as.onError(t); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + if (subscribers.get() == TERMINATED) { + return; + } + T v = value; + AsyncDisposable[] array = subscribers.getAndSet(TERMINATED); + if (v == null) { + for (AsyncDisposable as : array) { + as.onComplete(); + } + } else { + for (AsyncDisposable as : array) { + as.complete(v); + } + } + } + + @Override + public boolean hasObservers() { + return subscribers.get().length != 0; + } + + @Override + public boolean hasThrowable() { + return subscribers.get() == TERMINATED && error != null; + } + + @Override + public boolean hasComplete() { + return subscribers.get() == TERMINATED && error == null; + } + + @Override + public Throwable getThrowable() { + return subscribers.get() == TERMINATED ? error : null; + } + + @Override + protected void subscribeActual(Observer observer) { + AsyncDisposable as = new AsyncDisposable(observer, this); + observer.onSubscribe(as); + if (add(as)) { + if (as.isDisposed()) { + remove(as); + } + } else { + Throwable ex = error; + if (ex != null) { + observer.onError(ex); + } else { + T v = value; + if (v != null) { + as.complete(v); + } else { + as.onComplete(); + } + } + } + } + + /** + * Tries to add the given subscriber to the subscribers array atomically + * or returns false if the subject has terminated. + * @param ps the subscriber to add + * @return true if successful, false if the subject has terminated + */ + boolean add(AsyncDisposable ps) { + for (;;) { + AsyncDisposable[] a = subscribers.get(); + if (a == TERMINATED) { + return false; + } + + int n = a.length; + @SuppressWarnings("unchecked") + AsyncDisposable[] b = new AsyncDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = ps; + + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + /** + * Atomically removes the given subscriber if it is subscribed to the subject. + * @param ps the subject to remove + */ + @SuppressWarnings("unchecked") + void remove(AsyncDisposable ps) { + for (;;) { + AsyncDisposable[] a = subscribers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == ps) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + AsyncDisposable[] b; + + if (n == 1) { + b = EMPTY; + } else { + b = new AsyncDisposable[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (subscribers.compareAndSet(a, b)) { + return; + } + } + } + + /** + * Returns true if the subject has any value. + *

The method is thread-safe. + * @return true if the subject has any value + */ + public boolean hasValue() { + return subscribers.get() == TERMINATED && value != null; + } + + /** + * Returns a single value the Subject currently has or null if no such value exists. + *

The method is thread-safe. + * @return a single value the Subject currently has or null if no such value exists + */ + @Nullable + public T getValue() { + return subscribers.get() == TERMINATED ? value : null; + } + + /** + * Returns an Object array containing snapshot all values of the Subject. + *

The method is thread-safe. + * @return the array containing the snapshot of all values of the Subject + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x + */ + @Deprecated + public Object[] getValues() { + T v = getValue(); + return v != null ? new Object[] { v } : new Object[0]; + } + + /** + * Returns a typed array containing a snapshot of all values of the Subject. + *

The method follows the conventions of Collection.toArray by setting the array element + * after the last value to null (if the capacity permits). + *

The method is thread-safe. + * @param array the target array to copy values into if it fits + * @return the given array if the values fit into it or a new array containing all values + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x + */ + @Deprecated + public T[] getValues(T[] array) { + T v = getValue(); + if (v == null) { + if (array.length != 0) { + array[0] = null; + } + return array; + } + if (array.length == 0) { + array = Arrays.copyOf(array, 1); + } + array[0] = v; + if (array.length != 1) { + array[1] = null; + } + return array; + } + + static final class AsyncDisposable extends DeferredScalarDisposable { + private static final long serialVersionUID = 5629876084736248016L; + + final AsyncSubject parent; + + AsyncDisposable(Observer actual, AsyncSubject parent) { + super(actual); + this.parent = parent; + } + + @Override + public void dispose() { + if (super.tryDispose()) { + parent.remove(this); + } + } + + void onComplete() { + if (!isDisposed()) { + downstream.onComplete(); + } + } + + void onError(Throwable t) { + if (isDisposed()) { + RxJavaPlugins.onError(t); + } else { + downstream.onError(t); + } + } + } +} diff --git a/src/main/java/io/reactivex/subjects/BehaviorSubject.java b/src/main/java/io/reactivex/subjects/BehaviorSubject.java new file mode 100755 index 0000000..ed5ceca --- /dev/null +++ b/src/main/java/io/reactivex/subjects/BehaviorSubject.java @@ -0,0 +1,591 @@ +/** + * 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.subjects; + +import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; +import java.lang.reflect.Array; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.*; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.util.*; +import io.reactivex.internal.util.AppendOnlyLinkedArrayList.NonThrowingPredicate; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Subject that emits the most recent item it has observed and all subsequent observed items to each subscribed + * {@link Observer}. + *

+ * + *

+ * This subject does not have a public constructor by design; a new empty instance of this + * {@code BehaviorSubject} can be created via the {@link #create()} method and + * a new non-empty instance can be created via {@link #createDefault(Object)} (named as such to avoid + * overload resolution conflict with {@code Observable.create} that creates an Observable, not a {@code BehaviorSubject}). + *

+ * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (Rule 2.13) as + * default initial values in {@link #createDefault(Object)} or as parameters to {@link #onNext(Object)} and + * {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + *

+ * Since a {@code BehaviorSubject} is an {@link io.reactivex.Observable}, it does not support backpressure. + *

+ * When this {@code BehaviorSubject} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, the + * last observed item (if any) is cleared and late {@link Observer}s only receive + * the respective terminal event. + *

+ * The {@code BehaviorSubject} does not support clearing its cached value (to appear empty again), however, the + * effect can be achieved by using a special item and making sure {@code Observer}s subscribe through a + * filter whose predicate filters out this special item: + *


+ * BehaviorSubject<Integer> subject = BehaviorSubject.create();
+ *
+ * final Integer EMPTY = Integer.MIN_VALUE;
+ *
+ * Observable<Integer> observable = subject.filter(v -> v != EMPTY);
+ *
+ * TestObserver<Integer> to1 = observable.test();
+ *
+ * observable.onNext(1);
+ * // this will "clear" the cache
+ * observable.onNext(EMPTY);
+ *
+ * TestObserver<Integer> to2 = observable.test();
+ *
+ * subject.onNext(2);
+ * subject.onComplete();
+ *
+ * // to1 received both non-empty items
+ * to1.assertResult(1, 2);
+ *
+ * // to2 received only 2 even though the current item was EMPTY
+ * // when it got subscribed
+ * to2.assertResult(2);
+ *
+ * // Observers coming after the subject was terminated receive
+ * // no items and only the onComplete event in this case.
+ * observable.test().assertResult();
+ * 
+ *

+ * Even though {@code BehaviorSubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code BehaviorSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + *

+ * This {@code BehaviorSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read the latest observed value + * in a non-blocking and thread-safe manner via {@link #hasValue()}, {@link #getValue()}, + * {@link #getValues()} or {@link #getValues(Object[])}. + *

+ *
Scheduler:
+ *
{@code BehaviorSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Observer}s get notified on the thread the respective {@code onXXX} methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code BehaviorSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Observer}s. During this emission, + * if one or more {@code Observer}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Observer}s + * cancel at once). + * If there were no {@code Observer}s subscribed to this {@code BehaviorSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *

 {@code
+
+  // observer will receive all 4 events (including "default").
+  BehaviorSubject subject = BehaviorSubject.createDefault("default");
+  subject.subscribe(observer);
+  subject.onNext("one");
+  subject.onNext("two");
+  subject.onNext("three");
+
+  // observer will receive the "one", "two" and "three" events, but not "zero"
+  BehaviorSubject subject = BehaviorSubject.create();
+  subject.onNext("zero");
+  subject.onNext("one");
+  subject.subscribe(observer);
+  subject.onNext("two");
+  subject.onNext("three");
+
+  // observer will receive only onComplete
+  BehaviorSubject subject = BehaviorSubject.create();
+  subject.onNext("zero");
+  subject.onNext("one");
+  subject.onComplete();
+  subject.subscribe(observer);
+
+  // observer will receive only onError
+  BehaviorSubject subject = BehaviorSubject.create();
+  subject.onNext("zero");
+  subject.onNext("one");
+  subject.onError(new RuntimeException("error"));
+  subject.subscribe(observer);
+  } 
+ *
+ * @param 
+ *          the type of item expected to be observed by the Subject
+ */
+public final class BehaviorSubject extends Subject {
+
+    /** An empty array to avoid allocation in getValues(). */
+    private static final Object[] EMPTY_ARRAY = new Object[0];
+
+    final AtomicReference value;
+
+    final AtomicReference[]> subscribers;
+
+    @SuppressWarnings("rawtypes")
+    static final BehaviorDisposable[] EMPTY = new BehaviorDisposable[0];
+
+    @SuppressWarnings("rawtypes")
+    static final BehaviorDisposable[] TERMINATED = new BehaviorDisposable[0];
+    final ReadWriteLock lock;
+    final Lock readLock;
+    final Lock writeLock;
+
+    final AtomicReference terminalEvent;
+
+    long index;
+
+    /**
+     * Creates a {@link BehaviorSubject} without a default item.
+     *
+     * @param 
+     *            the type of item the Subject will emit
+     * @return the constructed {@link BehaviorSubject}
+     */
+    @CheckReturnValue
+    @NonNull
+    public static  BehaviorSubject create() {
+        return new BehaviorSubject();
+    }
+
+    /**
+     * Creates a {@link BehaviorSubject} that emits the last item it observed and all subsequent items to each
+     * {@link Observer} that subscribes to it.
+     *
+     * @param 
+     *            the type of item the Subject will emit
+     * @param defaultValue
+     *            the item that will be emitted first to any {@link Observer} as long as the
+     *            {@link BehaviorSubject} has not yet observed any items from its source {@code Observable}
+     * @return the constructed {@link BehaviorSubject}
+     */
+    @CheckReturnValue
+    @NonNull
+    public static  BehaviorSubject createDefault(T defaultValue) {
+        return new BehaviorSubject(defaultValue);
+    }
+
+    /**
+     * Constructs an empty BehaviorSubject.
+     * @since 2.0
+     */
+    @SuppressWarnings("unchecked")
+    BehaviorSubject() {
+        this.lock = new ReentrantReadWriteLock();
+        this.readLock = lock.readLock();
+        this.writeLock = lock.writeLock();
+        this.subscribers = new AtomicReference[]>(EMPTY);
+        this.value = new AtomicReference();
+        this.terminalEvent = new AtomicReference();
+    }
+
+    /**
+     * Constructs a BehaviorSubject with the given initial value.
+     * @param defaultValue the initial value, not null (verified)
+     * @throws NullPointerException if {@code defaultValue} is null
+     * @since 2.0
+     */
+    BehaviorSubject(T defaultValue) {
+        this();
+        this.value.lazySet(ObjectHelper.requireNonNull(defaultValue, "defaultValue is null"));
+    }
+
+    @Override
+    protected void subscribeActual(Observer observer) {
+        BehaviorDisposable bs = new BehaviorDisposable(observer, this);
+        observer.onSubscribe(bs);
+        if (add(bs)) {
+            if (bs.cancelled) {
+                remove(bs);
+            } else {
+                bs.emitFirst();
+            }
+        } else {
+            Throwable ex = terminalEvent.get();
+            if (ex == ExceptionHelper.TERMINATED) {
+                observer.onComplete();
+            } else {
+                observer.onError(ex);
+            }
+        }
+    }
+
+    @Override
+    public void onSubscribe(Disposable d) {
+        if (terminalEvent.get() != null) {
+            d.dispose();
+        }
+    }
+
+    @Override
+    public void onNext(T t) {
+        ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources.");
+
+        if (terminalEvent.get() != null) {
+            return;
+        }
+        Object o = NotificationLite.next(t);
+        setCurrent(o);
+        for (BehaviorDisposable bs : subscribers.get()) {
+            bs.emitNext(o, index);
+        }
+    }
+
+    @Override
+    public void onError(Throwable t) {
+        ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources.");
+        if (!terminalEvent.compareAndSet(null, t)) {
+            RxJavaPlugins.onError(t);
+            return;
+        }
+        Object o = NotificationLite.error(t);
+        for (BehaviorDisposable bs : terminate(o)) {
+            bs.emitNext(o, index);
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        if (!terminalEvent.compareAndSet(null, ExceptionHelper.TERMINATED)) {
+            return;
+        }
+        Object o = NotificationLite.complete();
+        for (BehaviorDisposable bs : terminate(o)) {
+            bs.emitNext(o, index);  // relaxed read okay since this is the only mutator thread
+        }
+    }
+
+    @Override
+    public boolean hasObservers() {
+        return subscribers.get().length != 0;
+    }
+
+    /* test support*/ int subscriberCount() {
+        return subscribers.get().length;
+    }
+
+    @Override
+    @Nullable
+    public Throwable getThrowable() {
+        Object o = value.get();
+        if (NotificationLite.isError(o)) {
+            return NotificationLite.getError(o);
+        }
+        return null;
+    }
+
+    /**
+     * Returns a single value the Subject currently has or null if no such value exists.
+     * 

The method is thread-safe. + * @return a single value the Subject currently has or null if no such value exists + */ + @Nullable + public T getValue() { + Object o = value.get(); + if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { + return null; + } + return NotificationLite.getValue(o); + } + + /** + * Returns an Object array containing snapshot all values of the Subject. + *

The method is thread-safe. + * @return the array containing the snapshot of all values of the Subject + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x + */ + @Deprecated + public Object[] getValues() { + @SuppressWarnings("unchecked") + T[] a = (T[])EMPTY_ARRAY; + T[] b = getValues(a); + if (b == EMPTY_ARRAY) { + return new Object[0]; + } + return b; + + } + + /** + * Returns a typed array containing a snapshot of all values of the Subject. + *

The method follows the conventions of Collection.toArray by setting the array element + * after the last value to null (if the capacity permits). + *

The method is thread-safe. + * @param array the target array to copy values into if it fits + * @return the given array if the values fit into it or a new array containing all values + * @deprecated in 2.1.14; put the result of {@link #getValue()} into an array manually, will be removed in 3.x + */ + @Deprecated + @SuppressWarnings("unchecked") + public T[] getValues(T[] array) { + Object o = value.get(); + if (o == null || NotificationLite.isComplete(o) || NotificationLite.isError(o)) { + if (array.length != 0) { + array[0] = null; + } + return array; + } + T v = NotificationLite.getValue(o); + if (array.length != 0) { + array[0] = v; + if (array.length != 1) { + array[1] = null; + } + } else { + array = (T[])Array.newInstance(array.getClass().getComponentType(), 1); + array[0] = v; + } + return array; + } + + @Override + public boolean hasComplete() { + Object o = value.get(); + return NotificationLite.isComplete(o); + } + + @Override + public boolean hasThrowable() { + Object o = value.get(); + return NotificationLite.isError(o); + } + + /** + * Returns true if the subject has any value. + *

The method is thread-safe. + * @return true if the subject has any value + */ + public boolean hasValue() { + Object o = value.get(); + return o != null && !NotificationLite.isComplete(o) && !NotificationLite.isError(o); + } + + boolean add(BehaviorDisposable rs) { + for (;;) { + BehaviorDisposable[] a = subscribers.get(); + if (a == TERMINATED) { + return false; + } + int len = a.length; + @SuppressWarnings("unchecked") + BehaviorDisposable[] b = new BehaviorDisposable[len + 1]; + System.arraycopy(a, 0, b, 0, len); + b[len] = rs; + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(BehaviorDisposable rs) { + for (;;) { + BehaviorDisposable[] a = subscribers.get(); + int len = a.length; + if (len == 0) { + return; + } + int j = -1; + for (int i = 0; i < len; i++) { + if (a[i] == rs) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + BehaviorDisposable[] b; + if (len == 1) { + b = EMPTY; + } else { + b = new BehaviorDisposable[len - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, len - j - 1); + } + if (subscribers.compareAndSet(a, b)) { + return; + } + } + } + + @SuppressWarnings("unchecked") + BehaviorDisposable[] terminate(Object terminalValue) { + + BehaviorDisposable[] a = subscribers.getAndSet(TERMINATED); + if (a != TERMINATED) { + // either this or atomics with lots of allocation + setCurrent(terminalValue); + } + + return a; + } + + void setCurrent(Object o) { + writeLock.lock(); + index++; + value.lazySet(o); + writeLock.unlock(); + } + + static final class BehaviorDisposable implements Disposable, NonThrowingPredicate { + + final Observer downstream; + final BehaviorSubject state; + + boolean next; + boolean emitting; + AppendOnlyLinkedArrayList queue; + + boolean fastPath; + + volatile boolean cancelled; + + long index; + + BehaviorDisposable(Observer actual, BehaviorSubject state) { + this.downstream = actual; + this.state = state; + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + + state.remove(this); + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + + void emitFirst() { + if (cancelled) { + return; + } + Object o; + synchronized (this) { + if (cancelled) { + return; + } + if (next) { + return; + } + + BehaviorSubject s = state; + Lock lock = s.readLock; + + lock.lock(); + index = s.index; + o = s.value.get(); + lock.unlock(); + + emitting = o != null; + next = true; + } + + if (o != null) { + if (test(o)) { + return; + } + + emitLoop(); + } + } + + void emitNext(Object value, long stateIndex) { + if (cancelled) { + return; + } + if (!fastPath) { + synchronized (this) { + if (cancelled) { + return; + } + if (index == stateIndex) { + return; + } + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.add(value); + return; + } + next = true; + } + fastPath = true; + } + + test(value); + } + + @Override + public boolean test(Object o) { + return cancelled || NotificationLite.accept(o, downstream); + } + + void emitLoop() { + for (;;) { + if (cancelled) { + return; + } + AppendOnlyLinkedArrayList q; + synchronized (this) { + q = queue; + if (q == null) { + emitting = false; + return; + } + queue = null; + } + + q.forEachWhile(this); + } + } + } +} diff --git a/src/main/java/io/reactivex/subjects/CompletableSubject.java b/src/main/java/io/reactivex/subjects/CompletableSubject.java new file mode 100755 index 0000000..57c27d3 --- /dev/null +++ b/src/main/java/io/reactivex/subjects/CompletableSubject.java @@ -0,0 +1,283 @@ +/** + * 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.subjects; + +import io.reactivex.annotations.Nullable; +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Represents a hot Completable-like source and consumer of events similar to Subjects. + *

+ * + *

+ * This subject does not have a public constructor by design; a new non-terminated instance of this + * {@code CompletableSubject} can be created via the {@link #create()} method. + *

+ * Since the {@code CompletableSubject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (Rule 2.13) + * as parameters to {@link #onError(Throwable)}. + *

+ * Even though {@code CompletableSubject} implements the {@code CompletableObserver} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code CompletableSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + *

+ * All methods are thread safe. Calling {@link #onComplete()} multiple + * times has no effect. Calling {@link #onError(Throwable)} multiple times relays the {@code Throwable} to + * the {@link RxJavaPlugins#onError(Throwable)} global error handler. + *

+ * This {@code CompletableSubject} supports the standard state-peeking methods {@link #hasComplete()}, + * {@link #hasThrowable()}, {@link #getThrowable()} and {@link #hasObservers()}. + *

+ *
Scheduler:
+ *
{@code CompletableSubject} does not operate by default on a particular {@link Scheduler} and + * the {@code CompletableObserver}s get notified on the thread where the terminating {@code onError} or {@code onComplete} + * methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code CompletableSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code CompletableObserver}s. During this emission, + * if one or more {@code CompletableObserver}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code CompletableObserver}s + * cancel at once). + * If there were no {@code CompletableObserver}s subscribed to this {@code CompletableSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *


+ * CompletableSubject subject = CompletableSubject.create();
+ *
+ * TestObserver<Void> to1 = subject.test();
+ *
+ * // a fresh CompletableSubject is empty
+ * to1.assertEmpty();
+ *
+ * subject.onComplete();
+ *
+ * // a CompletableSubject is always void of items
+ * to1.assertResult();
+ *
+ * TestObserver<Void> to2 = subject.test()
+ *
+ * // late CompletableObservers receive the terminal event
+ * to2.assertResult();
+ * 
+ *

History: 2.0.5 - experimental + * @since 2.1 + */ +public final class CompletableSubject extends Completable implements CompletableObserver { + + final AtomicReference observers; + + static final CompletableDisposable[] EMPTY = new CompletableDisposable[0]; + + static final CompletableDisposable[] TERMINATED = new CompletableDisposable[0]; + + final AtomicBoolean once; + Throwable error; + + /** + * Creates a fresh CompletableSubject. + * @return the new CompletableSubject instance + */ + @CheckReturnValue + @NonNull + public static CompletableSubject create() { + return new CompletableSubject(); + } + + CompletableSubject() { + once = new AtomicBoolean(); + observers = new AtomicReference(EMPTY); + } + + @Override + public void onSubscribe(Disposable d) { + if (observers.get() == TERMINATED) { + d.dispose(); + } + } + + @Override + public void onError(Throwable e) { + ObjectHelper.requireNonNull(e, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (once.compareAndSet(false, true)) { + this.error = e; + for (CompletableDisposable md : observers.getAndSet(TERMINATED)) { + md.downstream.onError(e); + } + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + public void onComplete() { + if (once.compareAndSet(false, true)) { + for (CompletableDisposable md : observers.getAndSet(TERMINATED)) { + md.downstream.onComplete(); + } + } + } + + @Override + protected void subscribeActual(CompletableObserver observer) { + CompletableDisposable md = new CompletableDisposable(observer, this); + observer.onSubscribe(md); + if (add(md)) { + if (md.isDisposed()) { + remove(md); + } + } else { + Throwable ex = error; + if (ex != null) { + observer.onError(ex); + } else { + observer.onComplete(); + } + } + } + + boolean add(CompletableDisposable inner) { + for (;;) { + CompletableDisposable[] a = observers.get(); + if (a == TERMINATED) { + return false; + } + + int n = a.length; + + CompletableDisposable[] b = new CompletableDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (observers.compareAndSet(a, b)) { + return true; + } + } + } + + void remove(CompletableDisposable inner) { + for (;;) { + CompletableDisposable[] a = observers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + CompletableDisposable[] b; + if (n == 1) { + b = EMPTY; + } else { + b = new CompletableDisposable[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + + if (observers.compareAndSet(a, b)) { + return; + } + } + } + + /** + * Returns the terminal error if this CompletableSubject has been terminated with an error, null otherwise. + * @return the terminal error or null if not terminated or not with an error + */ + @Nullable + public Throwable getThrowable() { + if (observers.get() == TERMINATED) { + return error; + } + return null; + } + + /** + * Returns true if this CompletableSubject has been terminated with an error. + * @return true if this CompletableSubject has been terminated with an error + */ + public boolean hasThrowable() { + return observers.get() == TERMINATED && error != null; + } + + /** + * Returns true if this CompletableSubject has been completed. + * @return true if this CompletableSubject has been completed + */ + public boolean hasComplete() { + return observers.get() == TERMINATED && error == null; + } + + /** + * Returns true if this CompletableSubject has observers. + * @return true if this CompletableSubject has observers + */ + public boolean hasObservers() { + return observers.get().length != 0; + } + + /** + * Returns the number of current observers. + * @return the number of current observers + */ + /* test */ int observerCount() { + return observers.get().length; + } + + static final class CompletableDisposable + extends AtomicReference implements Disposable { + private static final long serialVersionUID = -7650903191002190468L; + + final CompletableObserver downstream; + + CompletableDisposable(CompletableObserver actual, CompletableSubject parent) { + this.downstream = actual; + lazySet(parent); + } + + @Override + public void dispose() { + CompletableSubject parent = getAndSet(null); + if (parent != null) { + parent.remove(this); + } + } + + @Override + public boolean isDisposed() { + return get() == null; + } + } +} diff --git a/src/main/java/io/reactivex/subjects/MaybeSubject.java b/src/main/java/io/reactivex/subjects/MaybeSubject.java new file mode 100755 index 0000000..b3f0f06 --- /dev/null +++ b/src/main/java/io/reactivex/subjects/MaybeSubject.java @@ -0,0 +1,351 @@ +/** + * 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.subjects; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Represents a hot Maybe-like source and consumer of events similar to Subjects. + *

+ * + *

+ * This subject does not have a public constructor by design; a new non-terminated instance of this + * {@code MaybeSubject} can be created via the {@link #create()} method. + *

+ * Since the {@code MaybeSubject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (Rule 2.13) + * as parameters to {@link #onSuccess(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + *

+ * Since a {@code MaybeSubject} is a {@link Maybe}, calling {@code onSuccess}, {@code onError} + * or {@code onComplete} will move this {@code MaybeSubject} into its terminal state atomically. + *

+ * All methods are thread safe. Calling {@link #onSuccess(Object)} or {@link #onComplete()} multiple + * times has no effect. Calling {@link #onError(Throwable)} multiple times relays the {@code Throwable} to + * the {@link RxJavaPlugins#onError(Throwable)} global error handler. + *

+ * Even though {@code MaybeSubject} implements the {@code MaybeObserver} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code MaybeSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + *

+ * This {@code MaybeSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read any success item in a non-blocking + * and thread-safe manner via {@link #hasValue()} and {@link #getValue()}. + *

+ * The {@code MaybeSubject} does not support clearing its cached {@code onSuccess} value. + *

+ *
Scheduler:
+ *
{@code MaybeSubject} does not operate by default on a particular {@link Scheduler} and + * the {@code MaybeObserver}s get notified on the thread where the terminating {@code onSuccess}, {@code onError} or {@code onComplete} + * methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code MaybeSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code MaybeObserver}s. During this emission, + * if one or more {@code MaybeObserver}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code MaybeObserver}s + * cancel at once). + * If there were no {@code MaybeObserver}s subscribed to this {@code MaybeSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *


+ * MaybeSubject<Integer> subject1 = MaybeSubject.create();
+ *
+ * TestObserver<Integer> to1 = subject1.test();
+ *
+ * // MaybeSubjects are empty by default
+ * to1.assertEmpty();
+ *
+ * subject1.onSuccess(1);
+ *
+ * // onSuccess is a terminal event with MaybeSubjects
+ * // TestObserver converts onSuccess into onNext + onComplete
+ * to1.assertResult(1);
+ *
+ * TestObserver<Integer> to2 = subject1.test();
+ *
+ * // late Observers receive the terminal signal (onSuccess) too
+ * to2.assertResult(1);
+ *
+ * // -----------------------------------------------------
+ *
+ * MaybeSubject<Integer> subject2 = MaybeSubject.create();
+ *
+ * TestObserver<Integer> to3 = subject2.test();
+ *
+ * subject2.onComplete();
+ *
+ * // a completed MaybeSubject completes its MaybeObservers
+ * to3.assertResult();
+ *
+ * TestObserver<Integer> to4 = subject1.test();
+ *
+ * // late Observers receive the terminal signal (onComplete) too
+ * to4.assertResult();
+ * 
+ *

History: 2.0.5 - experimental + * @param the value type received and emitted + * @since 2.1 + */ +public final class MaybeSubject extends Maybe implements MaybeObserver { + + final AtomicReference[]> observers; + + @SuppressWarnings("rawtypes") + static final MaybeDisposable[] EMPTY = new MaybeDisposable[0]; + + @SuppressWarnings("rawtypes") + static final MaybeDisposable[] TERMINATED = new MaybeDisposable[0]; + + final AtomicBoolean once; + T value; + Throwable error; + + /** + * Creates a fresh MaybeSubject. + * @param the value type received and emitted + * @return the new MaybeSubject instance + */ + @CheckReturnValue + @NonNull + public static MaybeSubject create() { + return new MaybeSubject(); + } + + @SuppressWarnings("unchecked") + MaybeSubject() { + once = new AtomicBoolean(); + observers = new AtomicReference[]>(EMPTY); + } + + @Override + public void onSubscribe(Disposable d) { + if (observers.get() == TERMINATED) { + d.dispose(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onSuccess(T value) { + ObjectHelper.requireNonNull(value, "onSuccess called with null. Null values are generally not allowed in 2.x operators and sources."); + if (once.compareAndSet(false, true)) { + this.value = value; + for (MaybeDisposable md : observers.getAndSet(TERMINATED)) { + md.downstream.onSuccess(value); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(Throwable e) { + ObjectHelper.requireNonNull(e, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (once.compareAndSet(false, true)) { + this.error = e; + for (MaybeDisposable md : observers.getAndSet(TERMINATED)) { + md.downstream.onError(e); + } + } else { + RxJavaPlugins.onError(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onComplete() { + if (once.compareAndSet(false, true)) { + for (MaybeDisposable md : observers.getAndSet(TERMINATED)) { + md.downstream.onComplete(); + } + } + } + + @Override + protected void subscribeActual(MaybeObserver observer) { + MaybeDisposable md = new MaybeDisposable(observer, this); + observer.onSubscribe(md); + if (add(md)) { + if (md.isDisposed()) { + remove(md); + } + } else { + Throwable ex = error; + if (ex != null) { + observer.onError(ex); + } else { + T v = value; + if (v == null) { + observer.onComplete(); + } else { + observer.onSuccess(v); + } + } + } + } + + boolean add(MaybeDisposable inner) { + for (;;) { + MaybeDisposable[] a = observers.get(); + if (a == TERMINATED) { + return false; + } + + int n = a.length; + @SuppressWarnings("unchecked") + MaybeDisposable[] b = new MaybeDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (observers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(MaybeDisposable inner) { + for (;;) { + MaybeDisposable[] a = observers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + MaybeDisposable[] b; + if (n == 1) { + b = EMPTY; + } else { + b = new MaybeDisposable[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + + if (observers.compareAndSet(a, b)) { + return; + } + } + } + + /** + * Returns the success value if this MaybeSubject was terminated with a success value. + * @return the success value or null + */ + @Nullable + public T getValue() { + if (observers.get() == TERMINATED) { + return value; + } + return null; + } + + /** + * Returns true if this MaybeSubject was terminated with a success value. + * @return true if this MaybeSubject was terminated with a success value + */ + public boolean hasValue() { + return observers.get() == TERMINATED && value != null; + } + + /** + * Returns the terminal error if this MaybeSubject has been terminated with an error, null otherwise. + * @return the terminal error or null if not terminated or not with an error + */ + @Nullable + public Throwable getThrowable() { + if (observers.get() == TERMINATED) { + return error; + } + return null; + } + + /** + * Returns true if this MaybeSubject has been terminated with an error. + * @return true if this MaybeSubject has been terminated with an error + */ + public boolean hasThrowable() { + return observers.get() == TERMINATED && error != null; + } + + /** + * Returns true if this MaybeSubject has been completed. + * @return true if this MaybeSubject has been completed + */ + public boolean hasComplete() { + return observers.get() == TERMINATED && value == null && error == null; + } + + /** + * Returns true if this MaybeSubject has observers. + * @return true if this MaybeSubject has observers + */ + public boolean hasObservers() { + return observers.get().length != 0; + } + + /** + * Returns the number of current observers. + * @return the number of current observers + */ + /* test */ int observerCount() { + return observers.get().length; + } + + static final class MaybeDisposable + extends AtomicReference> implements Disposable { + private static final long serialVersionUID = -7650903191002190468L; + + final MaybeObserver downstream; + + MaybeDisposable(MaybeObserver actual, MaybeSubject parent) { + this.downstream = actual; + lazySet(parent); + } + + @Override + public void dispose() { + MaybeSubject parent = getAndSet(null); + if (parent != null) { + parent.remove(this); + } + } + + @Override + public boolean isDisposed() { + return get() == null; + } + } +} diff --git a/src/main/java/io/reactivex/subjects/PublishSubject.java b/src/main/java/io/reactivex/subjects/PublishSubject.java new file mode 100755 index 0000000..ab51eb3 --- /dev/null +++ b/src/main/java/io/reactivex/subjects/PublishSubject.java @@ -0,0 +1,338 @@ +/** + * 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.subjects; + +import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; +import java.util.concurrent.atomic.*; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * A Subject that emits (multicasts) items to currently subscribed {@link Observer}s and terminal events to current + * or late {@code Observer}s. + *

+ * + *

+ * This subject does not have a public constructor by design; a new empty instance of this + * {@code PublishSubject} can be created via the {@link #create()} method. + *

+ * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (Rule 2.13) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + *

+ * Since a {@code PublishSubject} is an {@link io.reactivex.Observable}, it does not support backpressure. + *

+ * When this {@code PublishSubject} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * late {@link Observer}s only receive the respective terminal event. + *

+ * Unlike a {@link BehaviorSubject}, a {@code PublishSubject} doesn't retain/cache items, therefore, a new + * {@code Observer} won't receive any past items. + *

+ * Even though {@code PublishSubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code PublishSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + *

+ * This {@code PublishSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()}. + *

+ *
Scheduler:
+ *
{@code PublishSubject} does not operate by default on a particular {@link io.reactivex.Scheduler} and + * the {@code Observer}s get notified on the thread the respective {@code onXXX} methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code PublishSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Observer}s. During this emission, + * if one or more {@code Observer}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Observer}s + * cancel at once). + * If there were no {@code Observer}s subscribed to this {@code PublishSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *

 {@code
+
+  PublishSubject subject = PublishSubject.create();
+  // observer1 will receive all onNext and onComplete events
+  subject.subscribe(observer1);
+  subject.onNext("one");
+  subject.onNext("two");
+  // observer2 will only receive "three" and onComplete
+  subject.subscribe(observer2);
+  subject.onNext("three");
+  subject.onComplete();
+
+  // late Observers only receive the terminal event
+  subject.test().assertEmpty();
+  } 
+ *
+ * @param 
+ *          the type of items observed and emitted by the Subject
+ */
+public final class PublishSubject extends Subject {
+    /** The terminated indicator for the subscribers array. */
+    @SuppressWarnings("rawtypes")
+    static final PublishDisposable[] TERMINATED = new PublishDisposable[0];
+    /** An empty subscribers array to avoid allocating it all the time. */
+    @SuppressWarnings("rawtypes")
+    static final PublishDisposable[] EMPTY = new PublishDisposable[0];
+
+    /** The array of currently subscribed subscribers. */
+    final AtomicReference[]> subscribers;
+
+    /** The error, write before terminating and read after checking subscribers. */
+    Throwable error;
+
+    /**
+     * Constructs a PublishSubject.
+     * @param  the value type
+     * @return the new PublishSubject
+     */
+    @CheckReturnValue
+    @NonNull
+    public static  PublishSubject create() {
+        return new PublishSubject();
+    }
+
+    /**
+     * Constructs a PublishSubject.
+     * @since 2.0
+     */
+    @SuppressWarnings("unchecked")
+    PublishSubject() {
+        subscribers = new AtomicReference[]>(EMPTY);
+    }
+
+    @Override
+    protected void subscribeActual(Observer t) {
+        PublishDisposable ps = new PublishDisposable(t, this);
+        t.onSubscribe(ps);
+        if (add(ps)) {
+            // if cancellation happened while a successful add, the remove() didn't work
+            // so we need to do it again
+            if (ps.isDisposed()) {
+                remove(ps);
+            }
+        } else {
+            Throwable ex = error;
+            if (ex != null) {
+                t.onError(ex);
+            } else {
+                t.onComplete();
+            }
+        }
+    }
+
+    /**
+     * Tries to add the given subscriber to the subscribers array atomically
+     * or returns false if the subject has terminated.
+     * @param ps the subscriber to add
+     * @return true if successful, false if the subject has terminated
+     */
+    boolean add(PublishDisposable ps) {
+        for (;;) {
+            PublishDisposable[] a = subscribers.get();
+            if (a == TERMINATED) {
+                return false;
+            }
+
+            int n = a.length;
+            @SuppressWarnings("unchecked")
+            PublishDisposable[] b = new PublishDisposable[n + 1];
+            System.arraycopy(a, 0, b, 0, n);
+            b[n] = ps;
+
+            if (subscribers.compareAndSet(a, b)) {
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Atomically removes the given subscriber if it is subscribed to the subject.
+     * @param ps the subject to remove
+     */
+    @SuppressWarnings("unchecked")
+    void remove(PublishDisposable ps) {
+        for (;;) {
+            PublishDisposable[] a = subscribers.get();
+            if (a == TERMINATED || a == EMPTY) {
+                return;
+            }
+
+            int n = a.length;
+            int j = -1;
+            for (int i = 0; i < n; i++) {
+                if (a[i] == ps) {
+                    j = i;
+                    break;
+                }
+            }
+
+            if (j < 0) {
+                return;
+            }
+
+            PublishDisposable[] b;
+
+            if (n == 1) {
+                b = EMPTY;
+            } else {
+                b = new PublishDisposable[n - 1];
+                System.arraycopy(a, 0, b, 0, j);
+                System.arraycopy(a, j + 1, b, j, n - j - 1);
+            }
+            if (subscribers.compareAndSet(a, b)) {
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void onSubscribe(Disposable d) {
+        if (subscribers.get() == TERMINATED) {
+            d.dispose();
+        }
+    }
+
+    @Override
+    public void onNext(T t) {
+        ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources.");
+        for (PublishDisposable pd : subscribers.get()) {
+            pd.onNext(t);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void onError(Throwable t) {
+        ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources.");
+        if (subscribers.get() == TERMINATED) {
+            RxJavaPlugins.onError(t);
+            return;
+        }
+        error = t;
+
+        for (PublishDisposable pd : subscribers.getAndSet(TERMINATED)) {
+            pd.onError(t);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void onComplete() {
+        if (subscribers.get() == TERMINATED) {
+            return;
+        }
+        for (PublishDisposable pd : subscribers.getAndSet(TERMINATED)) {
+            pd.onComplete();
+        }
+    }
+
+    @Override
+    public boolean hasObservers() {
+        return subscribers.get().length != 0;
+    }
+
+    @Override
+    @Nullable
+    public Throwable getThrowable() {
+        if (subscribers.get() == TERMINATED) {
+            return error;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean hasThrowable() {
+        return subscribers.get() == TERMINATED && error != null;
+    }
+
+    @Override
+    public boolean hasComplete() {
+        return subscribers.get() == TERMINATED && error == null;
+    }
+
+    /**
+     * Wraps the actual subscriber, tracks its requests and makes cancellation
+     * to remove itself from the current subscribers array.
+     *
+     * @param  the value type
+     */
+    static final class PublishDisposable extends AtomicBoolean implements Disposable {
+
+        private static final long serialVersionUID = 3562861878281475070L;
+        /** The actual subscriber. */
+        final Observer downstream;
+        /** The subject state. */
+        final PublishSubject parent;
+
+        /**
+         * Constructs a PublishSubscriber, wraps the actual subscriber and the state.
+         * @param actual the actual subscriber
+         * @param parent the parent PublishProcessor
+         */
+        PublishDisposable(Observer actual, PublishSubject parent) {
+            this.downstream = actual;
+            this.parent = parent;
+        }
+
+        public void onNext(T t) {
+            if (!get()) {
+                downstream.onNext(t);
+            }
+        }
+
+        public void onError(Throwable t) {
+            if (get()) {
+                RxJavaPlugins.onError(t);
+            } else {
+                downstream.onError(t);
+            }
+        }
+
+        public void onComplete() {
+            if (!get()) {
+                downstream.onComplete();
+            }
+        }
+
+        @Override
+        public void dispose() {
+            if (compareAndSet(false, true)) {
+                parent.remove(this);
+            }
+        }
+
+        @Override
+        public boolean isDisposed() {
+            return get();
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/subjects/ReplaySubject.java b/src/main/java/io/reactivex/subjects/ReplaySubject.java
new file mode 100755
index 0000000..41c8498
--- /dev/null
+++ b/src/main/java/io/reactivex/subjects/ReplaySubject.java
@@ -0,0 +1,1336 @@
+/**
+ * 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.subjects;
+
+import java.lang.reflect.Array;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.*;
+
+import io.reactivex.Observer;
+import io.reactivex.Scheduler;
+import io.reactivex.annotations.*;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.internal.functions.ObjectHelper;
+import io.reactivex.internal.util.NotificationLite;
+import io.reactivex.plugins.RxJavaPlugins;
+
+/**
+ * Replays events (in a configurable bounded or unbounded manner) to current and late {@link Observer}s.
+ * 

+ * This subject does not have a public constructor by design; a new empty instance of this + * {@code ReplaySubject} can be created via the following {@code create} methods that + * allow specifying the retention policy for items: + *

    + *
  • {@link #create()} - creates an empty, unbounded {@code ReplaySubject} that + * caches all items and the terminal event it receives. + *

    + * + *

    + * + *

  • + *
  • {@link #create(int)} - creates an empty, unbounded {@code ReplaySubject} + * with a hint about how many total items one expects to retain. + *
  • + *
  • {@link #createWithSize(int)} - creates an empty, size-bound {@code ReplaySubject} + * that retains at most the given number of the latest item it receives. + *

    + * + *

  • + *
  • {@link #createWithTime(long, TimeUnit, Scheduler)} - creates an empty, time-bound + * {@code ReplaySubject} that retains items no older than the specified time amount. + *

    + * + *

  • + *
  • {@link #createWithTimeAndSize(long, TimeUnit, Scheduler, int)} - creates an empty, + * time- and size-bound {@code ReplaySubject} that retains at most the given number + * items that are also not older than the specified time amount. + *

    + * + *

  • + *
+ *

+ * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (Rule 2.13) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + *

+ * Since a {@code ReplaySubject} is an {@link io.reactivex.Observable}, it does not support backpressure. + *

+ * When this {@code ReplaySubject} is terminated via {@link #onError(Throwable)} or {@link #onComplete()}, + * late {@link Observer}s will receive the retained/cached items first (if any) followed by the respective + * terminal event. If the {@code ReplaySubject} has a time-bound, the age of the retained/cached items are still considered + * when replaying and thus it may result in no items being emitted before the terminal event. + *

+ * Once an {@code Observer} has subscribed, it will receive items continuously from that point on. Bounds only affect how + * many past items a new {@code Observer} will receive before it catches up with the live event feed. + *

+ * Even though {@code ReplaySubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code ReplaySubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + *

+ * This {@code ReplaySubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read the retained/cached items + * in a non-blocking and thread-safe manner via {@link #hasValue()}, {@link #getValue()}, + * {@link #getValues()} or {@link #getValues(Object[])}. + *

+ * Note that due to concurrency requirements, a size- and time-bounded {@code ReplaySubject} may hold strong references to more + * source emissions than specified while it isn't terminated yet. Use the {@link #cleanupBuffer()} to allow + * such inaccessible items to be cleaned up by GC once no consumer references it anymore. + *

+ *
Scheduler:
+ *
{@code ReplaySubject} does not operate by default on a particular {@link Scheduler} and + * the {@code Observer}s get notified on the thread the respective {@code onXXX} methods were invoked. + * Time-bound {@code ReplaySubject}s use the given {@code Scheduler} in their {@code create} methods + * as time source to timestamp of items received for the age checks.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code ReplaySubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code Observer}s. During this emission, + * if one or more {@code Observer}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code Observer}s + * cancel at once). + * If there were no {@code Observer}s subscribed to this {@code ReplaySubject} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *

 {@code
+
+  ReplaySubject subject = ReplaySubject.create();
+  subject.onNext("one");
+  subject.onNext("two");
+  subject.onNext("three");
+  subject.onComplete();
+
+  // both of the following will get the onNext/onComplete calls from above
+  subject.subscribe(observer1);
+  subject.subscribe(observer2);
+
+  } 
+ *
+ * @param  the value type
+ */
+public final class ReplaySubject extends Subject {
+    final ReplayBuffer buffer;
+
+    final AtomicReference[]> observers;
+
+    @SuppressWarnings("rawtypes")
+    static final ReplayDisposable[] EMPTY = new ReplayDisposable[0];
+
+    @SuppressWarnings("rawtypes")
+    static final ReplayDisposable[] TERMINATED = new ReplayDisposable[0];
+
+    boolean done;
+
+    /**
+     * Creates an unbounded replay subject.
+     * 

+ * The internal buffer is backed by an {@link ArrayList} and starts with an initial capacity of 16. Once the + * number of items reaches this capacity, it will grow as necessary (usually by 50%). However, as the + * number of items grows, this causes frequent array reallocation and copying, and may hurt performance + * and latency. This can be avoided with the {@link #create(int)} overload which takes an initial capacity + * parameter and can be tuned to reduce the array reallocation frequency as needed. + * + * @param + * the type of items observed and emitted by the Subject + * @return the created subject + */ + @CheckReturnValue + @NonNull + public static ReplaySubject create() { + return new ReplaySubject(new UnboundedReplayBuffer(16)); + } + + /** + * Creates an unbounded replay subject with the specified initial buffer capacity. + *

+ * Use this method to avoid excessive array reallocation while the internal buffer grows to accommodate new + * items. For example, if you know that the buffer will hold 32k items, you can ask the + * {@code ReplaySubject} to preallocate its internal array with a capacity to hold that many items. Once + * the items start to arrive, the internal array won't need to grow, creating less garbage and no overhead + * due to frequent array-copying. + * + * @param + * the type of items observed and emitted by the Subject + * @param capacityHint + * the initial buffer capacity + * @return the created subject + */ + @CheckReturnValue + @NonNull + public static ReplaySubject create(int capacityHint) { + return new ReplaySubject(new UnboundedReplayBuffer(capacityHint)); + } + + /** + * Creates a size-bounded replay subject. + *

+ * In this setting, the {@code ReplaySubject} holds at most {@code size} items in its internal buffer and + * discards the oldest item. + *

+ * When observers subscribe to a terminated {@code ReplaySubject}, they are guaranteed to see at most + * {@code size} {@code onNext} events followed by a termination event. + *

+ * If an observer subscribes while the {@code ReplaySubject} is active, it will observe all items in the + * buffer at that point in time and each item observed afterwards, even if the buffer evicts items due to + * the size constraint in the mean time. In other words, once an Observer subscribes, it will receive items + * without gaps in the sequence. + * + * @param + * the type of items observed and emitted by the Subject + * @param maxSize + * the maximum number of buffered items + * @return the created subject + */ + @CheckReturnValue + @NonNull + public static ReplaySubject createWithSize(int maxSize) { + return new ReplaySubject(new SizeBoundReplayBuffer(maxSize)); + } + + /** + * Creates an unbounded replay subject with the bounded-implementation for testing purposes. + *

+ * This variant behaves like the regular unbounded {@code ReplaySubject} created via {@link #create()} but + * uses the structures of the bounded-implementation. This is by no means intended for the replacement of + * the original, array-backed and unbounded {@code ReplaySubject} due to the additional overhead of the + * linked-list based internal buffer. The sole purpose is to allow testing and reasoning about the behavior + * of the bounded implementations without the interference of the eviction policies. + * + * @param + * the type of items observed and emitted by the Subject + * @return the created subject + */ + /* test */ static ReplaySubject createUnbounded() { + return new ReplaySubject(new SizeBoundReplayBuffer(Integer.MAX_VALUE)); + } + + /** + * Creates a time-bounded replay subject. + *

+ * In this setting, the {@code ReplaySubject} internally tags each observed item with a timestamp value + * supplied by the {@link Scheduler} and keeps only those whose age is less than the supplied time value + * converted to milliseconds. For example, an item arrives at T=0 and the max age is set to 5; at T>=5 + * this first item is then evicted by any subsequent item or termination event, leaving the buffer empty. + *

+ * Once the subject is terminated, observers subscribing to it will receive items that remained in the + * buffer after the terminal event, regardless of their age. + *

+ * If an observer subscribes while the {@code ReplaySubject} is active, it will observe only those items + * from within the buffer that have an age less than the specified time, and each item observed thereafter, + * even if the buffer evicts items due to the time constraint in the mean time. In other words, once an + * observer subscribes, it observes items without gaps in the sequence except for any outdated items at the + * beginning of the sequence. + *

+ * Note that terminal notifications ({@code onError} and {@code onComplete}) trigger eviction as well. For + * example, with a max age of 5, the first item is observed at T=0, then an {@code onComplete} notification + * arrives at T=10. If an observer subscribes at T=11, it will find an empty {@code ReplaySubject} with just + * an {@code onComplete} notification. + * + * @param + * the type of items observed and emitted by the Subject + * @param maxAge + * the maximum age of the contained items + * @param unit + * the time unit of {@code time} + * @param scheduler + * the {@link Scheduler} that provides the current time + * @return the created subject + */ + @CheckReturnValue + @NonNull + public static ReplaySubject createWithTime(long maxAge, TimeUnit unit, Scheduler scheduler) { + return new ReplaySubject(new SizeAndTimeBoundReplayBuffer(Integer.MAX_VALUE, maxAge, unit, scheduler)); + } + + /** + * Creates a time- and size-bounded replay subject. + *

+ * In this setting, the {@code ReplaySubject} internally tags each received item with a timestamp value + * supplied by the {@link Scheduler} and holds at most {@code size} items in its internal buffer. It evicts + * items from the start of the buffer if their age becomes less-than or equal to the supplied age in + * milliseconds or the buffer reaches its {@code size} limit. + *

+ * When observers subscribe to a terminated {@code ReplaySubject}, they observe the items that remained in + * the buffer after the terminal notification, regardless of their age, but at most {@code size} items. + *

+ * If an observer subscribes while the {@code ReplaySubject} is active, it will observe only those items + * from within the buffer that have age less than the specified time and each subsequent item, even if the + * buffer evicts items due to the time constraint in the mean time. In other words, once an observer + * subscribes, it observes items without gaps in the sequence except for the outdated items at the beginning + * of the sequence. + *

+ * Note that terminal notifications ({@code onError} and {@code onComplete}) trigger eviction as well. For + * example, with a max age of 5, the first item is observed at T=0, then an {@code onComplete} notification + * arrives at T=10. If an observer subscribes at T=11, it will find an empty {@code ReplaySubject} with just + * an {@code onComplete} notification. + * + * @param + * the type of items observed and emitted by the Subject + * @param maxAge + * the maximum age of the contained items + * @param unit + * the time unit of {@code time} + * @param maxSize + * the maximum number of buffered items + * @param scheduler + * the {@link Scheduler} that provides the current time + * @return the created subject + */ + @CheckReturnValue + @NonNull + public static ReplaySubject createWithTimeAndSize(long maxAge, TimeUnit unit, Scheduler scheduler, int maxSize) { + return new ReplaySubject(new SizeAndTimeBoundReplayBuffer(maxSize, maxAge, unit, scheduler)); + } + + /** + * Constructs a ReplayProcessor with the given custom ReplayBuffer instance. + * @param buffer the ReplayBuffer instance, not null (not verified) + */ + @SuppressWarnings("unchecked") + ReplaySubject(ReplayBuffer buffer) { + this.buffer = buffer; + this.observers = new AtomicReference[]>(EMPTY); + } + + @Override + protected void subscribeActual(Observer observer) { + ReplayDisposable rs = new ReplayDisposable(observer, this); + observer.onSubscribe(rs); + + if (!rs.cancelled) { + if (add(rs)) { + if (rs.cancelled) { + remove(rs); + return; + } + } + buffer.replay(rs); + } + } + + @Override + public void onSubscribe(Disposable d) { + if (done) { + d.dispose(); + } + } + + @Override + public void onNext(T t) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + if (done) { + return; + } + + ReplayBuffer b = buffer; + b.add(t); + + for (ReplayDisposable rs : observers.get()) { + b.replay(rs); + } + } + + @Override + public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + + Object o = NotificationLite.error(t); + + ReplayBuffer b = buffer; + + b.addFinal(o); + + for (ReplayDisposable rs : terminate(o)) { + b.replay(rs); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + Object o = NotificationLite.complete(); + + ReplayBuffer b = buffer; + + b.addFinal(o); + + for (ReplayDisposable rs : terminate(o)) { + b.replay(rs); + } + } + + @Override + public boolean hasObservers() { + return observers.get().length != 0; + } + + /* test */ int observerCount() { + return observers.get().length; + } + + @Override + @Nullable + public Throwable getThrowable() { + Object o = buffer.get(); + if (NotificationLite.isError(o)) { + return NotificationLite.getError(o); + } + return null; + } + + /** + * Returns a single value the Subject currently has or null if no such value exists. + *

The method is thread-safe. + * @return a single value the Subject currently has or null if no such value exists + */ + @Nullable + public T getValue() { + return buffer.getValue(); + } + + /** + * Makes sure the item cached by the head node in a bounded + * ReplaySubject is released (as it is never part of a replay). + *

+ * By default, live bounded buffers will remember one item before + * the currently receivable one to ensure subscribers can always + * receive a continuous sequence of items. A terminated ReplaySubject + * automatically releases this inaccessible item. + *

+ * The method must be called sequentially, similar to the standard + * {@code onXXX} methods. + *

History: 2.1.11 - experimental + * @since 2.2 + */ + public void cleanupBuffer() { + buffer.trimHead(); + } + + /** An empty array to avoid allocation in getValues(). */ + private static final Object[] EMPTY_ARRAY = new Object[0]; + + /** + * Returns an Object array containing snapshot all values of the Subject. + *

The method is thread-safe. + * @return the array containing the snapshot of all values of the Subject + */ + public Object[] getValues() { + @SuppressWarnings("unchecked") + T[] a = (T[])EMPTY_ARRAY; + T[] b = getValues(a); + if (b == EMPTY_ARRAY) { + return new Object[0]; + } + return b; + + } + + /** + * Returns a typed array containing a snapshot of all values of the Subject. + *

The method follows the conventions of Collection.toArray by setting the array element + * after the last value to null (if the capacity permits). + *

The method is thread-safe. + * @param array the target array to copy values into if it fits + * @return the given array if the values fit into it or a new array containing all values + */ + public T[] getValues(T[] array) { + return buffer.getValues(array); + } + + @Override + public boolean hasComplete() { + Object o = buffer.get(); + return NotificationLite.isComplete(o); + } + + @Override + public boolean hasThrowable() { + Object o = buffer.get(); + return NotificationLite.isError(o); + } + + /** + * Returns true if the subject has any value. + *

The method is thread-safe. + * @return true if the subject has any value + */ + public boolean hasValue() { + return buffer.size() != 0; // NOPMD + } + + /* test*/ int size() { + return buffer.size(); + } + + boolean add(ReplayDisposable rs) { + for (;;) { + ReplayDisposable[] a = observers.get(); + if (a == TERMINATED) { + return false; + } + int len = a.length; + @SuppressWarnings("unchecked") + ReplayDisposable[] b = new ReplayDisposable[len + 1]; + System.arraycopy(a, 0, b, 0, len); + b[len] = rs; + if (observers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(ReplayDisposable rs) { + for (;;) { + ReplayDisposable[] a = observers.get(); + if (a == TERMINATED || a == EMPTY) { + return; + } + int len = a.length; + int j = -1; + for (int i = 0; i < len; i++) { + if (a[i] == rs) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + ReplayDisposable[] b; + if (len == 1) { + b = EMPTY; + } else { + b = new ReplayDisposable[len - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, len - j - 1); + } + if (observers.compareAndSet(a, b)) { + return; + } + } + } + + @SuppressWarnings("unchecked") + ReplayDisposable[] terminate(Object terminalValue) { + if (buffer.compareAndSet(null, terminalValue)) { + return observers.getAndSet(TERMINATED); + } + return TERMINATED; + } + + /** + * Abstraction over a buffer that receives events and replays them to + * individual Observers. + * + * @param the value type + */ + interface ReplayBuffer { + + void add(T value); + + void addFinal(Object notificationLite); + + void replay(ReplayDisposable rs); + + int size(); + + @Nullable + T getValue(); + + T[] getValues(T[] array); + /** + * Returns the terminal NotificationLite object or null if not yet terminated. + * @return the terminal NotificationLite object or null if not yet terminated + */ + Object get(); + + /** + * Atomically compares and sets the next terminal NotificationLite object if the + * current equals to the expected NotificationLite object. + * @param expected the expected NotificationLite object + * @param next the next NotificationLite object + * @return true if successful + */ + boolean compareAndSet(Object expected, Object next); + + /** + * Make sure an old inaccessible head value is released + * in a bounded buffer. + */ + void trimHead(); + } + + static final class ReplayDisposable extends AtomicInteger implements Disposable { + + private static final long serialVersionUID = 466549804534799122L; + final Observer downstream; + final ReplaySubject state; + + Object index; + + volatile boolean cancelled; + + ReplayDisposable(Observer actual, ReplaySubject state) { + this.downstream = actual; + this.state = state; + } + + @Override + public void dispose() { + if (!cancelled) { + cancelled = true; + state.remove(this); + } + } + + @Override + public boolean isDisposed() { + return cancelled; + } + } + + static final class UnboundedReplayBuffer + extends AtomicReference + implements ReplayBuffer { + + private static final long serialVersionUID = -733876083048047795L; + + final List buffer; + + volatile boolean done; + + volatile int size; + + UnboundedReplayBuffer(int capacityHint) { + this.buffer = new ArrayList(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); + } + + @Override + public void add(T value) { + buffer.add(value); + size++; + } + + @Override + public void addFinal(Object notificationLite) { + buffer.add(notificationLite); + trimHead(); + size++; + done = true; + } + + @Override + public void trimHead() { + // no-op in this type of buffer + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + public T getValue() { + int s = size; + if (s != 0) { + List b = buffer; + Object o = b.get(s - 1); + if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { + if (s == 1) { + return null; + } + return (T)b.get(s - 2); + } + return (T)o; + } + return null; + } + + @Override + @SuppressWarnings("unchecked") + public T[] getValues(T[] array) { + int s = size; + if (s == 0) { + if (array.length != 0) { + array[0] = null; + } + return array; + } + List b = buffer; + Object o = b.get(s - 1); + + if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { + s--; + if (s == 0) { + if (array.length != 0) { + array[0] = null; + } + return array; + } + } + + if (array.length < s) { + array = (T[])Array.newInstance(array.getClass().getComponentType(), s); + } + for (int i = 0; i < s; i++) { + array[i] = (T)b.get(i); + } + if (array.length > s) { + array[s] = null; + } + + return array; + } + + @Override + @SuppressWarnings("unchecked") + public void replay(ReplayDisposable rs) { + if (rs.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final List b = buffer; + final Observer a = rs.downstream; + + Integer indexObject = (Integer)rs.index; + int index; + if (indexObject != null) { + index = indexObject; + } else { + index = 0; + rs.index = 0; + } + + for (;;) { + + if (rs.cancelled) { + rs.index = null; + return; + } + + int s = size; + + while (s != index) { + + if (rs.cancelled) { + rs.index = null; + return; + } + + Object o = b.get(index); + + if (done) { + if (index + 1 == s) { + s = size; + if (index + 1 == s) { + if (NotificationLite.isComplete(o)) { + a.onComplete(); + } else { + a.onError(NotificationLite.getError(o)); + } + rs.index = null; + rs.cancelled = true; + return; + } + } + } + + a.onNext((T)o); + index++; + } + + if (index != size) { + continue; + } + + rs.index = index; + + missed = rs.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public int size() { + int s = size; + if (s != 0) { + Object o = buffer.get(s - 1); + if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { + return s - 1; + } + return s; + } + return 0; + } + } + + static final class Node extends AtomicReference> { + + private static final long serialVersionUID = 6404226426336033100L; + + final T value; + + Node(T value) { + this.value = value; + } + } + + static final class TimedNode extends AtomicReference> { + + private static final long serialVersionUID = 6404226426336033100L; + + final T value; + final long time; + + TimedNode(T value, long time) { + this.value = value; + this.time = time; + } + } + + static final class SizeBoundReplayBuffer + extends AtomicReference + implements ReplayBuffer { + + private static final long serialVersionUID = 1107649250281456395L; + + final int maxSize; + int size; + + volatile Node head; + + Node tail; + + volatile boolean done; + + SizeBoundReplayBuffer(int maxSize) { + this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize"); + Node h = new Node(null); + this.tail = h; + this.head = h; + } + + void trim() { + if (size > maxSize) { + size--; + Node h = head; + head = h.get(); + } + } + + @Override + public void add(T value) { + Node n = new Node(value); + Node t = tail; + + tail = n; + size++; + t.set(n); // releases both the tail and size + + trim(); + } + + @Override + public void addFinal(Object notificationLite) { + Node n = new Node(notificationLite); + Node t = tail; + + tail = n; + size++; + t.lazySet(n); // releases both the tail and size + + trimHead(); + done = true; + } + + /** + * Replace a non-empty head node with an empty one to + * allow the GC of the inaccessible old value. + */ + @Override + public void trimHead() { + Node h = head; + if (h.value != null) { + Node n = new Node(null); + n.lazySet(h.get()); + head = n; + } + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + public T getValue() { + Node prev = null; + Node h = head; + + for (;;) { + Node next = h.get(); + if (next == null) { + break; + } + prev = h; + h = next; + } + + Object v = h.value; + if (v == null) { + return null; + } + if (NotificationLite.isComplete(v) || NotificationLite.isError(v)) { + return (T)prev.value; + } + + return (T)v; + } + + @Override + @SuppressWarnings("unchecked") + public T[] getValues(T[] array) { + Node h = head; + int s = size(); + + if (s == 0) { + if (array.length != 0) { + array[0] = null; + } + } else { + if (array.length < s) { + array = (T[])Array.newInstance(array.getClass().getComponentType(), s); + } + + int i = 0; + while (i != s) { + Node next = h.get(); + array[i] = (T)next.value; + i++; + h = next; + } + if (array.length > s) { + array[s] = null; + } + } + + return array; + } + + @Override + @SuppressWarnings("unchecked") + public void replay(ReplayDisposable rs) { + if (rs.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final Observer a = rs.downstream; + + Node index = (Node)rs.index; + if (index == null) { + index = head; + } + + for (;;) { + + for (;;) { + if (rs.cancelled) { + rs.index = null; + return; + } + + Node n = index.get(); + + if (n == null) { + break; + } + + Object o = n.value; + + if (done) { + if (n.get() == null) { + + if (NotificationLite.isComplete(o)) { + a.onComplete(); + } else { + a.onError(NotificationLite.getError(o)); + } + rs.index = null; + rs.cancelled = true; + return; + } + } + + a.onNext((T)o); + + index = n; + } + + if (index.get() != null) { + continue; + } + + rs.index = index; + + missed = rs.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public int size() { + int s = 0; + Node h = head; + while (s != Integer.MAX_VALUE) { + Node next = h.get(); + if (next == null) { + Object o = h.value; + if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { + s--; + } + break; + } + s++; + h = next; + } + + return s; + } + } + + static final class SizeAndTimeBoundReplayBuffer + extends AtomicReference + implements ReplayBuffer { + + private static final long serialVersionUID = -8056260896137901749L; + + final int maxSize; + final long maxAge; + final TimeUnit unit; + final Scheduler scheduler; + int size; + + volatile TimedNode head; + + TimedNode tail; + + volatile boolean done; + + SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) { + this.maxSize = ObjectHelper.verifyPositive(maxSize, "maxSize"); + this.maxAge = ObjectHelper.verifyPositive(maxAge, "maxAge"); + this.unit = ObjectHelper.requireNonNull(unit, "unit is null"); + this.scheduler = ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + TimedNode h = new TimedNode(null, 0L); + this.tail = h; + this.head = h; + } + + void trim() { + if (size > maxSize) { + size--; + TimedNode h = head; + head = h.get(); + } + long limit = scheduler.now(unit) - maxAge; + + TimedNode h = head; + + for (;;) { + if (size <= 1) { + head = h; + break; + } + TimedNode next = h.get(); + if (next == null) { + head = h; + break; + } + + if (next.time > limit) { + head = h; + break; + } + + h = next; + size--; + } + + } + + void trimFinal() { + long limit = scheduler.now(unit) - maxAge; + + TimedNode h = head; + + for (;;) { + TimedNode next = h.get(); + if (next.get() == null) { + if (h.value != null) { + TimedNode lasth = new TimedNode(null, 0L); + lasth.lazySet(h.get()); + head = lasth; + } else { + head = h; + } + break; + } + + if (next.time > limit) { + if (h.value != null) { + TimedNode lasth = new TimedNode(null, 0L); + lasth.lazySet(h.get()); + head = lasth; + } else { + head = h; + } + break; + } + + h = next; + } + } + + @Override + public void add(T value) { + TimedNode n = new TimedNode(value, scheduler.now(unit)); + TimedNode t = tail; + + tail = n; + size++; + t.set(n); // releases both the tail and size + + trim(); + } + + @Override + public void addFinal(Object notificationLite) { + TimedNode n = new TimedNode(notificationLite, Long.MAX_VALUE); + TimedNode t = tail; + + tail = n; + size++; + t.lazySet(n); // releases both the tail and size + trimFinal(); + + done = true; + } + + /** + * Replace a non-empty head node with an empty one to + * allow the GC of the inaccessible old value. + */ + @Override + public void trimHead() { + TimedNode h = head; + if (h.value != null) { + TimedNode n = new TimedNode(null, 0); + n.lazySet(h.get()); + head = n; + } + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + public T getValue() { + TimedNode prev = null; + TimedNode h = head; + + for (;;) { + TimedNode next = h.get(); + if (next == null) { + break; + } + prev = h; + h = next; + } + + long limit = scheduler.now(unit) - maxAge; + if (h.time < limit) { + return null; + } + + Object v = h.value; + if (v == null) { + return null; + } + if (NotificationLite.isComplete(v) || NotificationLite.isError(v)) { + return (T)prev.value; + } + + return (T)v; + } + + TimedNode getHead() { + TimedNode index = head; + // skip old entries + long limit = scheduler.now(unit) - maxAge; + TimedNode next = index.get(); + while (next != null) { + long ts = next.time; + if (ts > limit) { + break; + } + index = next; + next = index.get(); + } + return index; + } + + @Override + @SuppressWarnings("unchecked") + public T[] getValues(T[] array) { + TimedNode h = getHead(); + int s = size(h); + + if (s == 0) { + if (array.length != 0) { + array[0] = null; + } + } else { + if (array.length < s) { + array = (T[])Array.newInstance(array.getClass().getComponentType(), s); + } + + int i = 0; + while (i != s) { + TimedNode next = h.get(); + array[i] = (T)next.value; + i++; + h = next; + } + if (array.length > s) { + array[s] = null; + } + } + + return array; + } + + @Override + @SuppressWarnings("unchecked") + public void replay(ReplayDisposable rs) { + if (rs.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final Observer a = rs.downstream; + + TimedNode index = (TimedNode)rs.index; + if (index == null) { + index = getHead(); + } + + for (;;) { + + if (rs.cancelled) { + rs.index = null; + return; + } + + for (;;) { + if (rs.cancelled) { + rs.index = null; + return; + } + + TimedNode n = index.get(); + + if (n == null) { + break; + } + + Object o = n.value; + + if (done) { + if (n.get() == null) { + + if (NotificationLite.isComplete(o)) { + a.onComplete(); + } else { + a.onError(NotificationLite.getError(o)); + } + rs.index = null; + rs.cancelled = true; + return; + } + } + + a.onNext((T)o); + + index = n; + } + + if (index.get() != null) { + continue; + } + + rs.index = index; + + missed = rs.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + @Override + public int size() { + return size(getHead()); + } + + int size(TimedNode h) { + int s = 0; + while (s != Integer.MAX_VALUE) { + TimedNode next = h.get(); + if (next == null) { + Object o = h.value; + if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) { + s--; + } + break; + } + s++; + h = next; + } + + return s; + } + } +} diff --git a/src/main/java/io/reactivex/subjects/SerializedSubject.java b/src/main/java/io/reactivex/subjects/SerializedSubject.java new file mode 100755 index 0000000..ec4263b --- /dev/null +++ b/src/main/java/io/reactivex/subjects/SerializedSubject.java @@ -0,0 +1,205 @@ +/** + * 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.subjects; + +import io.reactivex.Observer; +import io.reactivex.annotations.Nullable; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.util.*; +import io.reactivex.internal.util.AppendOnlyLinkedArrayList.NonThrowingPredicate; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Serializes calls to the Observer methods. + *

All other Observable and Subject methods are thread-safe by design. + * + * @param the item value type + */ +/* public */ final class SerializedSubject extends Subject implements NonThrowingPredicate { + /** The actual subscriber to serialize Subscriber calls to. */ + final Subject actual; + /** Indicates an emission is going on, guarded by this. */ + boolean emitting; + /** If not null, it holds the missed NotificationLite events. */ + AppendOnlyLinkedArrayList queue; + /** Indicates a terminal event has been received and all further events will be dropped. */ + volatile boolean done; + + /** + * Constructor that wraps an actual subject. + * @param actual the subject wrapped + */ + SerializedSubject(final Subject actual) { + this.actual = actual; + } + + @Override + protected void subscribeActual(Observer observer) { + actual.subscribe(observer); + } + + @Override + public void onSubscribe(Disposable d) { + boolean cancel; + if (!done) { + synchronized (this) { + if (done) { + cancel = true; + } else { + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.add(NotificationLite.disposable(d)); + return; + } + emitting = true; + cancel = false; + } + } + } else { + cancel = true; + } + if (cancel) { + d.dispose(); + } else { + actual.onSubscribe(d); + emitLoop(); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + synchronized (this) { + if (done) { + return; + } + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.add(NotificationLite.next(t)); + return; + } + emitting = true; + } + actual.onNext(t); + emitLoop(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + boolean reportError; + synchronized (this) { + if (done) { + reportError = true; + } else { + done = true; + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.setFirst(NotificationLite.error(t)); + return; + } + reportError = false; + emitting = true; + } + } + if (reportError) { + RxJavaPlugins.onError(t); + return; + } + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + synchronized (this) { + if (done) { + return; + } + done = true; + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(4); + queue = q; + } + q.add(NotificationLite.complete()); + return; + } + emitting = true; + } + actual.onComplete(); + } + + /** Loops until all notifications in the queue has been processed. */ + void emitLoop() { + for (;;) { + AppendOnlyLinkedArrayList q; + synchronized (this) { + q = queue; + if (q == null) { + emitting = false; + return; + } + queue = null; + } + q.forEachWhile(this); + } + } + + @Override + public boolean test(Object o) { + return NotificationLite.acceptFull(o, actual); + } + + @Override + public boolean hasObservers() { + return actual.hasObservers(); + } + + @Override + public boolean hasThrowable() { + return actual.hasThrowable(); + } + + @Override + @Nullable + public Throwable getThrowable() { + return actual.getThrowable(); + } + + @Override + public boolean hasComplete() { + return actual.hasComplete(); + } +} diff --git a/src/main/java/io/reactivex/subjects/SingleSubject.java b/src/main/java/io/reactivex/subjects/SingleSubject.java new file mode 100755 index 0000000..2914052 --- /dev/null +++ b/src/main/java/io/reactivex/subjects/SingleSubject.java @@ -0,0 +1,312 @@ +/** + * 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.subjects; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.annotations.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Represents a hot Single-like source and consumer of events similar to Subjects. + *

+ * + *

+ * This subject does not have a public constructor by design; a new non-terminated instance of this + * {@code SingleSubject} can be created via the {@link #create()} method. + *

+ * Since the {@code SingleSubject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (Rule 2.13) + * as parameters to {@link #onSuccess(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + *

+ * Since a {@code SingleSubject} is a {@link Single}, calling {@code onSuccess} or {@code onError} + * will move this {@code SingleSubject} into its terminal state atomically. + *

+ * All methods are thread safe. Calling {@link #onSuccess(Object)} multiple + * times has no effect. Calling {@link #onError(Throwable)} multiple times relays the {@code Throwable} to + * the {@link RxJavaPlugins#onError(Throwable)} global error handler. + *

+ * Even though {@code SingleSubject} implements the {@code SingleObserver} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code SingleSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + *

+ * This {@code SingleSubject} supports the standard state-peeking methods {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()} as well as means to read any success item in a non-blocking + * and thread-safe manner via {@link #hasValue()} and {@link #getValue()}. + *

+ * The {@code SingleSubject} does not support clearing its cached {@code onSuccess} value. + *

+ *
Scheduler:
+ *
{@code SingleSubject} does not operate by default on a particular {@link Scheduler} and + * the {@code SingleObserver}s get notified on the thread where the terminating {@code onSuccess} or {@code onError} + * methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code SingleSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the last set of {@code SingleObserver}s. During this emission, + * if one or more {@code SingleObserver}s dispose their respective {@code Disposable}s, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} (multiple times if multiple {@code SingleObserver}s + * cancel at once). + * If there were no {@code SingleObserver}s subscribed to this {@code SingleSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *


+ * SingleSubject<Integer> subject1 = SingleSubject.create();
+ *
+ * TestObserver<Integer> to1 = subject1.test();
+ *
+ * // SingleSubjects are empty by default
+ * to1.assertEmpty();
+ *
+ * subject1.onSuccess(1);
+ *
+ * // onSuccess is a terminal event with SingleSubjects
+ * // TestObserver converts onSuccess into onNext + onComplete
+ * to1.assertResult(1);
+ *
+ * TestObserver<Integer> to2 = subject1.test();
+ *
+ * // late Observers receive the terminal signal (onSuccess) too
+ * to2.assertResult(1);
+ * 
+ *

History: 2.0.5 - experimental + * @param the value type received and emitted + * @since 2.1 + */ +public final class SingleSubject extends Single implements SingleObserver { + + final AtomicReference[]> observers; + + @SuppressWarnings("rawtypes") + static final SingleDisposable[] EMPTY = new SingleDisposable[0]; + + @SuppressWarnings("rawtypes") + static final SingleDisposable[] TERMINATED = new SingleDisposable[0]; + + final AtomicBoolean once; + T value; + Throwable error; + + /** + * Creates a fresh SingleSubject. + * @param the value type received and emitted + * @return the new SingleSubject instance + */ + @CheckReturnValue + @NonNull + public static SingleSubject create() { + return new SingleSubject(); + } + + @SuppressWarnings("unchecked") + SingleSubject() { + once = new AtomicBoolean(); + observers = new AtomicReference[]>(EMPTY); + } + + @Override + public void onSubscribe(@NonNull Disposable d) { + if (observers.get() == TERMINATED) { + d.dispose(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onSuccess(@NonNull T value) { + ObjectHelper.requireNonNull(value, "onSuccess called with null. Null values are generally not allowed in 2.x operators and sources."); + if (once.compareAndSet(false, true)) { + this.value = value; + for (SingleDisposable md : observers.getAndSet(TERMINATED)) { + md.downstream.onSuccess(value); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void onError(@NonNull Throwable e) { + ObjectHelper.requireNonNull(e, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (once.compareAndSet(false, true)) { + this.error = e; + for (SingleDisposable md : observers.getAndSet(TERMINATED)) { + md.downstream.onError(e); + } + } else { + RxJavaPlugins.onError(e); + } + } + + @Override + protected void subscribeActual(@NonNull SingleObserver observer) { + SingleDisposable md = new SingleDisposable(observer, this); + observer.onSubscribe(md); + if (add(md)) { + if (md.isDisposed()) { + remove(md); + } + } else { + Throwable ex = error; + if (ex != null) { + observer.onError(ex); + } else { + observer.onSuccess(value); + } + } + } + + boolean add(@NonNull SingleDisposable inner) { + for (;;) { + SingleDisposable[] a = observers.get(); + if (a == TERMINATED) { + return false; + } + + int n = a.length; + @SuppressWarnings("unchecked") + SingleDisposable[] b = new SingleDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (observers.compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(@NonNull SingleDisposable inner) { + for (;;) { + SingleDisposable[] a = observers.get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + SingleDisposable[] b; + if (n == 1) { + b = EMPTY; + } else { + b = new SingleDisposable[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + + if (observers.compareAndSet(a, b)) { + return; + } + } + } + + /** + * Returns the success value if this SingleSubject was terminated with a success value. + * @return the success value or null + */ + @Nullable + public T getValue() { + if (observers.get() == TERMINATED) { + return value; + } + return null; + } + + /** + * Returns true if this SingleSubject was terminated with a success value. + * @return true if this SingleSubject was terminated with a success value + */ + public boolean hasValue() { + return observers.get() == TERMINATED && value != null; + } + + /** + * Returns the terminal error if this SingleSubject has been terminated with an error, null otherwise. + * @return the terminal error or null if not terminated or not with an error + */ + @Nullable + public Throwable getThrowable() { + if (observers.get() == TERMINATED) { + return error; + } + return null; + } + + /** + * Returns true if this SingleSubject has been terminated with an error. + * @return true if this SingleSubject has been terminated with an error + */ + public boolean hasThrowable() { + return observers.get() == TERMINATED && error != null; + } + + /** + * Returns true if this SingleSubject has observers. + * @return true if this SingleSubject has observers + */ + public boolean hasObservers() { + return observers.get().length != 0; + } + + /** + * Returns the number of current observers. + * @return the number of current observers + */ + /* test */ int observerCount() { + return observers.get().length; + } + + static final class SingleDisposable + extends AtomicReference> implements Disposable { + private static final long serialVersionUID = -7650903191002190468L; + + final SingleObserver downstream; + + SingleDisposable(SingleObserver actual, SingleSubject parent) { + this.downstream = actual; + lazySet(parent); + } + + @Override + public void dispose() { + SingleSubject parent = getAndSet(null); + if (parent != null) { + parent.remove(this); + } + } + + @Override + public boolean isDisposed() { + return get() == null; + } + } +} diff --git a/src/main/java/io/reactivex/subjects/Subject.java b/src/main/java/io/reactivex/subjects/Subject.java new file mode 100755 index 0000000..4a57a4a --- /dev/null +++ b/src/main/java/io/reactivex/subjects/Subject.java @@ -0,0 +1,77 @@ +/** + * 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.subjects; + +import io.reactivex.*; +import io.reactivex.annotations.*; + +/** + * Represents an {@link Observer} and an {@link Observable} at the same time, allowing + * multicasting events from a single source to multiple child {@code Observer}s. + *

+ * All methods except the {@link #onSubscribe(io.reactivex.disposables.Disposable)}, {@link #onNext(Object)}, + * {@link #onError(Throwable)} and {@link #onComplete()} are thread-safe. + * Use {@link #toSerialized()} to make these methods thread-safe as well. + * + * @param the item value type + */ +public abstract class Subject extends Observable implements Observer { + /** + * Returns true if the subject has any Observers. + *

The method is thread-safe. + * @return true if the subject has any Observers + */ + public abstract boolean hasObservers(); + + /** + * Returns true if the subject has reached a terminal state through an error event. + *

The method is thread-safe. + * @return true if the subject has reached a terminal state through an error event + * @see #getThrowable() + * @see #hasComplete() + */ + public abstract boolean hasThrowable(); + + /** + * Returns true if the subject has reached a terminal state through a complete event. + *

The method is thread-safe. + * @return true if the subject has reached a terminal state through a complete event + * @see #hasThrowable() + */ + public abstract boolean hasComplete(); + + /** + * Returns the error that caused the Subject to terminate or null if the Subject + * hasn't terminated yet. + *

The method is thread-safe. + * @return the error that caused the Subject to terminate or null if the Subject + * hasn't terminated yet + */ + @Nullable + public abstract Throwable getThrowable(); + + /** + * Wraps this Subject and serializes the calls to the onSubscribe, onNext, onError and + * onComplete methods, making them thread-safe. + *

The method is thread-safe. + * @return the wrapped and serialized subject + */ + @NonNull + public final Subject toSerialized() { + if (this instanceof SerializedSubject) { + return this; + } + return new SerializedSubject(this); + } +} diff --git a/src/main/java/io/reactivex/subjects/UnicastSubject.java b/src/main/java/io/reactivex/subjects/UnicastSubject.java new file mode 100755 index 0000000..c4ec0f7 --- /dev/null +++ b/src/main/java/io/reactivex/subjects/UnicastSubject.java @@ -0,0 +1,573 @@ +/** + * 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.subjects; + +import io.reactivex.annotations.Nullable; +import io.reactivex.annotations.NonNull; +import io.reactivex.plugins.RxJavaPlugins; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.annotations.CheckReturnValue; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.fuseable.SimpleQueue; +import io.reactivex.internal.observers.BasicIntQueueDisposable; +import io.reactivex.internal.queue.SpscLinkedArrayQueue; + +/** + * A Subject that queues up events until a single {@link Observer} subscribes to it, replays + * those events to it until the {@code Observer} catches up and then switches to relaying events live to + * this single {@code Observer} until this {@code UnicastSubject} terminates or the {@code Observer} unsubscribes. + *

+ * + *

+ * Note that {@code UnicastSubject} holds an unbounded internal buffer. + *

+ * This subject does not have a public constructor by design; a new empty instance of this + * {@code UnicastSubject} can be created via the following {@code create} methods that + * allow specifying the retention policy for items: + *

    + *
  • {@link #create()} - creates an empty, unbounded {@code UnicastSubject} that + * caches all items and the terminal event it receives.
  • + *
  • {@link #create(int)} - creates an empty, unbounded {@code UnicastSubject} + * with a hint about how many total items one expects to retain.
  • + *
  • {@link #create(boolean)} - creates an empty, unbounded {@code UnicastSubject} that + * optionally delays an error it receives and replays it after the regular items have been emitted.
  • + *
  • {@link #create(int, Runnable)} - creates an empty, unbounded {@code UnicastSubject} + * with a hint about how many total items one expects to retain and a callback that will be + * called exactly once when the {@code UnicastSubject} gets terminated or the single {@code Observer} unsubscribes.
  • + *
  • {@link #create(int, Runnable, boolean)} - creates an empty, unbounded {@code UnicastSubject} + * with a hint about how many total items one expects to retain and a callback that will be + * called exactly once when the {@code UnicastSubject} gets terminated or the single {@code Observer} unsubscribes + * and optionally delays an error it receives and replays it after the regular items have been emitted.
  • + *
+ *

+ * If more than one {@code Observer} attempts to subscribe to this {@code UnicastSubject}, they + * will receive an {@code IllegalStateException} indicating the single-use-only nature of this {@code UnicastSubject}, + * even if the {@code UnicastSubject} already terminated with an error. + *

+ * Since a {@code Subject} is conceptionally derived from the {@code Processor} type in the Reactive Streams specification, + * {@code null}s are not allowed (Rule 2.13) as + * parameters to {@link #onNext(Object)} and {@link #onError(Throwable)}. Such calls will result in a + * {@link NullPointerException} being thrown and the subject's state is not changed. + *

+ * Since a {@code UnicastSubject} is an {@link Observable}, it does not support backpressure. + *

+ * When this {@code UnicastSubject} is terminated via {@link #onError(Throwable)} the current or late single {@code Observer} + * may receive the {@code Throwable} before any available items could be emitted. To make sure an onError event is delivered + * to the {@code Observer} after the normal items, create a {@code UnicastSubject} with the {@link #create(boolean)} or + * {@link #create(int, Runnable, boolean)} factory methods. + *

+ * Even though {@code UnicastSubject} implements the {@code Observer} interface, calling + * {@code onSubscribe} is not required (Rule 2.12) + * if the subject is used as a standalone source. However, calling {@code onSubscribe} + * after the {@code UnicastSubject} reached its terminal state will result in the + * given {@code Disposable} being disposed immediately. + *

+ * Calling {@link #onNext(Object)}, {@link #onError(Throwable)} and {@link #onComplete()} + * is required to be serialized (called from the same thread or called non-overlappingly from different threads + * through external means of serialization). The {@link #toSerialized()} method available to all {@code Subject}s + * provides such serialization and also protects against reentrance (i.e., when a downstream {@code Observer} + * consuming this subject also wants to call {@link #onNext(Object)} on this subject recursively). + *

+ * This {@code UnicastSubject} supports the standard state-peeking methods {@link #hasComplete()}, {@link #hasThrowable()}, + * {@link #getThrowable()} and {@link #hasObservers()}. + *

+ *
Scheduler:
+ *
{@code UnicastSubject} does not operate by default on a particular {@link Scheduler} and + * the single {@code Observer} gets notified on the thread the respective {@code onXXX} methods were invoked.
+ *
Error handling:
+ *
When the {@link #onError(Throwable)} is called, the {@code UnicastSubject} enters into a terminal state + * and emits the same {@code Throwable} instance to the current single {@code Observer}. During this emission, + * if the single {@code Observer}s disposes its respective {@code Disposable}, the + * {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)}. + * If there were no {@code Observer}s subscribed to this {@code UnicastSubject} when the {@code onError()} + * was called, the global error handler is not invoked. + *
+ *
+ *

+ * Example usage: + *


+ * UnicastSubject<Integer> subject = UnicastSubject.create();
+ *
+ * TestObserver<Integer> to1 = subject.test();
+ *
+ * // fresh UnicastSubjects are empty
+ * to1.assertEmpty();
+ *
+ * TestObserver<Integer> to2 = subject.test();
+ *
+ * // A UnicastSubject only allows one Observer during its lifetime
+ * to2.assertFailure(IllegalStateException.class);
+ *
+ * subject.onNext(1);
+ * to1.assertValue(1);
+ *
+ * subject.onNext(2);
+ * to1.assertValues(1, 2);
+ *
+ * subject.onComplete();
+ * to1.assertResult(1, 2);
+ *
+ * // ----------------------------------------------------
+ *
+ * UnicastSubject<Integer> subject2 = UnicastSubject.create();
+ *
+ * // a UnicastSubject caches events until its single Observer subscribes
+ * subject2.onNext(1);
+ * subject2.onNext(2);
+ * subject2.onComplete();
+ *
+ * TestObserver<Integer> to3 = subject2.test();
+ *
+ * // the cached events are emitted in order
+ * to3.assertResult(1, 2);
+ * 
+ * @param the value type received and emitted by this Subject subclass + * @since 2.0 + */ +public final class UnicastSubject extends Subject { + /** The queue that buffers the source events. */ + final SpscLinkedArrayQueue queue; + + /** The single Observer. */ + final AtomicReference> downstream; + + /** The optional callback when the Subject gets cancelled or terminates. */ + final AtomicReference onTerminate; + + /** deliver onNext events before error event. */ + final boolean delayError; + + /** Indicates the single observer has cancelled. */ + volatile boolean disposed; + + /** Indicates the source has terminated. */ + volatile boolean done; + /** + * The terminal error if not null. + * Must be set before writing to done and read after done == true. + */ + Throwable error; + + /** Set to 1 atomically for the first and only Subscriber. */ + final AtomicBoolean once; + + /** The wip counter and QueueDisposable surface. */ + final BasicIntQueueDisposable wip; + + boolean enableOperatorFusion; + + /** + * Creates an UnicastSubject with an internal buffer capacity hint 16. + * @param the value type + * @return an UnicastSubject instance + */ + @CheckReturnValue + @NonNull + public static UnicastSubject create() { + return new UnicastSubject(bufferSize(), true); + } + + /** + * Creates an UnicastSubject with the given internal buffer capacity hint. + * @param the value type + * @param capacityHint the hint to size the internal unbounded buffer + * @return an UnicastSubject instance + */ + @CheckReturnValue + @NonNull + public static UnicastSubject create(int capacityHint) { + return new UnicastSubject(capacityHint, true); + } + + /** + * Creates an UnicastSubject with the given internal buffer capacity hint and a callback for + * the case when the single Subscriber cancels its subscription. + * + *

The callback, if not null, is called exactly once and + * non-overlapped with any active replay. + * + * @param the value type + * @param capacityHint the hint to size the internal unbounded buffer + * @param onTerminate the callback to run when the Subject is terminated or cancelled, null not allowed + * @return an UnicastSubject instance + */ + @CheckReturnValue + @NonNull + public static UnicastSubject create(int capacityHint, Runnable onTerminate) { + return new UnicastSubject(capacityHint, onTerminate, true); + } + + /** + * Creates an UnicastSubject with the given internal buffer capacity hint, delay error flag and + * a callback for the case when the single Subscriber cancels its subscription. + * + *

The callback, if not null, is called exactly once and + * non-overlapped with any active replay. + *

History: 2.0.8 - experimental + * @param the value type + * @param capacityHint the hint to size the internal unbounded buffer + * @param onTerminate the callback to run when the Subject is terminated or cancelled, null not allowed + * @param delayError deliver pending onNext events before onError + * @return an UnicastSubject instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public static UnicastSubject create(int capacityHint, Runnable onTerminate, boolean delayError) { + return new UnicastSubject(capacityHint, onTerminate, delayError); + } + + /** + * Creates an UnicastSubject with an internal buffer capacity hint 16 and given delay error flag. + * + *

The callback, if not null, is called exactly once and + * non-overlapped with any active replay. + *

History: 2.0.8 - experimental + * @param the value type + * @param delayError deliver pending onNext events before onError + * @return an UnicastSubject instance + * @since 2.2 + */ + @CheckReturnValue + @NonNull + public static UnicastSubject create(boolean delayError) { + return new UnicastSubject(bufferSize(), delayError); + } + + /** + * Creates an UnicastSubject with the given capacity hint and delay error flag. + *

History: 2.0.8 - experimental + * @param capacityHint the capacity hint for the internal, unbounded queue + * @param delayError deliver pending onNext events before onError + * @since 2.2 + */ + UnicastSubject(int capacityHint, boolean delayError) { + this.queue = new SpscLinkedArrayQueue(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); + this.onTerminate = new AtomicReference(); + this.delayError = delayError; + this.downstream = new AtomicReference>(); + this.once = new AtomicBoolean(); + this.wip = new UnicastQueueDisposable(); + } + + /** + * Creates an UnicastSubject with the given capacity hint and callback + * for when the Subject is terminated normally or its single Subscriber cancels. + * @param capacityHint the capacity hint for the internal, unbounded queue + * @param onTerminate the callback to run when the Subject is terminated or cancelled, null not allowed + * @since 2.0 + * + * */ + UnicastSubject(int capacityHint, Runnable onTerminate) { + this(capacityHint, onTerminate, true); + } + + /** + * Creates an UnicastSubject with the given capacity hint, delay error flag and callback + * for when the Subject is terminated normally or its single Subscriber cancels. + *

History: 2.0.8 - experimental + * @param capacityHint the capacity hint for the internal, unbounded queue + * @param onTerminate the callback to run when the Subject is terminated or cancelled, null not allowed + * @param delayError deliver pending onNext events before onError + * @since 2.2 + */ + UnicastSubject(int capacityHint, Runnable onTerminate, boolean delayError) { + this.queue = new SpscLinkedArrayQueue(ObjectHelper.verifyPositive(capacityHint, "capacityHint")); + this.onTerminate = new AtomicReference(ObjectHelper.requireNonNull(onTerminate, "onTerminate")); + this.delayError = delayError; + this.downstream = new AtomicReference>(); + this.once = new AtomicBoolean(); + this.wip = new UnicastQueueDisposable(); + } + + @Override + protected void subscribeActual(Observer observer) { + if (!once.get() && once.compareAndSet(false, true)) { + observer.onSubscribe(wip); + downstream.lazySet(observer); // full barrier in drain + if (disposed) { + downstream.lazySet(null); + return; + } + drain(); + } else { + EmptyDisposable.error(new IllegalStateException("Only a single observer allowed."), observer); + } + } + + void doTerminate() { + Runnable r = onTerminate.get(); + if (r != null && onTerminate.compareAndSet(r, null)) { + r.run(); + } + } + + @Override + public void onSubscribe(Disposable d) { + if (done || disposed) { + d.dispose(); + } + } + + @Override + public void onNext(T t) { + ObjectHelper.requireNonNull(t, "onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + if (done || disposed) { + return; + } + queue.offer(t); + drain(); + } + + @Override + public void onError(Throwable t) { + ObjectHelper.requireNonNull(t, "onError called with null. Null values are generally not allowed in 2.x operators and sources."); + if (done || disposed) { + RxJavaPlugins.onError(t); + return; + } + error = t; + done = true; + + doTerminate(); + + drain(); + } + + @Override + public void onComplete() { + if (done || disposed) { + return; + } + done = true; + + doTerminate(); + + drain(); + } + + void drainNormal(Observer a) { + int missed = 1; + SimpleQueue q = queue; + boolean failFast = !this.delayError; + boolean canBeError = true; + for (;;) { + for (;;) { + + if (disposed) { + downstream.lazySet(null); + q.clear(); + return; + } + + boolean d = this.done; + T v = queue.poll(); + boolean empty = v == null; + + if (d) { + if (failFast && canBeError) { + if (failedFast(q, a)) { + return; + } else { + canBeError = false; + } + } + + if (empty) { + errorOrComplete(a); + return; + } + } + + if (empty) { + break; + } + + a.onNext(v); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void drainFused(Observer a) { + int missed = 1; + + final SpscLinkedArrayQueue q = queue; + final boolean failFast = !delayError; + + for (;;) { + + if (disposed) { + downstream.lazySet(null); + return; + } + boolean d = done; + + if (failFast && d) { + if (failedFast(q, a)) { + return; + } + } + + a.onNext(null); + + if (d) { + errorOrComplete(a); + return; + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void errorOrComplete(Observer a) { + downstream.lazySet(null); + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } else { + a.onComplete(); + } + } + + boolean failedFast(final SimpleQueue q, Observer a) { + Throwable ex = error; + if (ex != null) { + downstream.lazySet(null); + q.clear(); + a.onError(ex); + return true; + } else { + return false; + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + Observer a = downstream.get(); + int missed = 1; + + for (;;) { + + if (a != null) { + if (enableOperatorFusion) { + drainFused(a); + } else { + drainNormal(a); + } + return; + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + + a = downstream.get(); + } + } + + @Override + public boolean hasObservers() { + return downstream.get() != null; + } + + @Override + @Nullable + public Throwable getThrowable() { + if (done) { + return error; + } + return null; + } + + @Override + public boolean hasThrowable() { + return done && error != null; + } + + @Override + public boolean hasComplete() { + return done && error == null; + } + + final class UnicastQueueDisposable extends BasicIntQueueDisposable { + + private static final long serialVersionUID = 7926949470189395511L; + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + enableOperatorFusion = true; + return ASYNC; + } + return NONE; + } + + @Nullable + @Override + public T poll() throws Exception { + return queue.poll(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public void dispose() { + if (!disposed) { + disposed = true; + + doTerminate(); + + downstream.lazySet(null); + if (wip.getAndIncrement() == 0) { + downstream.lazySet(null); + if (!enableOperatorFusion) { + queue.clear(); + } + } + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + + } +} diff --git a/src/main/java/io/reactivex/subjects/package-info.java b/src/main/java/io/reactivex/subjects/package-info.java new file mode 100755 index 0000000..091c223 --- /dev/null +++ b/src/main/java/io/reactivex/subjects/package-info.java @@ -0,0 +1,58 @@ +/** + * 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. + */ + +/** + * Classes representing so-called hot sources, aka subjects, that implement a base reactive class and + * the respective consumer type at once to allow forms of multicasting events to multiple + * consumers as well as consuming another base reactive type of their kind. + *

+ * Available subject classes with their respective base classes and consumer interfaces: + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Subject typeBase classConsumer interface
{@link io.reactivex.subjects.Subject Subject} + *
   {@link io.reactivex.subjects.AsyncSubject AsyncSubject} + *
   {@link io.reactivex.subjects.BehaviorSubject BehaviorSubject} + *
   {@link io.reactivex.subjects.PublishSubject PublishSubject} + *
   {@link io.reactivex.subjects.ReplaySubject ReplaySubject} + *
   {@link io.reactivex.subjects.UnicastSubject UnicastSubject} + *
{@link io.reactivex.Observable Observable}{@link io.reactivex.Observer Observer}
{@link io.reactivex.subjects.SingleSubject SingleSubject}{@link io.reactivex.Single Single}{@link io.reactivex.SingleObserver SingleObserver}
{@link io.reactivex.subjects.MaybeSubject MaybeSubject}{@link io.reactivex.Maybe Maybe}{@link io.reactivex.MaybeObserver MaybeObserver}
{@link io.reactivex.subjects.CompletableSubject CompletableSubject}{@link io.reactivex.Completable Completable}{@link io.reactivex.CompletableObserver CompletableObserver}
+ *

+ * The backpressure-aware variants of the {@code Subject} class are called + * {@link org.reactivestreams.Processor}s and reside in the {@code io.reactivex.processors} package. + * @see io.reactivex.processors + */ +package io.reactivex.subjects; diff --git a/src/main/java/io/reactivex/subscribers/DefaultSubscriber.java b/src/main/java/io/reactivex/subscribers/DefaultSubscriber.java new file mode 100755 index 0000000..3038a95 --- /dev/null +++ b/src/main/java/io/reactivex/subscribers/DefaultSubscriber.java @@ -0,0 +1,116 @@ +/** + * 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.subscribers; + +import org.reactivestreams.Subscription; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * Abstract base implementation of a {@link org.reactivestreams.Subscriber Subscriber} with + * support for requesting via {@link #request(long)}, cancelling via + * via {@link #cancel()} (both synchronously) and calls {@link #onStart()} + * when the subscription happens. + * + *

All pre-implemented final methods are thread-safe. + * + *

The default {@link #onStart()} requests Long.MAX_VALUE by default. Override + * the method to request a custom positive amount. + * + *

Note that calling {@link #request(long)} from {@link #onStart()} may trigger + * an immediate, asynchronous emission of data to {@link #onNext(Object)}. Make sure + * all initialization happens before the call to {@code request()} in {@code onStart()}. + * Calling {@link #request(long)} inside {@link #onNext(Object)} can happen at any time + * because by design, {@code onNext} calls from upstream are non-reentrant and non-overlapping. + * + *

Use the protected {@link #cancel()} to cancel the sequence from within an + * {@code onNext} implementation. + * + *

Like all other consumers, {@code DefaultSubscriber} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onNext(Object)}, {@link #onError(Throwable)} + * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * If for some reason this can't be avoided, use {@link io.reactivex.Flowable#safeSubscribe(org.reactivestreams.Subscriber)} + * instead of the standard {@code subscribe()} method. + * @param the value type + * + *

Example


+ * Flowable.range(1, 5)
+ *     .subscribe(new DefaultSubscriber<Integer>() {
+ *         @Override public void onStart() {
+ *             System.out.println("Start!");
+ *             request(1);
+ *         }
+ *         @Override public void onNext(Integer t) {
+ *             if (t == 3) {
+ *                 cancel();
+ *             }
+ *             System.out.println(t);
+ *             request(1);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *         }
+ *     });
+ * 
+ */ +public abstract class DefaultSubscriber implements FlowableSubscriber { + + Subscription upstream; + + @Override + public final void onSubscribe(Subscription s) { + if (EndConsumerHelper.validate(this.upstream, s, getClass())) { + this.upstream = s; + onStart(); + } + } + + /** + * Requests from the upstream Subscription. + * @param n the request amount, positive + */ + protected final void request(long n) { + Subscription s = this.upstream; + if (s != null) { + s.request(n); + } + } + + /** + * Cancels the upstream's Subscription. + */ + protected final void cancel() { + Subscription s = this.upstream; + this.upstream = SubscriptionHelper.CANCELLED; + s.cancel(); + } + /** + * Called once the subscription has been set on this observer; override this + * to perform initialization or issue an initial request. + *

+ * The default implementation requests {@link Long#MAX_VALUE}. + */ + protected void onStart() { + request(Long.MAX_VALUE); + } + +} diff --git a/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java b/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java new file mode 100755 index 0000000..076dc94 --- /dev/null +++ b/src/main/java/io/reactivex/subscribers/DisposableSubscriber.java @@ -0,0 +1,123 @@ +/** + * 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.subscribers; + +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Subscription; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract Subscriber that allows asynchronous, external cancellation by implementing Disposable. + * + *

All pre-implemented final methods are thread-safe. + * + *

The default {@link #onStart()} requests Long.MAX_VALUE by default. Override + * the method to request a custom positive amount. Use the protected {@link #request(long)} + * to request more items and {@link #cancel()} to cancel the sequence from within an + * {@code onNext} implementation. + * + *

Note that calling {@link #request(long)} from {@link #onStart()} may trigger + * an immediate, asynchronous emission of data to {@link #onNext(Object)}. Make sure + * all initialization happens before the call to {@code request()} in {@code onStart()}. + * Calling {@link #request(long)} inside {@link #onNext(Object)} can happen at any time + * because by design, {@code onNext} calls from upstream are non-reentrant and non-overlapping. + * + *

Like all other consumers, {@code DisposableSubscriber} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onNext(Object)}, {@link #onError(Throwable)} + * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * If for some reason this can't be avoided, use {@link io.reactivex.Flowable#safeSubscribe(org.reactivestreams.Subscriber)} + * instead of the standard {@code subscribe()} method. + * + *

Example


+ * Disposable d =
+ *     Flowable.range(1, 5)
+ *     .subscribeWith(new DisposableSubscriber<Integer>() {
+ *         @Override public void onStart() {
+ *             request(1);
+ *         }
+ *         @Override public void onNext(Integer t) {
+ *             if (t == 3) {
+ *                 cancel();
+ *             }
+ *             System.out.println(t);
+ *             request(1);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ * @param the received value type. + */ +public abstract class DisposableSubscriber implements FlowableSubscriber, Disposable { + final AtomicReference upstream = new AtomicReference(); + + @Override + public final void onSubscribe(Subscription s) { + if (EndConsumerHelper.setOnce(this.upstream, s, getClass())) { + onStart(); + } + } + + /** + * Called once the single upstream Subscription is set via onSubscribe. + */ + protected void onStart() { + upstream.get().request(Long.MAX_VALUE); + } + + /** + * Requests the specified amount from the upstream if its Subscription is set via + * onSubscribe already. + *

Note that calling this method before a Subscription is set via onSubscribe + * leads to NullPointerException and meant to be called from inside onStart or + * onNext. + * @param n the request amount, positive + */ + protected final void request(long n) { + upstream.get().request(n); + } + + /** + * Cancels the Subscription set via onSubscribe or makes sure a + * Subscription set asynchronously (later) is cancelled immediately. + *

This method is thread-safe and can be exposed as a public API. + */ + protected final void cancel() { + dispose(); + } + + @Override + public final boolean isDisposed() { + return upstream.get() == SubscriptionHelper.CANCELLED; + } + + @Override + public final void dispose() { + SubscriptionHelper.cancel(upstream); + } +} diff --git a/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java b/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java new file mode 100755 index 0000000..1b083d1 --- /dev/null +++ b/src/main/java/io/reactivex/subscribers/ResourceSubscriber.java @@ -0,0 +1,172 @@ +/** + * 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.subscribers; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.Subscription; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.internal.disposables.ListCompositeDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.EndConsumerHelper; + +/** + * An abstract Subscriber that allows asynchronous cancellation of its + * subscription and associated resources. + * + *

All pre-implemented final methods are thread-safe. + * + *

To release the associated resources, one has to call {@link #dispose()} + * in {@code onError()} and {@code onComplete()} explicitly. + * + *

Use {@link #add(Disposable)} to associate resources (as {@link Disposable Disposable}s) + * with this {@code ResourceSubscriber} that will be cleaned up when {@link #dispose()} is called. + * Removing previously associated resources is not possible but one can create a + * {@link io.reactivex.disposables.CompositeDisposable CompositeDisposable}, associate it with this + * {@code ResourceSubscriber} and then add/remove resources to/from the {@code CompositeDisposable} + * freely. + * + *

The default {@link #onStart()} requests Long.MAX_VALUE by default. Override + * the method to request a custom positive amount. Use the protected {@link #request(long)} + * to request more items and {@link #dispose()} to cancel the sequence from within an + * {@code onNext} implementation. + * + *

Note that calling {@link #request(long)} from {@link #onStart()} may trigger + * an immediate, asynchronous emission of data to {@link #onNext(Object)}. Make sure + * all initialization happens before the call to {@code request()} in {@code onStart()}. + * Calling {@link #request(long)} inside {@link #onNext(Object)} can happen at any time + * because by design, {@code onNext} calls from upstream are non-reentrant and non-overlapping. + * + *

Like all other consumers, {@code ResourceSubscriber} can be subscribed only once. + * Any subsequent attempt to subscribe it to a new source will yield an + * {@link IllegalStateException} with message {@code "It is not allowed to subscribe with a(n) multiple times."}. + * + *

Implementation of {@link #onStart()}, {@link #onNext(Object)}, {@link #onError(Throwable)} + * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. + * If for some reason this can't be avoided, use {@link io.reactivex.Flowable#safeSubscribe(org.reactivestreams.Subscriber)} + * instead of the standard {@code subscribe()} method. + * + *

Example


+ * Disposable d =
+ *     Flowable.range(1, 5)
+ *     .subscribeWith(new ResourceSubscriber<Integer>() {
+ *         @Override public void onStart() {
+ *             add(Schedulers.single()
+ *                 .scheduleDirect(() -> System.out.println("Time!"),
+ *                     2, TimeUnit.SECONDS));
+ *             request(1);
+ *         }
+ *         @Override public void onNext(Integer t) {
+ *             if (t == 3) {
+ *                 dispose();
+ *             }
+ *             System.out.println(t);
+ *             request(1);
+ *         }
+ *         @Override public void onError(Throwable t) {
+ *             t.printStackTrace();
+ *             dispose();
+ *         }
+ *         @Override public void onComplete() {
+ *             System.out.println("Done!");
+ *             dispose();
+ *         }
+ *     });
+ * // ...
+ * d.dispose();
+ * 
+ * + * @param the value type + */ +public abstract class ResourceSubscriber implements FlowableSubscriber, Disposable { + /** The active subscription. */ + private final AtomicReference upstream = new AtomicReference(); + + /** The resource composite, can never be null. */ + private final ListCompositeDisposable resources = new ListCompositeDisposable(); + + /** Remembers the request(n) counts until a subscription arrives. */ + private final AtomicLong missedRequested = new AtomicLong(); + + /** + * Adds a resource to this AsyncObserver. + * + * @param resource the resource to add + * + * @throws NullPointerException if resource is null + */ + public final void add(Disposable resource) { + ObjectHelper.requireNonNull(resource, "resource is null"); + resources.add(resource); + } + + @Override + public final void onSubscribe(Subscription s) { + if (EndConsumerHelper.setOnce(this.upstream, s, getClass())) { + long r = missedRequested.getAndSet(0L); + if (r != 0L) { + s.request(r); + } + onStart(); + } + } + + /** + * Called once the upstream sets a Subscription on this AsyncObserver. + * + *

You can perform initialization at this moment. The default + * implementation requests Long.MAX_VALUE from upstream. + */ + protected void onStart() { + request(Long.MAX_VALUE); + } + + /** + * Request the specified amount of elements from upstream. + * + *

This method can be called before the upstream calls onSubscribe(). + * When the subscription happens, all missed requests are requested. + * + * @param n the request amount, must be positive + */ + protected final void request(long n) { + SubscriptionHelper.deferredRequest(upstream, missedRequested, n); + } + + /** + * Cancels the subscription (if any) and disposes the resources associated with + * this AsyncObserver (if any). + * + *

This method can be called before the upstream calls onSubscribe at which + * case the Subscription will be immediately cancelled. + */ + @Override + public final void dispose() { + if (SubscriptionHelper.cancel(upstream)) { + resources.dispose(); + } + } + + /** + * Returns true if this AsyncObserver has been disposed/cancelled. + * @return true if this AsyncObserver has been disposed/cancelled + */ + @Override + public final boolean isDisposed() { + return upstream.get() == SubscriptionHelper.CANCELLED; + } +} diff --git a/src/main/java/io/reactivex/subscribers/SafeSubscriber.java b/src/main/java/io/reactivex/subscribers/SafeSubscriber.java new file mode 100755 index 0000000..e903a55 --- /dev/null +++ b/src/main/java/io/reactivex/subscribers/SafeSubscriber.java @@ -0,0 +1,234 @@ +/** + * 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.subscribers; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.exceptions.*; +import io.reactivex.internal.subscriptions.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Wraps another Subscriber and ensures all onXXX methods conform the protocol + * (except the requirement for serialized access). + * + * @param the value type + */ +public final class SafeSubscriber implements FlowableSubscriber, Subscription { + /** The actual Subscriber. */ + final Subscriber downstream; + /** The subscription. */ + Subscription upstream; + /** Indicates a terminal state. */ + boolean done; + + /** + * Constructs a SafeSubscriber by wrapping the given actual Subscriber. + * @param downstream the actual Subscriber to wrap, not null (not validated) + */ + public SafeSubscriber(Subscriber downstream) { + this.downstream = downstream; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + try { + downstream.onSubscribe(this); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + done = true; + // can't call onError because the actual's state may be corrupt at this point + try { + s.cancel(); + } catch (Throwable e1) { + Exceptions.throwIfFatal(e1); + RxJavaPlugins.onError(new CompositeException(e, e1)); + return; + } + RxJavaPlugins.onError(e); + } + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (upstream == null) { + onNextNoSubscription(); + return; + } + + if (t == null) { + Throwable ex = new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."); + try { + upstream.cancel(); + } catch (Throwable e1) { + Exceptions.throwIfFatal(e1); + onError(new CompositeException(ex, e1)); + return; + } + onError(ex); + return; + } + + try { + downstream.onNext(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + try { + upstream.cancel(); + } catch (Throwable e1) { + Exceptions.throwIfFatal(e1); + onError(new CompositeException(e, e1)); + return; + } + onError(e); + } + } + + void onNextNoSubscription() { + done = true; + Throwable ex = new NullPointerException("Subscription not set!"); + + try { + downstream.onSubscribe(EmptySubscription.INSTANCE); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because the actual's state may be corrupt at this point + RxJavaPlugins.onError(new CompositeException(ex, e)); + return; + } + try { + downstream.onError(ex); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // if onError failed, all that's left is to report the error to plugins + RxJavaPlugins.onError(new CompositeException(ex, e)); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + + if (upstream == null) { + Throwable npe = new NullPointerException("Subscription not set!"); + + try { + downstream.onSubscribe(EmptySubscription.INSTANCE); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because the actual's state may be corrupt at this point + RxJavaPlugins.onError(new CompositeException(t, npe, e)); + return; + } + try { + downstream.onError(new CompositeException(t, npe)); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // if onError failed, all that's left is to report the error to plugins + RxJavaPlugins.onError(new CompositeException(t, npe, e)); + } + return; + } + + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + + try { + downstream.onError(t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + + RxJavaPlugins.onError(new CompositeException(t, ex)); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + if (upstream == null) { + onCompleteNoSubscription(); + return; + } + + try { + downstream.onComplete(); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + RxJavaPlugins.onError(e); + } + } + + void onCompleteNoSubscription() { + + Throwable ex = new NullPointerException("Subscription not set!"); + + try { + downstream.onSubscribe(EmptySubscription.INSTANCE); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because the actual's state may be corrupt at this point + RxJavaPlugins.onError(new CompositeException(ex, e)); + return; + } + try { + downstream.onError(ex); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // if onError failed, all that's left is to report the error to plugins + RxJavaPlugins.onError(new CompositeException(ex, e)); + } + } + + @Override + public void request(long n) { + try { + upstream.request(n); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + try { + upstream.cancel(); + } catch (Throwable e1) { + Exceptions.throwIfFatal(e1); + RxJavaPlugins.onError(new CompositeException(e, e1)); + return; + } + RxJavaPlugins.onError(e); + } + } + + @Override + public void cancel() { + try { + upstream.cancel(); + } catch (Throwable e1) { + Exceptions.throwIfFatal(e1); + RxJavaPlugins.onError(e1); + } + } +} diff --git a/src/main/java/io/reactivex/subscribers/SerializedSubscriber.java b/src/main/java/io/reactivex/subscribers/SerializedSubscriber.java new file mode 100755 index 0000000..4e1b147 --- /dev/null +++ b/src/main/java/io/reactivex/subscribers/SerializedSubscriber.java @@ -0,0 +1,199 @@ +/** + * 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.subscribers; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Serializes access to the onNext, onError and onComplete methods of another Subscriber. + * + *

Note that {@link #onSubscribe(Subscription)} is not serialized in respect of the other methods so + * make sure the {@code onSubscribe} is called with a non-null {@code Subscription} + * before any of the other methods are called. + * + *

The implementation assumes that the actual Subscriber's methods don't throw. + * + * @param the value type + */ +public final class SerializedSubscriber implements FlowableSubscriber, Subscription { + final Subscriber downstream; + final boolean delayError; + + static final int QUEUE_LINK_SIZE = 4; + + Subscription upstream; + + boolean emitting; + AppendOnlyLinkedArrayList queue; + + volatile boolean done; + + /** + * Construct a SerializedSubscriber by wrapping the given actual Subscriber. + * @param downstream the actual Subscriber, not null (not verified) + */ + public SerializedSubscriber(Subscriber downstream) { + this(downstream, false); + } + + /** + * Construct a SerializedSubscriber by wrapping the given actual Observer and + * optionally delaying the errors till all regular values have been emitted + * from the internal buffer. + * @param actual the actual Subscriber, not null (not verified) + * @param delayError if true, errors are emitted after regular values have been emitted + */ + public SerializedSubscriber(Subscriber actual, boolean delayError) { + this.downstream = actual; + this.delayError = delayError; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.validate(this.upstream, s)) { + this.upstream = s; + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + if (t == null) { + upstream.cancel(); + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + synchronized (this) { + if (done) { + return; + } + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(QUEUE_LINK_SIZE); + queue = q; + } + q.add(NotificationLite.next(t)); + return; + } + emitting = true; + } + + downstream.onNext(t); + + emitLoop(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + boolean reportError; + synchronized (this) { + if (done) { + reportError = true; + } else + if (emitting) { + done = true; + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(QUEUE_LINK_SIZE); + queue = q; + } + Object err = NotificationLite.error(t); + if (delayError) { + q.add(err); + } else { + q.setFirst(err); + } + return; + } else { + done = true; + emitting = true; + reportError = false; + } + } + + if (reportError) { + RxJavaPlugins.onError(t); + return; + } + + downstream.onError(t); + // no need to loop because this onError is the last event + } + + @Override + public void onComplete() { + if (done) { + return; + } + synchronized (this) { + if (done) { + return; + } + if (emitting) { + AppendOnlyLinkedArrayList q = queue; + if (q == null) { + q = new AppendOnlyLinkedArrayList(QUEUE_LINK_SIZE); + queue = q; + } + q.add(NotificationLite.complete()); + return; + } + done = true; + emitting = true; + } + + downstream.onComplete(); + // no need to loop because this onComplete is the last event + } + + void emitLoop() { + for (;;) { + AppendOnlyLinkedArrayList q; + synchronized (this) { + q = queue; + if (q == null) { + emitting = false; + return; + } + queue = null; + } + + if (q.accept(downstream)) { + return; + } + } + } + + @Override + public void request(long n) { + upstream.request(n); + } + + @Override + public void cancel() { + upstream.cancel(); + } +} diff --git a/src/main/java/io/reactivex/subscribers/TestSubscriber.java b/src/main/java/io/reactivex/subscribers/TestSubscriber.java new file mode 100755 index 0000000..3b02dbd --- /dev/null +++ b/src/main/java/io/reactivex/subscribers/TestSubscriber.java @@ -0,0 +1,444 @@ +/** + * 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.subscribers; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.fuseable.QueueSubscription; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.observers.BaseTestConsumer; + +/** + * A subscriber that records events and allows making assertions about them. + * + *

You can override the onSubscribe, onNext, onError, onComplete, request and + * cancel methods but not the others (this is by design). + * + *

The TestSubscriber implements Disposable for convenience where dispose calls cancel. + * + *

When calling the default request method, you are requesting on behalf of the + * wrapped actual subscriber. + * + * @param the value type + */ +public class TestSubscriber +extends BaseTestConsumer> +implements FlowableSubscriber, Subscription, Disposable { + /** The actual subscriber to forward events to. */ + private final Subscriber downstream; + + /** Makes sure the incoming Subscriptions get cancelled immediately. */ + private volatile boolean cancelled; + + /** Holds the current subscription if any. */ + private final AtomicReference upstream; + + /** Holds the requested amount until a subscription arrives. */ + private final AtomicLong missedRequested; + + private QueueSubscription qs; + + /** + * Creates a TestSubscriber with Long.MAX_VALUE initial request. + * @param the value type + * @return the new TestSubscriber instance. + */ + public static TestSubscriber create() { + return new TestSubscriber(); + } + + /** + * Creates a TestSubscriber with the given initial request. + * @param the value type + * @param initialRequested the initial requested amount + * @return the new TestSubscriber instance. + */ + public static TestSubscriber create(long initialRequested) { + return new TestSubscriber(initialRequested); + } + + /** + * Constructs a forwarding TestSubscriber. + * @param the value type received + * @param delegate the actual Subscriber to forward events to + * @return the new TestObserver instance + */ + public static TestSubscriber create(Subscriber delegate) { + return new TestSubscriber(delegate); + } + + /** + * Constructs a non-forwarding TestSubscriber with an initial request value of Long.MAX_VALUE. + */ + public TestSubscriber() { + this(EmptySubscriber.INSTANCE, Long.MAX_VALUE); + } + + /** + * Constructs a non-forwarding TestSubscriber with the specified initial request value. + *

The TestSubscriber doesn't validate the initialRequest value so one can + * test sources with invalid values as well. + * @param initialRequest the initial request value + */ + public TestSubscriber(long initialRequest) { + this(EmptySubscriber.INSTANCE, initialRequest); + } + + /** + * Constructs a forwarding TestSubscriber but leaves the requesting to the wrapped subscriber. + * @param downstream the actual Subscriber to forward events to + */ + public TestSubscriber(Subscriber downstream) { + this(downstream, Long.MAX_VALUE); + } + + /** + * Constructs a forwarding TestSubscriber with the specified initial request value. + *

The TestSubscriber doesn't validate the initialRequest value so one can + * test sources with invalid values as well. + * @param actual the actual Subscriber to forward events to + * @param initialRequest the initial request value + */ + public TestSubscriber(Subscriber actual, long initialRequest) { + super(); + if (initialRequest < 0) { + throw new IllegalArgumentException("Negative initial request not allowed"); + } + this.downstream = actual; + this.upstream = new AtomicReference(); + this.missedRequested = new AtomicLong(initialRequest); + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + lastThread = Thread.currentThread(); + + if (s == null) { + errors.add(new NullPointerException("onSubscribe received a null Subscription")); + return; + } + if (!upstream.compareAndSet(null, s)) { + s.cancel(); + if (upstream.get() != SubscriptionHelper.CANCELLED) { + errors.add(new IllegalStateException("onSubscribe received multiple subscriptions: " + s)); + } + return; + } + + if (initialFusionMode != 0) { + if (s instanceof QueueSubscription) { + qs = (QueueSubscription)s; + + int m = qs.requestFusion(initialFusionMode); + establishedFusionMode = m; + + if (m == QueueSubscription.SYNC) { + checkSubscriptionOnce = true; + lastThread = Thread.currentThread(); + try { + T t; + while ((t = qs.poll()) != null) { + values.add(t); + } + completions++; + } catch (Throwable ex) { + // Exceptions.throwIfFatal(e); TODO add fatal exceptions? + errors.add(ex); + } + return; + } + } + } + + downstream.onSubscribe(s); + + long mr = missedRequested.getAndSet(0L); + if (mr != 0L) { + s.request(mr); + } + + onStart(); + } + + /** + * Called after the onSubscribe is called and handled. + */ + protected void onStart() { + + } + + @Override + public void onNext(T t) { + if (!checkSubscriptionOnce) { + checkSubscriptionOnce = true; + if (upstream.get() == null) { + errors.add(new IllegalStateException("onSubscribe not called in proper order")); + } + } + lastThread = Thread.currentThread(); + + if (establishedFusionMode == QueueSubscription.ASYNC) { + try { + while ((t = qs.poll()) != null) { + values.add(t); + } + } catch (Throwable ex) { + // Exceptions.throwIfFatal(e); TODO add fatal exceptions? + errors.add(ex); + qs.cancel(); + } + return; + } + + values.add(t); + + if (t == null) { + errors.add(new NullPointerException("onNext received a null value")); + } + + downstream.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (!checkSubscriptionOnce) { + checkSubscriptionOnce = true; + if (upstream.get() == null) { + errors.add(new NullPointerException("onSubscribe not called in proper order")); + } + } + try { + lastThread = Thread.currentThread(); + errors.add(t); + + if (t == null) { + errors.add(new IllegalStateException("onError received a null Throwable")); + } + + downstream.onError(t); + } finally { + done.countDown(); + } + } + + @Override + public void onComplete() { + if (!checkSubscriptionOnce) { + checkSubscriptionOnce = true; + if (upstream.get() == null) { + errors.add(new IllegalStateException("onSubscribe not called in proper order")); + } + } + try { + lastThread = Thread.currentThread(); + completions++; + + downstream.onComplete(); + } finally { + done.countDown(); + } + } + + @Override + public final void request(long n) { + SubscriptionHelper.deferredRequest(upstream, missedRequested, n); + } + + @Override + public final void cancel() { + if (!cancelled) { + cancelled = true; + SubscriptionHelper.cancel(upstream); + } + } + + /** + * Returns true if this TestSubscriber has been cancelled. + * @return true if this TestSubscriber has been cancelled + */ + public final boolean isCancelled() { + return cancelled; + } + + @Override + public final void dispose() { + cancel(); + } + + @Override + public final boolean isDisposed() { + return cancelled; + } + + // state retrieval methods + + /** + * Returns true if this TestSubscriber received a subscription. + * @return true if this TestSubscriber received a subscription + */ + public final boolean hasSubscription() { + return upstream.get() != null; + } + + // assertion methods + + /** + * Assert that the onSubscribe method was called exactly once. + * @return this + */ + @Override + public final TestSubscriber assertSubscribed() { + if (upstream.get() == null) { + throw fail("Not subscribed!"); + } + return this; + } + + /** + * Assert that the onSubscribe method hasn't been called at all. + * @return this + */ + @Override + public final TestSubscriber assertNotSubscribed() { + if (upstream.get() != null) { + throw fail("Subscribed!"); + } else + if (!errors.isEmpty()) { + throw fail("Not subscribed but errors found"); + } + return this; + } + + /** + * Sets the initial fusion mode if the upstream supports fusion. + *

Package-private: avoid leaking the now internal fusion properties into the public API. + * Use SubscriberFusion to work with such tests. + * @param mode the mode to establish, see the {@link QueueSubscription} constants + * @return this + */ + final TestSubscriber setInitialFusionMode(int mode) { + this.initialFusionMode = mode; + return this; + } + + /** + * Asserts that the given fusion mode has been established + *

Package-private: avoid leaking the now internal fusion properties into the public API. + * Use SubscriberFusion to work with such tests. + * @param mode the expected mode + * @return this + */ + final TestSubscriber assertFusionMode(int mode) { + int m = establishedFusionMode; + if (m != mode) { + if (qs != null) { + throw new AssertionError("Fusion mode different. Expected: " + fusionModeToString(mode) + + ", actual: " + fusionModeToString(m)); + } else { + throw fail("Upstream is not fuseable"); + } + } + return this; + } + + static String fusionModeToString(int mode) { + switch (mode) { + case QueueSubscription.NONE : return "NONE"; + case QueueSubscription.SYNC : return "SYNC"; + case QueueSubscription.ASYNC : return "ASYNC"; + default: return "Unknown(" + mode + ")"; + } + } + + /** + * Assert that the upstream is a fuseable source. + *

Package-private: avoid leaking the now internal fusion properties into the public API. + * Use SubscriberFusion to work with such tests. + * @return this + */ + final TestSubscriber assertFuseable() { + if (qs == null) { + throw new AssertionError("Upstream is not fuseable."); + } + return this; + } + + /** + * Assert that the upstream is not a fuseable source. + *

Package-private: avoid leaking the now internal fusion properties into the public API. + * Use SubscriberFusion to work with such tests. + * @return this + */ + final TestSubscriber assertNotFuseable() { + if (qs != null) { + throw new AssertionError("Upstream is fuseable."); + } + return this; + } + + /** + * Run a check consumer with this TestSubscriber instance. + * @param check the check consumer to run + * @return this + */ + public final TestSubscriber assertOf(Consumer> check) { + try { + check.accept(this); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + return this; + } + + /** + * Calls {@link #request(long)} and returns this. + *

History: 2.0.1 - experimental + * @param n the request amount + * @return this + * @since 2.1 + */ + public final TestSubscriber requestMore(long n) { + request(n); + return this; + } + + /** + * A subscriber that ignores all events and does not report errors. + */ + enum EmptySubscriber implements FlowableSubscriber { + INSTANCE; + + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + } +} diff --git a/src/main/java/io/reactivex/subscribers/package-info.java b/src/main/java/io/reactivex/subscribers/package-info.java new file mode 100755 index 0000000..409efd6 --- /dev/null +++ b/src/main/java/io/reactivex/subscribers/package-info.java @@ -0,0 +1,23 @@ +/** + * 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. + */ + +/** + * Default wrappers and implementations for Subscriber-based consumer classes and interfaces, + * including disposable and resource-tracking variants and + * the {@link io.reactivex.subscribers.TestSubscriber} that allows unit testing + * {@link io.reactivex.Flowable}-based flows. + */ +package io.reactivex.subscribers; diff --git a/src/main/java/net/educoder/DebuggerApplication.java b/src/main/java/net/educoder/DebuggerApplication.java new file mode 100644 index 0000000..0bf2242 --- /dev/null +++ b/src/main/java/net/educoder/DebuggerApplication.java @@ -0,0 +1,18 @@ +package net.educoder; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @Author: youys + * @Date: 2023/4/6 + * @Description: 启动类-入口 + */ +@SpringBootApplication +public class DebuggerApplication { + + public static void main(String[] args) { + SpringApplication.run(DebuggerApplication.class, args); + } + +} diff --git a/src/main/java/net/educoder/debugger/Debugger.java b/src/main/java/net/educoder/debugger/Debugger.java new file mode 100644 index 0000000..2ca84c7 --- /dev/null +++ b/src/main/java/net/educoder/debugger/Debugger.java @@ -0,0 +1,373 @@ +package net.educoder.debugger; + + +import com.microsoft.java.debug.core.DebugEvent; +import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.IBreakpoint; +import com.microsoft.java.debug.core.IDebugSession; +import com.sun.jdi.*; +import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.StepEvent; +import com.sun.jdi.request.StepRequest; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + + +/** + * @Author: youys + * @Date: 2023/4/6 + * @Description: + */ +public class Debugger { + + private static final String SERIALIZER_CLASS = "__Serializer__"; + private static final String SERIALIZE_DYNAMIC_RESOLVE = "serializeDynamicResolve"; + private Map breakpoints = new HashMap<>(); + private static final String OBJECT_TO_CLASS = "objectToString"; + private static IDebugSession debugSession; + private DebugEvent lastDebugEventFromVM; + private String inputFilePath = ""; + private String outputFilePath = ""; + private StepRequest stepRequest; + private Boolean isRunning; + + public Debugger(String paramString1, String paramString2) { + debugSession = getDebugSession(paramString1, paramString2); + this.isRunning = Boolean.valueOf(true); + + debugSession.getEventHub().events().subscribe(paramDebugEvent -> { + System.out.println("Event - " + paramDebugEvent.event.getClass().getName()); + Event event = paramDebugEvent.event; + synchronized (debugSession) { + if (event instanceof BreakpointEvent || event instanceof StepEvent || event instanceof com.sun.jdi.event.VMDeathEvent || event instanceof com.sun.jdi.event.VMDisconnectEvent) { + paramDebugEvent.shouldResume = false; + } + if (event instanceof com.sun.jdi.event.VMDisconnectEvent || event instanceof com.sun.jdi.event.VMDeathEvent) { + this.isRunning = Boolean.valueOf(false); + } + this.lastDebugEventFromVM = paramDebugEvent; + if (!paramDebugEvent.shouldResume) { + debugSession.notifyAll(); + } + } + }); + } + + + protected static IDebugSession getDebugSession(String paramString1, String paramString2) { + try { + VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager(); + debugSession = DebugUtility.launch(virtualMachineManager, paramString1, "", "--enable-preview", null, paramString2, "", null); + debugSession.getEventHub().events().subscribe(paramDebugEvent -> { + if (paramDebugEvent.event instanceof com.sun.jdi.event.VMDisconnectEvent) { + try { +// debugSession.getEventHub().close(); + } catch (Exception exception) { + } + } + }); + + + return debugSession; + } catch (Exception exception) { + return null; + } + } + + private ThreadReference getThreadFromLastEvent() throws Exception { + ThreadReference threadReference; + Event event = this.lastDebugEventFromVM.event; + + if (event instanceof BreakpointEvent) { + threadReference = ((BreakpointEvent) event).thread(); + } else if (event instanceof StepEvent) { + threadReference = ((StepEvent) event).thread(); + } else { + throw new Exception("Unknown type of event - " + event.toString()); + } + return threadReference; + } + + private void writeToProcessStdin(String paramString) throws IOException { + Process process = debugSession.process(); + OutputStream outputStream = process.getOutputStream(); + outputStream.write(paramString.getBytes()); + outputStream.close(); + } + + public boolean isRunning() { + return this.isRunning.booleanValue(); + } + + public void startProgram() throws Exception { + Process process = debugSession.process(); + + if (this.inputFilePath != "") { + Path path = Paths.get(this.inputFilePath, new String[0]); + String str = new String(Files.readAllBytes(path)); + + OutputStream outputStream = process.getOutputStream(); + outputStream.write(str.getBytes()); + outputStream.close(); + } + + if (this.outputFilePath != "") { + StreamGobbler streamGobbler1 = new StreamGobbler(process.getErrorStream(), this.outputFilePath); + StreamGobbler streamGobbler2 = new StreamGobbler(process.getInputStream(), this.outputFilePath); + streamGobbler2.start(); + streamGobbler1.start(); + } + + debugSession.start(); + } + + public void setBreakPoint(String className, Integer lineNo) throws Exception { + IBreakpoint iBreakpoint = debugSession.createBreakpoint(className, lineNo.intValue(), 0, null, null); + iBreakpoint.install(); + this.breakpoints.put(className + ":" + lineNo, iBreakpoint); + } + + public void removeBreakPoint(String className, Integer lineNo) throws Exception { + String str = className + ":" + lineNo; + IBreakpoint iBreakpoint = this.breakpoints.get(str); + iBreakpoint.close(); + this.breakpoints.remove(str); + } + + public ArrayList> getBreakpoints() { + ArrayList> arrayList = new ArrayList(); + Set set = this.breakpoints.keySet(); + Iterator iterator = set.iterator(); + while (iterator.hasNext()) { + String str1 = iterator.next(); + String[] arrayOfString = str1.split(":"); + String str2 = arrayOfString[0]; + String str3 = arrayOfString[1]; + arrayList.add(new ArrayList(Arrays.asList((Object[]) new String[]{str2, str3}))); + } + return arrayList; + } + + public void runContinue() throws Exception { + cleanPreviousStepRequest(); + ThreadReference threadReference = getThreadFromLastEvent(); + resetLastDebugEvent(); + threadReference.resume(); + } + + private void cleanPreviousStepRequest() throws Exception { + if (this.stepRequest != null) { + this.stepRequest.disable(); + } + } + + public void runStepInto() throws Exception { + cleanPreviousStepRequest(); + ThreadReference threadReference = getThreadFromLastEvent(); + this.stepRequest = DebugUtility.createStepIntoRequest(threadReference, new String[0]); + this.stepRequest.enable(); + resetLastDebugEvent(); + threadReference.resume(); + } + + public void runStepOver() throws Exception { + cleanPreviousStepRequest(); + ThreadReference threadReference = getThreadFromLastEvent(); + this.stepRequest = DebugUtility.createStepOverRequest(threadReference, new String[0]); + this.stepRequest.enable(); + resetLastDebugEvent(); + threadReference.resume(); + } + + public void runStepOut() throws Exception { + cleanPreviousStepRequest(); + ThreadReference threadReference = getThreadFromLastEvent(); + this.stepRequest = DebugUtility.createStepOutRequest(threadReference, new String[0]); + this.stepRequest.enable(); + resetLastDebugEvent(); + threadReference.resume(); + } + + private void resetLastDebugEvent() { + this.lastDebugEventFromVM = null; + } + + public void waitForVMPause() throws InterruptedException { + while (true) { + synchronized (debugSession) { + if (!isRunning()) { + return; + } + + if (this.lastDebugEventFromVM != null && !this.lastDebugEventFromVM.shouldResume) { + return; + } + + debugSession.wait(1L); + } + } + } + + private String callSerializerMethod(String paramString, List paramList) throws Exception { + VirtualMachine virtualMachine = debugSession.getVM(); + List list = virtualMachine.classesByName(SERIALIZER_CLASS); + + ClassType classType = (ClassType) list.get(0); + + List list1 = classType.methodsByName(paramString); + Method method = list1.get(0); + + ThreadReference threadReference = getThreadFromLastEvent(); + + + Value value = classType.invokeMethod(threadReference, method, paramList, 1); + String str = value.toString(); + + str = str.substring(1, str.length() - 1); + return str; + } + + private String serializeDynamicResolve(Value paramValue) { + try { + List list = Arrays.asList(new Value[]{paramValue}); + return callSerializerMethod(SERIALIZE_DYNAMIC_RESOLVE, list); + } catch (Exception exception) { + return null; + } + } + + private String objectToString(Value paramValue) { + try { + List list = Arrays.asList(new Value[]{paramValue}); + return callSerializerMethod(OBJECT_TO_CLASS, list); + } catch (Exception exception) { + + try { + return paramValue.toString(); + } catch (Exception exception1) { + return null; + } + } + } + + private String serialiseVariableValue(Value paramValue) { + String str = "null"; + if (paramValue == null) { + return str; + } + + str = serializeDynamicResolve(paramValue); + if (str != null) { + return str; + } + + str = objectToString(paramValue); + if (str != null) { + return str; + } + + return "Unable to serialise this object."; + } + + private ArrayList getLocalVariablesWithException() throws Exception { + ArrayList arrayList = new ArrayList(); + ThreadReference threadReference = getThreadFromLastEvent(); + StackFrame stackFrame = threadReference.frame(0); + if (stackFrame.location().method().isNative()) { + return arrayList; + } + + try { + for (LocalVariable localVariable : stackFrame.visibleVariables()) { + + stackFrame = threadReference.frame(0); + String str1 = localVariable.name(); + String str2 = serialiseVariableValue(stackFrame.getValue(localVariable)); + Variable variable = new Variable(str1, str2); + arrayList.add(variable); + } + } catch (AbsentInformationException absentInformationException) { + absentInformationException.printStackTrace(); + + List list = stackFrame.getArgumentValues(); + if (list == null) { + return arrayList; + } + + byte b = 0; + for (Value value : list) { + Variable variable = new Variable("arg" + b, serialiseVariableValue(value)); + arrayList.add(variable); + b++; + } + + return arrayList; + } + + return arrayList; + } + + public ArrayList getLocalVariables() { + try { + return getLocalVariablesWithException(); + } catch (Exception exception) { + exception.printStackTrace(); + return new ArrayList<>(); + } + } + + private Location getLocation() throws Exception { + ThreadReference threadReference = getThreadFromLastEvent(); + StackFrame stackFrame = threadReference.frame(0); + return stackFrame.location(); + } + + public String getLocationFilename() { + try { + Location location = getLocation(); + return location.sourceName(); + } catch (Exception exception) { + exception.printStackTrace(); + + return ""; + } + } + + public Integer getLocationLineNumber() { + try { + Location location = getLocation(); + return Integer.valueOf(location.lineNumber()); + } catch (Exception exception) { + exception.printStackTrace(); + + return Integer.valueOf(-1); + } + } + + public String getStatus() { + try { + ThreadReference threadReference = getThreadFromLastEvent(); + if (threadReference.frameCount() > 0) { + return "PAUSED"; + }else{ + return "RUNNING"; + } + } catch (Exception exception) { + return "STOP"; + } + } + + public void setInputFile(String paramString) { + this.inputFilePath = paramString; + } + + public void setOutputFile(String paramString) { + this.outputFilePath = paramString; + } +} diff --git a/src/main/java/net/educoder/debugger/DebuggerController.java b/src/main/java/net/educoder/debugger/DebuggerController.java new file mode 100644 index 0000000..5a85f0b --- /dev/null +++ b/src/main/java/net/educoder/debugger/DebuggerController.java @@ -0,0 +1,176 @@ +package net.educoder.debugger; + +import com.google.gson.Gson; +import com.microsoft.java.debug.core.DebugUtility; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * @Author: youys + * @Date: 2023/4/6 + * @Description: + */ + +@RestController +public class DebuggerController { + + public static final String CLASS_PATHS = "/Users/youyongsheng/Desktop/ZQ/online-debug/evaluation/test-java/part4/src:/Users/youyongsheng/Desktop/ZQ/online-debug/evaluation/test-java/part4/src/precompiled.jar"; + public static final String MAIN_CLASS = "__Driver__"; + private static final Debugger debugger = new Debugger(MAIN_CLASS, CLASS_PATHS); + private static final Gson gson = new Gson(); + + + @GetMapping("/") + public String home() { + return "Controller not registered"; + } + + /** + * 启动程序 + * @return + * @throws Exception + */ + @GetMapping("/start-program") + public String startProgram() throws Exception { + debugger.startProgram(); + return "OK"; + } + + /** + * 设置输入 + * @param filePath + * @return + */ + @GetMapping("/set-input-file") + public String setInputFile(@RequestParam("filepath") String filePath) { + debugger.setInputFile(filePath); + return "OK"; + } + + /** + * 设置输出 + * @param filePath + * @return + */ + @GetMapping("/set-output-file") + public String setOutputFile(@RequestParam("filepath") String filePath) { + debugger.setOutputFile(filePath); + return "OK"; + } + + /** + * 设置断点 + * @param className + * @param lineNo + * @return + * @throws Exception + */ + @GetMapping("/set-breakpoint") + public String setBreakpoint(@RequestParam("className") String className, @RequestParam("lineNo") Integer lineNo) + throws Exception { + debugger.setBreakPoint(className, lineNo); + return "OK"; + } + + /** + * 移除断点 + * @param className + * @param lineNo + * @return + * @throws Exception + */ + @GetMapping("/remove-breakpoint") + public String removeBreakpoint(@RequestParam("className") String className, @RequestParam("lineNo") Integer lineNo) + throws Exception { + debugger.removeBreakPoint(className, lineNo); + return "OK"; + } + + /** + * 获取断点 + * @return + */ + @GetMapping("/get-breakpoints") + public String getBreakpoints() { + ArrayList> breakpoints = debugger.getBreakpoints(); + return gson.toJson(breakpoints); + } + + /** + * 跳过断点(下一个) + * @return + * @throws Exception + */ + @GetMapping("/run-continue") + public String runContinue() throws Exception { + debugger.waitForVMPause(); + debugger.runContinue(); + return "OK"; + } + + /** + * 单步调试 + * @return + * @throws Exception + */ + @GetMapping("/run-step-into") + public String runStepInto() throws Exception { + debugger.waitForVMPause(); + debugger.runStepInto(); + return "OK"; + } + + /** + * 单步跳出 + * @return + * @throws Exception + */ + @GetMapping("/run-step-out") + public String runStepOut() throws Exception { + debugger.waitForVMPause(); + debugger.runStepOut(); + return "OK"; + } + + /** + * 单步跳过(下一步) + * @return + * @throws Exception + */ + @GetMapping("/run-step-over") + public String runStepOver() throws Exception { + debugger.waitForVMPause(); + debugger.runStepOver(); + return "OK"; + } + + + /** + * 获取当前状态 + * @return + * @throws Exception + */ + @GetMapping("/get-current-state") + public String getCurrentState() throws Exception { + debugger.waitForVMPause(); + + Map map = new HashMap<>(2); + if (!debugger.isRunning()) { + return gson.toJson(map); + } + + ArrayList localVariables = debugger.getLocalVariables(); + String locationFilename = debugger.getLocationFilename(); + Integer locationLineNumber = debugger.getLocationLineNumber(); + + map.put("location", locationFilename + ":" + locationLineNumber); + map.put("variables", localVariables); + map.put("status", debugger.getStatus()); + return gson.toJson(map); + } +} diff --git a/src/main/java/net/educoder/debugger/StreamGobbler.java b/src/main/java/net/educoder/debugger/StreamGobbler.java new file mode 100644 index 0000000..819fd92 --- /dev/null +++ b/src/main/java/net/educoder/debugger/StreamGobbler.java @@ -0,0 +1,39 @@ +package net.educoder.debugger; + +/** + * @Author: youys + * @Date: 2023/4/6 + * @Description: + */ +import java.io.*; + +public class StreamGobbler extends Thread { + InputStream inputStream; + + public StreamGobbler(InputStream paramInputStream, String paramString) { + this.inputStream = paramInputStream; + this.filepath = paramString; + } + String filepath; + + @Override + public void run() { + try { + InputStreamReader inputStreamReader = new InputStreamReader(this.inputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader); + + FileWriter fileWriter = new FileWriter(this.filepath); + + int i; + while ((i = bufferedReader.read()) != -1) { + System.out.println(filepath+ " write---------"+(char)i); + fileWriter.write((char)i); + fileWriter.flush(); + } + + fileWriter.close(); + } catch (IOException iOException) { + iOException.printStackTrace(); + } + } +} diff --git a/src/main/java/net/educoder/debugger/Variable.java b/src/main/java/net/educoder/debugger/Variable.java new file mode 100644 index 0000000..07edeb6 --- /dev/null +++ b/src/main/java/net/educoder/debugger/Variable.java @@ -0,0 +1,18 @@ +package net.educoder.debugger; + +/** + * @Author: youys + * @Date: 2023/4/6 + * @Description: + */ +public class Variable { + + public String value; + public String name; + + public Variable(String paramString1, String paramString2) { + this.name = paramString1; + this.value = paramString2; + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..b67e63e --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 8083 diff --git a/user.out b/user.out new file mode 100644 index 0000000..27ba77d --- /dev/null +++ b/user.out @@ -0,0 +1 @@ +true