Return-Path: X-Original-To: apmail-aries-commits-archive@www.apache.org Delivered-To: apmail-aries-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 4F386173AE for ; Thu, 7 May 2015 15:00:55 +0000 (UTC) Received: (qmail 52646 invoked by uid 500); 7 May 2015 15:00:55 -0000 Delivered-To: apmail-aries-commits-archive@aries.apache.org Received: (qmail 52474 invoked by uid 500); 7 May 2015 15:00:55 -0000 Mailing-List: contact commits-help@aries.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@aries.apache.org Delivered-To: mailing list commits@aries.apache.org Received: (qmail 52448 invoked by uid 99); 7 May 2015 15:00:54 -0000 Received: from eris.apache.org (HELO hades.apache.org) (140.211.11.105) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 07 May 2015 15:00:54 +0000 Received: from hades.apache.org (localhost [127.0.0.1]) by hades.apache.org (ASF Mail Server at hades.apache.org) with ESMTP id B92A7AC0981 for ; Thu, 7 May 2015 15:00:54 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1678227 [1/2] - in /aries/trunk/async: ./ promise-api/ promise-api/src/ promise-api/src/main/ promise-api/src/main/java/ promise-api/src/main/java/org/ promise-api/src/main/java/org/apache/ promise-api/src/main/java/org/apache/aries/ promi... Date: Thu, 07 May 2015 15:00:54 -0000 To: commits@aries.apache.org From: timothyjward@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20150507150054.B92A7AC0981@hades.apache.org> Author: timothyjward Date: Thu May 7 15:00:53 2015 New Revision: 1678227 URL: http://svn.apache.org/r1678227 Log: [ARIES-1317] Commit OSGi Promises implementation contributed by derek.baum@paremus.com Added: aries/trunk/async/ aries/trunk/async/README aries/trunk/async/pom.xml aries/trunk/async/promise-api/ (with props) aries/trunk/async/promise-api/LICENSE aries/trunk/async/promise-api/NOTICE aries/trunk/async/promise-api/pom.xml aries/trunk/async/promise-api/src/ aries/trunk/async/promise-api/src/main/ aries/trunk/async/promise-api/src/main/java/ aries/trunk/async/promise-api/src/main/java/org/ aries/trunk/async/promise-api/src/main/java/org/apache/ aries/trunk/async/promise-api/src/main/java/org/apache/aries/ aries/trunk/async/promise-api/src/main/java/org/apache/aries/async/ aries/trunk/async/promise-api/src/main/java/org/apache/aries/async/promise/ aries/trunk/async/promise-api/src/main/java/org/apache/aries/async/promise/PromiseImpl.java aries/trunk/async/promise-api/src/main/java/org/osgi/ aries/trunk/async/promise-api/src/main/java/org/osgi/util/ aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/ aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Function.java aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Predicate.java aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/packageinfo aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/ aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Deferred.java aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/FailedPromisesException.java aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Failure.java aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promise.java aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promises.java aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Success.java aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/packageinfo aries/trunk/async/promise-api/src/test/ aries/trunk/async/promise-api/src/test/java/ aries/trunk/async/promise-api/src/test/java/org/ aries/trunk/async/promise-api/src/test/java/org/apache/ aries/trunk/async/promise-api/src/test/java/org/apache/aries/ aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/ aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/ aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/ aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/CallbackTest.java aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/ChainTest.java aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/DeferredTest.java aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/FunctionTest.java aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/PromisesTest.java Added: aries/trunk/async/README URL: http://svn.apache.org/viewvc/aries/trunk/async/README?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/README (added) +++ aries/trunk/async/README Thu May 7 15:00:53 2015 @@ -0,0 +1,2 @@ +Sample OSGi Asynchronous Services implementation +------------------------------------------------ Added: aries/trunk/async/pom.xml URL: http://svn.apache.org/viewvc/aries/trunk/async/pom.xml?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/pom.xml (added) +++ aries/trunk/async/pom.xml Thu May 7 15:00:53 2015 @@ -0,0 +1,45 @@ + + + + + 4.0.0 + + + org.apache.aries + parent + 2.0.0 + ../parent/pom.xml + + + org.apache.aries.async + async + Apache Aries Async + pom + 1.0.0-SNAPSHOT + + Async services, including Promises. + + + + promise-api + + + + Propchange: aries/trunk/async/promise-api/ ------------------------------------------------------------------------------ --- svn:ignore (added) +++ svn:ignore Thu May 7 15:00:53 2015 @@ -0,0 +1 @@ +target Added: aries/trunk/async/promise-api/LICENSE URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/LICENSE?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/LICENSE (added) +++ aries/trunk/async/promise-api/LICENSE Thu May 7 15:00:53 2015 @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Added: aries/trunk/async/promise-api/NOTICE URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/NOTICE?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/NOTICE (added) +++ aries/trunk/async/promise-api/NOTICE Thu May 7 15:00:53 2015 @@ -0,0 +1,8 @@ + +Apache Aries +Copyright 2009-2011 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + + Added: aries/trunk/async/promise-api/pom.xml URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/pom.xml?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/pom.xml (added) +++ aries/trunk/async/promise-api/pom.xml Thu May 7 15:00:53 2015 @@ -0,0 +1,93 @@ + + + + + 4.0.0 + + + org.apache.aries + parent + 2.0.0 + ../../parent/pom.xml + + + org.apache.aries.async + org.apache.aries.async.promise.api + bundle + Apache Aries Async Promise API + 0.0.1-SNAPSHOT + + This bundle contains the Apache Aries Async Promise service API. + + + + + + + + + + + + org.osgi.util.function, + org.osgi.util.promise;provide:=true, + org.apache.aries.async.promise + + + + org.osgi.util.function, + org.osgi.util.promise, + * + + 0.0.0 + + + + + org.apache.aries + org.apache.aries.util + 1.0.0 + + + junit + junit + test + + + + + + + org.apache.aries.versioning + org.apache.aries.versioning.plugin + + + default-verify + verify + + version-check + + + + + + + + Added: aries/trunk/async/promise-api/src/main/java/org/apache/aries/async/promise/PromiseImpl.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/apache/aries/async/promise/PromiseImpl.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/apache/aries/async/promise/PromiseImpl.java (added) +++ aries/trunk/async/promise-api/src/main/java/org/apache/aries/async/promise/PromiseImpl.java Thu May 7 15:00:53 2015 @@ -0,0 +1,371 @@ +package org.apache.aries.async.promise; + +import org.osgi.util.function.Function; +import org.osgi.util.function.Predicate; +import org.osgi.util.promise.Failure; +import org.osgi.util.promise.Promise; +import org.osgi.util.promise.Success; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class PromiseImpl implements Promise { + + private final ExecutorService exec; + private final List tasks = new ArrayList(); + private final CountDownLatch resolved = new CountDownLatch(1); + + private List chain; + private Success onSuccess; + private Failure onFailure; + private Throwable failure; + private T value; + + public PromiseImpl() { + // Executor for onResolve() callbacks + // We could use an Executor that runs tasks in current thread + exec = Executors.newSingleThreadExecutor(); + } + + public void fail(Throwable failure) { + if (failure == null) + throw new NullPointerException(); + complete(null, failure); + } + + public void resolve(T value) { + complete(value, null); + } + + public Promise resolveWith(final Promise with) { + if (with == null) + throw new NullPointerException(); + final PromiseImpl result = new PromiseImpl(); + + with.then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + if (isDone()) { + result.fail(new IllegalStateException("associated Promise already resolved")); + } + PromiseImpl.this.resolve(resolved.getValue()); + result.resolve(null); + return null; + } + }, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + if (isDone()) { + result.fail(new IllegalStateException("associated Promise already resolved")); + } + PromiseImpl.this.fail(resolved.getFailure()); + result.resolve(null); + } + }); + + return result; + } + + private synchronized void complete(T value, Throwable failure) { + if (isDone()) { + throw new IllegalStateException("Promise is already resolved"); + } + + // mark this Promise as complete before invoking callbacks + if (failure != null) { + this.failure = failure; + } else { + this.value = value; + } + resolved.countDown(); + + if (chain != null) { + runChain(); + } + + // run onResolve() callbacks + for (Runnable task : tasks) { + exec.submit(task); + } + } + + // run chained success/failure callbacks + @SuppressWarnings("unchecked") + private void runChain() { + while (!chain.isEmpty()) { + PromiseImpl next = chain.remove(0); + if (failure != null) { + try { + if (next.onFailure != null) { + // "This method is called if the Promise with which it is registered resolves with a failure." + next.onFailure.fail(this); + } + // "If this method completes normally, the chained Promise will be failed + // with the same exception which failed the resolved Promise." + next.fail(failure); + } catch (Exception e) { + // "If this method throws an exception, the chained Promise will be failed with the thrown exception." + next.fail(e); + } + } else { + try { + // "This method is called if the Promise with which it is registered resolves successfully." + Promise p = null; + if (next.onSuccess != null) { + p = next.onSuccess.call(this); + } + if (p == null) { + // "If the returned Promise is null then the chained Promise will resolve immediately with a successful value of null." + next.resolve(null); + } else { + // "If the returned Promise is not null then the chained Promise will be resolved when the returned Promise is resolved" + next.resolveWith(p); + } + } catch (InvocationTargetException e) { + next.fail(e.getCause()); + } catch (Exception e) { + next.fail(e); + } + } + } + } + + // Promise API methods + + @Override + public boolean isDone() { + return resolved.getCount() == 0; + } + + @Override + public T getValue() throws InvocationTargetException, InterruptedException { + resolved.await(); + if (failure != null) { + throw new InvocationTargetException(failure); + } + return value; + } + + @Override + public Throwable getFailure() throws InterruptedException { + resolved.await(); + return failure; + } + + @Override + public synchronized Promise onResolve(Runnable callback) { + if (callback == null) + throw new NullPointerException(); + + if (isDone()) { + exec.submit(callback); + } else { + tasks.add(callback); + } + return this; + } + + @Override + public Promise then(Success success, Failure failure) { + PromiseImpl result = new PromiseImpl(); + result.onSuccess = success; + result.onFailure = failure; + synchronized (this) { + if (chain == null) { + chain = new ArrayList(); + } + chain.add(result); + if (isDone()) { + runChain(); + } + } + return result; + } + + @Override + public Promise then(Success success) { + return then(success, null); + } + + @Override + public Promise filter(final Predicate predicate) { + if (predicate == null) + throw new NullPointerException(); + final PromiseImpl result = new PromiseImpl(); + + then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + try { + if (predicate.test(resolved.getValue())) { + result.resolve(resolved.getValue()); + } else { + result.fail(new NoSuchElementException("predicate does not accept value")); + } + } catch (Throwable t) { + result.fail(t); + } + return null; + } + }, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + result.fail(resolved.getFailure()); + } + }); + + return result; + } + + @Override + public Promise map(final Function mapper) { + if (mapper == null) + throw new NullPointerException(); + final PromiseImpl result = new PromiseImpl(); + + then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + try { + R val = mapper.apply(resolved.getValue()); + result.resolve(val); + } catch (Throwable t) { + result.fail(t); + } + return null; + } + }, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + result.fail(resolved.getFailure()); + } + }); + + return result; + } + + @Override + public Promise flatMap(final Function> mapper) { + if (mapper == null) + throw new NullPointerException(); + final PromiseImpl result = new PromiseImpl(); + + then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + try { + Promise p = mapper.apply(resolved.getValue()); + result.resolveWith(p); + } catch (Throwable t) { + result.fail(t); + } + return null; + } + }, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + result.fail(resolved.getFailure()); + } + }); + + return result; + } + + @Override + public Promise recover(final Function, ? extends T> recovery) { + if (recovery == null) + throw new NullPointerException(); + + final PromiseImpl result = new PromiseImpl(); + + then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + result.resolve(resolved.getValue()); + return null; + } + }, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + try { + T recover = recovery.apply(resolved); + if (recover != null) { + result.resolve(recover); + } else { + result.fail(resolved.getFailure()); + } + } catch (Throwable t) { + result.fail(t); + } + } + }); + + return result; + } + + @Override + public Promise recoverWith(final Function, Promise> recovery) { + if (recovery == null) + throw new NullPointerException(); + + final PromiseImpl result = new PromiseImpl(); + + then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + result.resolve(resolved.getValue()); + return null; + } + }, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + try { + Promise recover = recovery.apply(resolved); + if (recover != null) { + result.resolveWith(recover); + } else { + result.fail(resolved.getFailure()); + } + } catch (Throwable t) { + result.fail(t); + } + } + }); + + return result; + } + + @Override + public Promise fallbackTo(final Promise fallback) { + if (fallback == null) + throw new NullPointerException(); + + final PromiseImpl result = new PromiseImpl(); + + then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + result.resolve(resolved.getValue()); + return null; + } + }, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + @SuppressWarnings({"not thrown", "all"}) + Throwable fail = fallback.getFailure(); + if (fail != null) { + result.fail(resolved.getFailure()); + } else { + result.resolve(fallback.getValue()); + } + } + }); + + return result; + } +} Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Function.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Function.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Function.java (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Function.java Thu May 7 15:00:53 2015 @@ -0,0 +1,20 @@ +package org.osgi.util.function; + +/** + * A function that accepts a single argument and produces a result. + *

+ * This is a functional interface and can be used as the assignment target for a lambda expression or method reference. + * + * @param The type of the function input. + * @param The type of the function output. + */ +//@org.osgi.annotation.versioning.ConsumerType +public interface Function { + + /** + * Applies this function to the specified argument. + * @param t The input to this function. + * @return The output of this function. + */ + R apply(T t); +} Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Predicate.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Predicate.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Predicate.java (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/Predicate.java Thu May 7 15:00:53 2015 @@ -0,0 +1,19 @@ +package org.osgi.util.function; + +/** + * A predicate that accepts a single argument and produces a boolean result. + *

+ * This is a functional interface and can be used as the assignment target for a lambda expression or method reference. + * + * @param The type of the predicate input. + */ +//@org.osgi.annotation.versioning.ConsumerType +public interface Predicate { + /** + * Evaluates this predicate on the specified argument. + * + * @param t The input to this predicate. + * @return true if the specified argument is accepted by this predicate; false otherwise. + */ + boolean test(T t); +} Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/packageinfo URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/packageinfo?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/packageinfo (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/function/packageinfo Thu May 7 15:00:53 2015 @@ -0,0 +1 @@ +version 1.0.0 Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Deferred.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Deferred.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Deferred.java (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Deferred.java Thu May 7 15:00:53 2015 @@ -0,0 +1,92 @@ +package org.osgi.util.promise; + +import org.apache.aries.async.promise.PromiseImpl; + +/** + * A Deferred Promise resolution. + *

+ * Instances of this class can be used to create a Promise that can be resolved in the future. The associated Promise + * can be successfully resolved with resolve(Object) or resolved with a failure with fail(Throwable). + *

+ * It can also be resolved with the resolution of another promise using resolveWith(Promise). + *

+ * The associated Promise can be provided to anyone, but the Deferred object should be made available only to the party + * that will responsible for resolving the Promise. + * + * @param The value type associated with the created Promise. + */ +public class Deferred { + private final PromiseImpl promise; + + /** + * Create a new Deferred with an associated Promise. + */ + public Deferred() { + promise = new PromiseImpl(); + } + + /** + * Returns the Promise associated with this Deferred. + * + * @return The Promise associated with this Deferred. + */ + public Promise getPromise() { + return promise; + } + + /** + * Successfully resolve the Promise associated with this Deferred. + *

+ * After the associated Promise is resolved with the specified value, all registered callbacks are called and any + * chained Promises are resolved. + *

+ * Resolving the associated Promise happens-before any registered callback is called. That is, in a registered + * callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block. + * + * @param value The value of the resolved Promise. + * @throws IllegalStateException If the associated Promise was already resolved. + */ + public void resolve(T value) { + promise.resolve(value); + } + + /** + * Fail the Promise associated with this Deferred. + *

+ * After the associated Promise is resolved with the specified failure, all registered callbacks are called and any + * chained Promises are resolved. + *

+ * Resolving the associated Promise happens-before any registered callback is called. That is, in a registered + * callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block. + * + * @param failure The failure of the resolved Promise. Must not be null. + * @throws IllegalStateException If the associated Promise was already resolved. + */ + public void fail(Throwable failure) { + promise.fail(failure); + } + + /** + * Resolve the Promise associated with this Deferred with the specified Promise. + *

+ * If the specified Promise is successfully resolved, the associated Promise is resolved with the value of the + * specified Promise. If the specified Promise is resolved with a failure, the associated Promise is resolved with + * the failure of the specified Promise. + *

+ * After the associated Promise is resolved with the specified Promise, all registered callbacks are called and any + * chained Promises are resolved. + *

+ * Resolving the associated Promise happens-before any registered callback is called. That is, in a registered + * callback, Promise.isDone() must return true and Promise.getValue() and Promise.getFailure() must not block + * + * @param with A Promise whose value or failure will be used to resolve the associated Promise. Must not be null. + * @return A Promise that is resolved only when the associated Promise is resolved by the specified Promise. The + * returned Promise will be successfully resolved, with the value null, if the associated Promise was resolved by + * the specified Promise. The returned Promise will be resolved with a failure of IllegalStateException if the + * associated Promise was already resolved when the specified Promise was resolved. + */ + public Promise resolveWith(Promise with) { + return promise.resolveWith(with); + } + +} Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/FailedPromisesException.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/FailedPromisesException.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/FailedPromisesException.java (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/FailedPromisesException.java Thu May 7 15:00:53 2015 @@ -0,0 +1,44 @@ +package org.osgi.util.promise; + +import java.util.Collection; +import java.util.Collections; + +/** + * Promise failure exception for a collection of failed Promises. + */ +public class FailedPromisesException extends RuntimeException { + + private final Collection> failed; + + /** + * Create a new FailedPromisesException with the specified Promises. + * + * @param failed A collection of Promises that have been resolved with a failure. Must not be null. + */ + public FailedPromisesException(Collection> failed) { + this(failed, null); + } + + /** + * Create a new FailedPromisesException with the specified Promises. + * + * @param failed A collection of Promises that have been resolved with a failure. Must not be null. + * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A null + * value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + public FailedPromisesException(Collection> failed, Throwable cause) { + super(cause); + assert failed != null; + this.failed = failed; + } + + /** + * Returns the collection of Promises that have been resolved with a failure. + * + * @return The collection of Promises that have been resolved with a failure. The returned collection is + * unmodifiable. + */ + public Collection> getFailedPromises() { + return Collections.unmodifiableCollection(failed); + } +} Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Failure.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Failure.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Failure.java (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Failure.java Thu May 7 15:00:53 2015 @@ -0,0 +1,25 @@ +package org.osgi.util.promise; + +/** + * Failure callback for a Promise. + *

+ * A Failure callback is registered with a Promise using the Promise.then(Success, Failure) method and is called if the Promise is resolved with a failure. + *

+ * This is a functional interface and can be used as the assignment target for a lambda expression or method reference. + */ +//@org.osgi.annotation.versioning.ConsumerType +public interface Failure { + /** + * Failure callback for a Promise. + *

+ * This method is called if the Promise with which it is registered resolves with a failure. + *

+ * In the remainder of this description we will refer to the Promise returned by Promise.then(Success, Failure) when this Failure callback was registered as the chained Promise. + *

+ * If this method completes normally, the chained Promise will be failed with the same exception which failed the resolved Promise. If this method throws an exception, the chained Promise will be failed with the thrown exception. + * + * @param resolved The failed resolved Promise. + * @throws Exception The chained Promise will be failed with the thrown exception. + */ + void fail(Promise resolved) throws Exception; +} Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promise.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promise.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promise.java (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promise.java Thu May 7 15:00:53 2015 @@ -0,0 +1,279 @@ +package org.osgi.util.promise; + +import org.osgi.util.function.Function; +import org.osgi.util.function.Predicate; + +import java.lang.reflect.InvocationTargetException; + +/** + * A Promise of a value. + *

+ * A Promise represents a future value. It handles the interactions to for asynchronous processing. A Deferred object + * can be used to create a Promise and later resolve the Promise. A Promise is used by the caller of an asynchronous + * function to get the result or handle the error. The caller can either get a callback when the Promise is resolved + * with a value or an error, or the Promise can be used in chaining. In chaining, callbacks are provided that receive + * the resolved Promise, and a new Promise is generated that resolves based upon the result of a callback. + *

+ * Both callbacks and chaining can be repeated any number of times, even after the Promise has been resolved. + *

+ * Example callback usage: + *

+ * final Promise foo= foo();
+ * foo.onResolve(new Runnable() {
+ * public void run() {
+ * System.out.println(foo.getValue());
+ * }
+ * });
+ * 
+ *

+ * Example chaining usage; + *

+ * Success doubler = new Success() {
+ * public Promise call(Promise p) throws Exception {
+ * return Promises.resolved(p.getValue()+p.getValue());
+ * }
+ * };
+ * final Promise foo = foo().then(doubler).then(doubler);
+ * foo.onResolve(new Runnable() {
+ * public void run() {
+ * System.out.println(foo.getValue());
+ * }
+ * });
+ * 
+ * + * @param The value type associated with this Promise. + */ +//@org.osgi.annotation.versioning.ProviderType +public interface Promise { + /** + * Returns whether this Promise has been resolved. + *

+ * This Promise may be successfully resolved or resolved with a failure. + * + * @return true if this Promise was resolved either successfully or with a failure; false if this Promise is + * unresolved. + */ + boolean isDone(); + + /** + * Returns the value of this Promise. + *

+ * If this Promise is not resolved, this method must block and wait for this Promise to be resolved before + * completing. + *

+ * If this Promise was successfully resolved, this method returns with the value of this Promise. If this Promise + * was resolved with a failure, this method must throw an InvocationTargetException with the failure exception as + * the cause. + * + * @return The value of this resolved Promise. + * @throws InvocationTargetException If this Promise was resolved with a failure. The cause of the + * InvocationTargetException is the failure exception. + * @throws InterruptedException If the current thread was interrupted while waiting. + */ + T getValue() throws InvocationTargetException, InterruptedException; + + /** + * Returns the failure of this Promise. + *

+ * If this Promise is not resolved, this method must block and wait for this Promise to be resolved before + * completing. + *

+ * If this Promise was resolved with a failure, this method returns with the failure of this Promise. If this + * Promise was successfully resolved, this method must return null. + * + * @return The failure of this resolved Promise or null if this Promise was successfully resolved. + * @throws InterruptedException If the current thread was interrupted while waiting. + */ + Throwable getFailure() throws InterruptedException; + + /** + * Register a callback to be called when this Promise is resolved. + *

+ * The specified callback is called when this Promise is resolved either successfully or with a failure. + *

+ * This method may be called at any time including before and after this Promise has been resolved. + *

+ * Resolving this Promise happens-before any registered callback is called. That is, in a registered callback, + * isDone() must return true and getValue() and getFailure() must not block. + *

+ * A callback may be called on a different thread than the thread which registered the callback. So the callback + * must be thread safe but can rely upon that the registration of the callback happens-before the registered + * callback is called. + * + * @param callback A callback to be called when this Promise is resolved. Must not be null. + * @return This Promise. + */ + Promise onResolve(Runnable callback); + + + /** + * Chain a new Promise to this Promise with Success and Failure callbacks. + *

+ * The specified Success callback is called when this Promise is successfully resolved and the specified Failure + * callback is called when this Promise is resolved with a failure. + *

+ * This method returns a new Promise which is chained to this Promise. The returned Promise must be resolved when + * this Promise is resolved after the specified Success or Failure callback is executed. The result of the executed + * callback must be used to resolve the returned Promise. Multiple calls to this method can be used to create a + * chain of promises which are resolved in sequence. + *

+ * If this Promise is successfully resolved, the Success callback is executed and the result Promise, if any, or + * thrown exception is used to resolve the returned Promise from this method. If this Promise is resolved with a + * failure, the Failure callback is executed and the returned Promise from this method is failed. + *

+ * This method may be called at any time including before and after this Promise has been resolved. + *

+ * Resolving this Promise happens-before any registered callback is called. That is, in a registered callback, + * isDone() must return true and getValue() and getFailure() must not block. + *

+ * A callback may be called on a different thread than the thread which registered the callback. So the callback + * must be thread safe but can rely upon that the registration of the callback happens-before the registered + * callback is called. + * + * @param The value type associated with the returned Promise. + * @param success A Success callback to be called when this Promise is successfully resolved. May be null if no + * Success callback is required. In thi + * @param failure A Failure callback to be called when this Promise is resolved with a failure. May be null if no + * Failure callback is required. + * @return A new Promise which is chained to this Promise. The returned Promise must be resolved when this Promise + * is resolved after the specified Success or Failure callback, if any, is executed + */ + Promise then(Success success, Failure failure); + + + /** + * Chain a new Promise to this Promise with a Success callback. + *

+ * This method performs the same function as calling then(Success, Failure) with the specified Success callback and + * null for the Failure callback + * + * @param success A Success callback to be called when this Promise is successfully resolved. May be null if no + * Success callback is required. In this case, the returned Promise must be resolved with the value + * null when this Promise is successfully resolved. + * @param The value type associated with the returned Promise. + * @return A new Promise which is chained to this Promise. The returned Promise must be resolved when this Promise + * is resolved after the specified Success, if any, is executed. + * @see #then(Success, Failure) + */ + Promise then(Success success); + + + /** + * Filter the value of this Promise. + *

+ * If this Promise is successfully resolved, the returned Promise will either be resolved with the value of this + * Promise if the specified Predicate accepts that value or failed with a NoSuchElementException if the specified + * Predicate does not accept that value. If the specified Predicate throws an exception, the returned Promise will + * be failed with the exception. + *

+ * If this Promise is resolved with a failure, the returned Promise will be failed with that failure. + *

+ * This method may be called at any time including before and after this Promise has been resolved. + * + * @param predicate The Predicate to evaluate the value of this Promise. Must not be null. + * @return A Promise that filters the value of this Promise. + */ + Promise filter(Predicate predicate); + + /** + * Map the value of this Promise. + *

+ * If this Promise is successfully resolved, the returned Promise will be resolved with the value of specified + * Function as applied to the value of this Promise. If the specified Function throws an exception, the returned + * Promise will be failed with the exception. + *

+ * If this Promise is resolved with a failure, the returned Promise will be failed with that failure. + *

+ * This method may be called at any time including before and after this Promise has been resolved. + * + * @param mapper The Function that will map the value of this Promise to the value that will be used to resolve the + * returned Promise. Must not be null. + * @param The value type associated with the returned Promise. + * @return A Promise that returns the value of this Promise as mapped by the specified Function. + */ + Promise map(Function mapper); + + /** + * FlatMap the value of this Promise. + *

+ * If this Promise is successfully resolved, the returned Promise will be resolved with the Promise from the + * specified Function as applied to the value of this Promise. If the specified Function throws an exception, the + * returned Promise will be failed with the exception. + *

+ * If this Promise is resolved with a failure, the returned Promise will be failed with that failure. + *

+ * This method may be called at any time including before and after this Promise has been resolved. + * + * @param mapper The Function that will flatMap the value of this Promise to a Promise that will be used to resolve + * the returned Promise. Must not be null. + * @param The value type associated with the returned Promise. + * @return A Promise that returns the value of this Promise as mapped by the specified Function. + */ + Promise flatMap(Function> mapper); + + /** + * Recover from a failure of this Promise with a recovery value. + *

+ * If this Promise is successfully resolved, the returned Promise will be resolved with the value of this Promise. + *

+ * If this Promise is resolved with a failure, the specified Function is applied to this Promise to produce a + * recovery value. + *

+ * If the recovery value is not null, the returned Promise will be resolved with the recovery value. + *

+ * If the recovery value is null, the returned Promise will be failed with the failure of this Promise. + *

+ * If the specified Function throws an exception, the returned Promise will be failed with that exception. + *

+ * To recover from a failure of this Promise with a recovery value of null, the recoverWith(Function) method must be + * used. The specified Function for recoverWith(Function) can return Promises.resolved(null) to supply the desired + * null value. + *

+ * This method may be called at any time including before and after this Promise has been resolved. + * + * @param recovery If this Promise resolves with a failure, the specified Function is called to produce a recovery + * value to be used to resolve the returned Promise. Must not be null. + * @return A Promise that resolves with the value of this Promise or recovers from the failure of this Promise. + */ + Promise recover(Function, ? extends T> recovery); + + /** + * Recover from a failure of this Promise with a recovery Promise. + *

+ * If this Promise is successfully resolved, the returned Promise will be resolved with the value of this Promise. + *

+ * If this Promise is resolved with a failure, the specified Function is applied to this Promise to produce a + * recovery Promise. + *

+ * If the recovery Promise is not null, the returned Promise will be resolved with the recovery Promise. + *

+ * If the recovery Promise is null, the returned Promise will be failed with the failure of this Promise. + *

+ * If the specified Function throws an exception, the returned Promise will be failed with that exception. + *

+ * This method may be called at any time including before and after this Promise has been resolved. + * + * @param recovery If this Promise resolves with a failure, the specified Function is called to produce a recovery + * Promise to be used to resolve the returned Promise. Must not be null. + * @return A Promise that resolves with the value of this Promise or recovers from the failure of this Promise. + */ + Promise recoverWith(Function, Promise> recovery); + + /** + * Fall back to the value of the specified Promise if this Promise fails. + *

+ * If this Promise is successfully resolved, the returned Promise will be resolved with the value of this Promise. + *

+ * If this Promise is resolved with a failure, the successful result of the specified Promise is used to resolve the + * returned Promise. If the specified Promise is resolved with a failure, the returned Promise will be failed with + * the failure of this Promise rather than the failure of the specified Promise. + *

+ * This method may be called at any time including before and after this Promise has been resolved. + * + * @param fallback The Promise whose value will be used to resolve the returned Promise if this Promise resolves + * with a failure. Must not be null. + * @return A Promise that returns the value of this Promise or falls back to the value of the specified Promise. + */ + Promise fallbackTo(Promise fallback); + +} Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promises.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promises.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promises.java (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Promises.java Thu May 7 15:00:53 2015 @@ -0,0 +1,122 @@ +package org.osgi.util.promise; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Static helper methods for Promises. + */ +public class Promises { + /** + * Create a new Promise that has been resolved with the specified value. + * + * @param value The value of the resolved Promise. + * @param The value type associated with the returned Promise. + * @return A new Promise that has been resolved with the specified value. + */ + public static Promise resolved(T value) { + Deferred def = new Deferred(); + def.resolve(value); + return def.getPromise(); + } + + /** + * Create a new Promise that has been resolved with the specified failure. + * + * @param failure The failure of the resolved Promise. Must not be null. + * @param The value type associated with the returned Promise. + * @return A new Promise that has been resolved with the specified failure. + */ + public static Promise failed(Throwable failure) { + if (failure == null) + throw new NullPointerException(); + Deferred def = new Deferred(); + def.fail(failure); + return def.getPromise(); + } + + /** + * Create a new Promise that is a latch on the resolution of the specified Promises. + *

+ * The new Promise acts as a gate and must be resolved after all of the specified Promises are resolved. + * + * @param promises The Promises which must be resolved before the returned Promise must be resolved. Must not be + * null. + * @param The value type of the List value associated with the returned Promise. + * @param A subtype of the value type of the List value associated with the returned Promise. + * @return A Promise that is resolved only when all the specified Promises are resolved. The returned Promise will + * be successfully resolved, with a List of the values in the order of the specified Promises, if all the specified + * Promises are successfully resolved. The List in the returned Promise is the property of the caller and is + * modifiable. The returned Promise will be resolved with a failure of FailedPromisesException if any of the + * specified Promises are resolved with a failure. The failure FailedPromisesException must contain all of the + * specified Promises which resolved with a failure. + */ + public static Promise> all(final Collection> promises) { + if (promises == null) + throw new NullPointerException(); + final Deferred> result = new Deferred>(); + final Collection> failedPromises = new ArrayList>(); + final List resolvedValues = new ArrayList(); + + if (promises.size() == 0) { + result.resolve(resolvedValues); + } + for (final Promise promise : promises) { + promise.then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + // "S is subtype of the value type of the List" + @SuppressWarnings("unchecked") + T value = (T) resolved.getValue(); + resolvedValues.add(value); + + if (resolvedValues.size() == promises.size()) { + result.resolve(resolvedValues); + } else if (failedPromises.size() + resolvedValues.size() == promises.size()) { + result.fail(new FailedPromisesException(failedPromises)); + } + return null; + } + }, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + failedPromises.add(resolved); + if (failedPromises.size() + resolvedValues.size() == promises.size()) { + result.fail(new FailedPromisesException(failedPromises)); + } + } + }); + + } + + return result.getPromise(); + } + + /** + * Create a new Promise that is a latch on the resolution of the specified Promises. + *

+ * The new Promise acts as a gate and must be resolved after all of the specified Promises are resolved. + * + * @param promises The Promises which must be resolved before the returned Promise must be resolved. Must not be + * null. + * @param The value type associated with the specified Promises. + * @return A Promise that is resolved only when all the specified Promises are resolved. The returned Promise will + * be successfully resolved, with a List of the values in the order of the specified Promises, if all the specified + * Promises are successfully resolved. The List in the returned Promise is the property of the caller and is + * modifiable. The returned Promise will be resolved with a failure of FailedPromisesException if any of the + * specified Promises are resolved with a failure. The failure FailedPromisesException must contain all of the + * specified Promises which resolved with a failure. + */ + public static Promise> all(final Promise... promises) { + if (promises == null) + throw new NullPointerException(); + List> list = new ArrayList>(); + for (Promise promise : promises) { + @SuppressWarnings("unchecked") + Promise pt = (Promise) promise; + list.add(pt); + } + return all(list); + } +} Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Success.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Success.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Success.java (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/Success.java Thu May 7 15:00:53 2015 @@ -0,0 +1,29 @@ +package org.osgi.util.promise; + +/** + * Success callback for a Promise. + *

+ * A Success callback is registered with a Promise using the Promise.then(Success) method and is called if the Promise is resolved successfully. + *

+ * This is a functional interface and can be used as the assignment target for a lambda expression or method reference. + *

+ * @param The value type of the resolved Promise passed as input to this callback. + * @param The value type of the returned Promise from this callback. + */ +//@org.osgi.annotation.versioning.ConsumerType +public interface Success { + /** + * Success callback for a Promise. + *

+ * This method is called if the Promise with which it is registered resolves successfully. + *

+ * In the remainder of this description we will refer to the Promise returned by this method as the returned Promise and the Promise returned by Promise.then(Success) when this Success callback was registered as the chained Promise. + *

+ * If the returned Promise is null then the chained Promise will resolve immediately with a successful value of null. If the returned Promise is not null then the chained Promise will be resolved when the returned Promise is resolved. + * + * @param resolved The successfully resolved Promise. + * @return The Promise to use to resolve the chained Promise, or null if the chained Promise is to be resolved immediately with the value null. + * @throws Exception The chained Promise will be failed with the thrown exception. + */ + Promise call(Promise resolved) throws Exception; +} Added: aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/packageinfo URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/packageinfo?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/packageinfo (added) +++ aries/trunk/async/promise-api/src/main/java/org/osgi/util/promise/packageinfo Thu May 7 15:00:53 2015 @@ -0,0 +1 @@ +version 1.0.0 Added: aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/CallbackTest.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/CallbackTest.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/CallbackTest.java (added) +++ aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/CallbackTest.java Thu May 7 15:00:53 2015 @@ -0,0 +1,70 @@ +package org.apache.aries.async.promise.test; + +import org.osgi.util.promise.Deferred; +import org.osgi.util.promise.Promise; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CallbackTest { + + @Test + public void testCallbacks() throws Exception { + Deferred def = new Deferred(); + final Promise promise = def.getPromise(); + + Callback cb1 = new Callback(promise, "Hello"); + assertEquals("onResolve returns promise", promise, promise.onResolve(cb1)); + + Callback cb2 = new Callback(promise, "Hello"); + promise.onResolve(cb2); + + def.resolve("Hello"); + + assertTrue("callback1 executed", cb1.latch.await(1, TimeUnit.SECONDS)); + assertEquals("callback1 succeeded", null, cb1.error); + + assertTrue("callback2 executed", cb2.latch.await(1, TimeUnit.SECONDS)); + assertEquals("callback2 succeeded", null, cb2.error); + + // register callback after promise is resolved + Callback cb3 = new Callback(promise, "Hello"); + promise.onResolve(cb3); + assertTrue("callback3 executed", cb3.latch.await(1, TimeUnit.SECONDS)); + assertEquals("callback3 succeeded", null, cb3.error); + } + + + class Callback implements Runnable { + final CountDownLatch latch = new CountDownLatch(1); + Throwable error = null; + + private final Promise promise; + private final Object value; + + Callback(Promise promise, Object value) { + this.promise = promise; + this.value = value; + } + + @Override + public void run() { + try { + assertTrue("Promise resolved", promise.isDone()); + assertEquals("Value matches", value, promise.getValue()); + } + catch (Throwable t) { + error = t; + } + finally { + latch.countDown(); + } + } + } + + +} Added: aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/ChainTest.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/ChainTest.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/ChainTest.java (added) +++ aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/ChainTest.java Thu May 7 15:00:53 2015 @@ -0,0 +1,254 @@ +package org.apache.aries.async.promise.test; + +import org.junit.Test; +import org.osgi.util.promise.*; + +import static org.junit.Assert.*; + +public class ChainTest { + + @Test + public void testThenSuccess() throws Exception { + Deferred def = new Deferred(); + + Promise chain = def.getPromise().then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + return Promises.resolved("success!"); + } + }); + assertFalse("chain not resolved", chain.isDone()); + + def.resolve("ok"); + assertTrue("chain resolved", chain.isDone()); + assertEquals("chain value matches", "success!", chain.getValue()); + } + + @Test + public void testThenSuccessDeferred() throws Exception { + Deferred def = new Deferred(); + final Deferred def2 = new Deferred(); + + Promise chain = def.getPromise().then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + return def2.getPromise(); + } + }); + assertFalse("chain not resolved", chain.isDone()); + + def.resolve("ok"); + assertFalse("chain still not resolved", chain.isDone()); + + def2.resolve("success!"); + assertTrue("chain resolved", chain.isDone()); + assertEquals("chain value matches", "success!", chain.getValue()); + } + + @Test + public void testThenSuccessFailed() throws Exception { + Deferred def = new Deferred(); + final Promise promise = def.getPromise(); + final Throwable thenFail = new Throwable("failed!"); + + Promise chain = promise.then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + return Promises.failed(thenFail); + } + }); + assertFalse("chain not resolved", chain.isDone()); + + def.resolve("ok"); + assertTrue("chain resolved", chain.isDone()); + assertEquals("chain failure matches", thenFail, chain.getFailure()); + } + + @Test + public void testThenSuccessException() throws Exception { + Deferred def = new Deferred(); + final Promise promise = def.getPromise(); + final Exception thenException = new Exception("then exception!"); + + Promise chain = promise.then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + throw thenException; + } + }); + assertFalse("chain not resolved", chain.isDone()); + + def.resolve("ok"); + assertTrue("chain resolved", chain.isDone()); + assertEquals("chain failure matches", thenException, chain.getFailure()); + } + + @Test + public void testThenFail() throws Exception { + Deferred def = new Deferred(); + final Promise promise = def.getPromise(); + + Promise chain = promise.then(null, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + } + }); + assertFalse("chain not resolved", chain.isDone()); + + Throwable failure = new Throwable("fail!"); + def.fail(failure); + assertTrue("chain resolved", chain.isDone()); + assertEquals("chain failure matches", failure, chain.getFailure()); + } + + @Test + public void testThenFailNoCallback() throws Exception { + Deferred def = new Deferred(); + final Promise promise = def.getPromise(); + + Promise chain = promise.then(null); + assertFalse("chain not resolved", chain.isDone()); + + Throwable failure = new Throwable("fail!"); + def.fail(failure); + assertTrue("chain resolved", chain.isDone()); + assertEquals("chain failure matches", failure, chain.getFailure()); + } + + @Test + public void testThenFailException() throws Exception { + Deferred def = new Deferred(); + final Promise promise = def.getPromise(); + final Exception thenException = new Exception("eek!"); + + Promise chain = promise.then(null, new Failure() { + @Override + public void fail(Promise resolved) throws Exception { + throw thenException; + } + }); + assertFalse("chain not resolved", chain.isDone()); + + def.fail(new Throwable("failed")); + assertTrue("chain resolved", chain.isDone()); + assertEquals("chain failure matches", thenException, chain.getFailure()); + } + + @Test + public void testThenNull() throws Exception { + Deferred def = new Deferred(); + final Promise promise = def.getPromise(); + Promise chain = promise.then(null); + assertFalse("chain not resolved", chain.isDone()); + + def.resolve("ok"); + assertTrue("chain resolved", chain.isDone()); + assertNull("chain value null", chain.getValue()); + } + + @Test + public void testThenNullResolved() throws Exception { + Deferred def = new Deferred(); + def.resolve("ok"); + Promise chain = def.getPromise().then(null); + + assertTrue("chain resolved", chain.isDone()); + assertNull("chain value null", chain.getValue()); + } + + @Test + public void testThenAlreadyResolved() throws Exception { + Deferred def = new Deferred(); + final Promise promise = def.getPromise(); + def.resolve("ok"); + + Promise chain = promise.then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + return Promises.resolved("success!"); + } + }); + assertTrue("chain resolved", chain.isDone()); + assertEquals("chain value matches", "success!", chain.getValue()); + } + + @Test + public void testExampleChain() throws Exception { + Success doubler = new Success() { + public Promise call(Promise p) throws Exception { + return Promises.resolved(p.getValue() + p.getValue()); + } + }; + + Deferred def = new Deferred(); + final Promise foo = def.getPromise().then(doubler).then(doubler); + + def.resolve("hello!"); + assertEquals("doubler matches", "hello!hello!hello!hello!", foo.getValue()); + } + + @Test + public void testThen2() throws Exception { + Deferred def = new Deferred(); + + Promise chain1 = def.getPromise().then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + return Promises.resolved("success1"); + } + }); + assertFalse("chain not resolved", chain1.isDone()); + + Promise chain2 = def.getPromise().then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + return Promises.resolved("success2"); + } + }); + assertFalse("chain not resolved", chain2.isDone()); + + def.resolve("ok"); + assertTrue("chain1 resolved", chain1.isDone()); + assertEquals("chain1 value matches", "success1", chain1.getValue()); + assertTrue("chain2 resolved", chain2.isDone()); + assertEquals("chain2 value matches", "success2", chain2.getValue()); + } + + @Test + public void testThenResolved() throws Exception { + Deferred def = new Deferred(); + def.resolve("already resolved"); + + Promise chain = def.getPromise().then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + return Promises.resolved("success!"); + } + }); + assertTrue("chain resolved", chain.isDone()); + + assertEquals("chain value matches", "success!", chain.getValue()); + } + + @Test + public void testThenResolved2() throws Exception { + Deferred def = new Deferred(); + def.resolve("already resolved"); + + Promise chain = def.getPromise().then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + return Promises.resolved("success1"); + } + }).then(new Success() { + @Override + public Promise call(Promise resolved) throws Exception { + return Promises.resolved("success2"); + } + }); + + assertTrue("chain resolved", chain.isDone()); + + assertEquals("chain value matches", "success2", chain.getValue()); + } +} Added: aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/DeferredTest.java URL: http://svn.apache.org/viewvc/aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/DeferredTest.java?rev=1678227&view=auto ============================================================================== --- aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/DeferredTest.java (added) +++ aries/trunk/async/promise-api/src/test/java/org/apache/aries/async/promise/test/DeferredTest.java Thu May 7 15:00:53 2015 @@ -0,0 +1,128 @@ +package org.apache.aries.async.promise.test; + +import org.osgi.util.promise.Deferred; +import org.osgi.util.promise.Promise; +import org.junit.Test; + +import java.lang.reflect.InvocationTargetException; + +import static org.junit.Assert.*; + +public class DeferredTest { + + @Test + public void testResolve() throws Exception { + Deferred def = new Deferred(); + Promise promise = def.getPromise(); + assertFalse("Initial Promise not resolved", promise.isDone()); + + def.resolve("Hello"); + assertTrue("Promise resolved", promise.isDone()); + assertEquals("Value matches", "Hello", promise.getValue()); + assertNull("Failure is null", promise.getFailure()); + + try { + def.resolve("Again"); + fail("Already resolved didn't throw IllegalStateException"); + } catch (IllegalStateException e) { + // suppress empty catch block warning + } + } + + @Test + public void testResolveWithSuccess() throws Exception { + Deferred def = new Deferred(); + Promise promise = def.getPromise(); + + Deferred with = new Deferred(); + Promise resolvedWith = def.resolveWith(with.getPromise()); + + // If the specified Promise is successfully resolved, + // the associated Promise is resolved with the value of the specified Promise. + with.resolve("resolveWith"); + assertTrue("Promise resolved", promise.isDone()); + assertEquals("Value matches", "resolveWith", promise.getValue()); + + // The returned Promise will be successfully resolved, with the value null, + // if the associated Promise was resolved by the specified Promise. + assertNull("resolveWith null", resolvedWith.getValue()); + } + + @Test + public void testResolveWithAlreadyResolved() throws Exception { + Deferred def = new Deferred(); + Deferred with = new Deferred(); + Promise resolvedWith = def.resolveWith(with.getPromise()); + + // The returned Promise will be resolved with a failure of IllegalStateException + // if the associated Promise was already resolved when the specified Promise was resolved. + def.resolve("Already resolved"); + with.resolve("resolveWith"); + @SuppressWarnings({"not thrown", "all"}) + Throwable failure = resolvedWith.getFailure(); + assertTrue("resolveWith IllegalStateException", failure instanceof IllegalStateException); + } + + @Test + public void testResolveWithAlreadyFailed() throws Exception { + Deferred def = new Deferred(); + Deferred with = new Deferred(); + Promise resolvedWith = def.resolveWith(with.getPromise()); + + // The returned Promise will be resolved with a failure of IllegalStateException + // if the associated Promise was already resolved when the specified Promise was resolved. + def.resolve("Already resolved"); + with.fail(new Throwable("failed")); + @SuppressWarnings({"not thrown", "all"}) + Throwable failure = resolvedWith.getFailure(); + assertTrue("resolveWith IllegalStateException", failure instanceof IllegalStateException); + } + + @Test + public void testResolveWithFailure() throws Exception { + Deferred def = new Deferred(); + Promise promise = def.getPromise(); + + Deferred def2 = new Deferred(); + Promise promise2 = def2.getPromise(); + Promise with = def.resolveWith(promise2); + + // If the specified Promise is resolved with a failure, + // the associated Promise is resolved with the failure of the specified Promise. + Exception failure = new Exception("resolveWithFailure"); + def2.fail(failure); + assertTrue("Promise resolved", promise.isDone()); + assertEquals("Failure matches", failure, promise.getFailure()); + + // The returned Promise will be successfully resolved, with the value null, + // if the associated Promise was resolved by the specified Promise. + assertNull("resolveWith null", with.getValue()); + } + + @Test + public void testFail() throws Exception { + Deferred def = new Deferred(); + Promise promise = def.getPromise(); + Exception failure = new Exception("Oops"); + + def.fail(failure); + assertTrue("Promise resolved", promise.isDone()); + assertEquals("Failure matches", failure, promise.getFailure()); + + try { + promise.getValue(); + fail("getValue didn't throw InvocationTargetException"); + } catch (InvocationTargetException e) { + assertEquals("Failure matches", failure, e.getCause()); + } + + try { + def.fail(failure); + fail("Already failed didn't throw IllegalStateException"); + } catch (IllegalStateException e) { + assertNotNull(e); + } + } + + +}