org.apache.sshd.common.future.DefaultSshFuture Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.future;
import java.io.InterruptedIOException;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeoutException;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
* A default implementation of {@link SshFuture}.
*
* @param Type of future
* @author Apache MINA SSHD Project
*/
public class DefaultSshFuture> extends AbstractSshFuture {
/**
* A lock used by the wait() method
*/
private final Object lock;
private Object listeners;
private Object result;
/**
* Creates a new instance.
*
* @param id Some identifier useful as {@code toString()} value
* @param lock A synchronization object for locking access - if {@code null} then synchronization occurs on
* {@code this} instance
*/
public DefaultSshFuture(Object id, Object lock) {
super(id);
this.lock = (lock != null) ? lock : this;
}
@Override
protected Object await0(long timeoutMillis, boolean interruptable, CancelOption... options)
throws InterruptedIOException {
ValidateUtils.checkTrue(timeoutMillis >= 0L, "Negative timeout N/A: %d", timeoutMillis);
long startTime = System.currentTimeMillis();
long curTime = startTime;
long endTime = ((Long.MAX_VALUE - timeoutMillis) < curTime) ? Long.MAX_VALUE : (curTime + timeoutMillis);
boolean canceled = false;
try {
synchronized (lock) {
if (result != null) {
return result;
}
if (timeoutMillis <= 0) {
result = cancelOnTimeout(timeoutMillis, options);
canceled = result != null;
return null;
}
for (;;) {
try {
lock.wait(endTime - curTime);
} catch (InterruptedException e) {
if (interruptable) {
curTime = System.currentTimeMillis();
InterruptedIOException interrupted = formatExceptionMessage(msg -> {
InterruptedIOException exc = new InterruptedIOException(msg);
exc.initCause(e);
return exc;
}, "Interrupted after %d msec.", curTime - startTime);
if (result == null && Arrays.asList(options).contains(CancelOption.CANCEL_ON_INTERRUPT)) {
CancelFuture future = createCancellation();
if (future != null) {
CancellationException cancellation = new CancellationException("Canceled on interrupt");
cancellation.initCause(interrupted);
future.setBackTrace(cancellation);
result = future;
canceled = true;
}
}
throw interrupted;
}
}
if (result != null) {
return result;
}
curTime = System.currentTimeMillis();
if (curTime >= endTime) {
result = cancelOnTimeout(timeoutMillis, options);
canceled = result != null;
return null;
}
}
}
} finally {
// Notify listeners outside the monitor
if (canceled) {
notifyListeners();
}
}
}
private CancelFuture cancelOnTimeout(long timeoutMillis, CancelOption... options) {
if (Arrays.asList(options).contains(CancelOption.CANCEL_ON_TIMEOUT)) {
CancelFuture future = createCancellation();
if (future != null) {
TimeoutException cause = new TimeoutException("Timed out after " + timeoutMillis + "msec");
CancellationException cancellation = new CancellationException(cause.getMessage());
cancellation.initCause(cause);
future.setBackTrace(cancellation);
return future;
}
}
return null;
}
@Override
public boolean isDone() {
synchronized (lock) {
return result != null;
}
}
/**
* Sets the result of the asynchronous operation, and mark it as finished.
*
* @param newValue The operation result
*/
public void setValue(Object newValue) {
synchronized (lock) {
// Allow only once.
if (result != null) {
return;
}
result = (newValue != null) ? newValue : GenericUtils.NULL;
onValueSet(newValue);
lock.notifyAll();
}
notifyListeners();
}
public int getNumRegisteredListeners() {
synchronized (lock) {
if (listeners == null) {
return 0;
} else if (listeners instanceof SshFutureListener) {
return 1;
} else {
int l = Array.getLength(listeners);
int count = 0;
for (int i = 0; i < l; i++) {
if (Array.get(listeners, i) != null) {
count++;
}
}
return count;
}
}
}
/**
* @return The result of the asynchronous operation - or {@code null} if none set.
*/
public Object getValue() {
synchronized (lock) {
return (result == GenericUtils.NULL) ? null : result;
}
}
@Override
public T addListener(SshFutureListener listener) {
Objects.requireNonNull(listener, "Missing listener argument");
boolean notifyNow = false;
synchronized (lock) {
// if already have a result don't register the listener and invoke it directly
if (result != null) {
notifyNow = true;
} else if (listeners == null) {
listeners = listener; // 1st listener ?
} else if (listeners instanceof SshFutureListener) {
listeners = new Object[] { listeners, listener };
} else { // increase array of registered listeners
Object[] ol = (Object[]) listeners;
int l = ol.length;
Object[] nl = new Object[l + 1];
System.arraycopy(ol, 0, nl, 0, l);
nl[l] = listener;
listeners = nl;
}
}
if (notifyNow) {
notifyListener(listener);
}
return asT();
}
@Override
public T removeListener(SshFutureListener listener) {
Objects.requireNonNull(listener, "No listener provided");
synchronized (lock) {
if (result != null) {
return asT(); // the train has already left the station...
}
if (listeners == null) {
return asT(); // no registered instances anyway
}
if (listeners == listener) {
listeners = null; // the one and only
} else if (!(listeners instanceof SshFutureListener)) {
int l = Array.getLength(listeners);
for (int i = 0; i < l; i++) {
if (Array.get(listeners, i) == listener) {
Array.set(listeners, i, null);
break;
}
}
}
}
return asT();
}
protected void notifyListeners() {
/*
* There won't be any visibility problem or concurrent modification because result value is checked in both
* addListener and removeListener calls under lock. If the result is already set then both methods will not
* modify the internal listeners
*/
if (listeners != null) {
if (listeners instanceof SshFutureListener) {
notifyListener(asListener(listeners));
} else {
int l = Array.getLength(listeners);
for (int i = 0; i < l; i++) {
SshFutureListener listener = asListener(Array.get(listeners, i));
if (listener != null) {
notifyListener(listener);
}
}
}
}
}
/**
* Creates a {@link CancelFuture} if this future can be canceled.
*
* This doesn't cancel this future yet.
*
*
* @return A {@link CancelFuture} that can be used to wait for the cancellation to have been effected, or
* {@code null} if the future cannot be canceled.
*/
protected CancelFuture createCancellation() {
return null;
}
/**
* Callback that is invoked under lock when the future's value is set.
*
* As this is called under lock, subclasses should not do any elaborate processing. It is intended to give
* subclasses a safe and convenient way to update local state before any listeners are invoked or callers waiting in
* {@link #await0(long, boolean, CancelOption...)} are woken up.
*
*
* The default implementation does nothing.
*
*
* @param value that was just set
*/
protected void onValueSet(Object value) {
// Do nothing
}
@Override
public String toString() {
return super.toString() + "[value=" + result + "]";
}
}