/* * 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.Objects; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; /** * A default implementation of {@link SshFuture}. * * @param <T> Type of future * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> */ public class DefaultSshFuture<T extends SshFuture> extends AbstractSshFuture<T> { /** * A lock used by the wait() method */ private final Object lock; private Object listeners; private Object result; /** * Creates a new instance. * * @param lock A synchronization object for locking access - if {@code null} * then synchronization occurs on {@code this} instance */ public DefaultSshFuture(Object lock) { this.lock = lock != null ? lock : this; } @Override protected Object await0(long timeoutMillis, boolean interruptable) 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); synchronized (lock) { if ((result != null) || (timeoutMillis <= 0)) { return result; } for (;;) { try { lock.wait(endTime - curTime); } catch (InterruptedException e) { if (interruptable) { curTime = System.currentTimeMillis(); throw (InterruptedIOException) new InterruptedIOException("Interrupted after " + (curTime - startTime) + " msec.").initCause(e); } } curTime = System.currentTimeMillis(); if ((result != null) || (curTime >= endTime)) { return result; } } } } @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; 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<T> 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<T> 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<T> listener = asListener(Array.get(listeners, i)); if (listener != null) { notifyListener(listener); } } } } } public boolean isCanceled() { return getValue() == CANCELED; } public void cancel() { setValue(CANCELED); } }