classpathArtifacts = project.getArtifacts();
103 |
104 | for (Artifact artifact : classpathArtifacts)
105 | {
106 | if (artifact.getArtifactHandler().isAddedToClasspath())
107 | {
108 | File file = artifact.getFile();
109 | if (file != null)
110 | {
111 | classpath.add(file.getPath());
112 | }
113 | }
114 | }
115 | return classpath;
116 | }
117 |
118 | @Override
119 | protected ClassLoader createClassLoader()
120 | {
121 | final URL[] urls = generateTestClasspath().stream().map(f -> {
122 | try
123 | {
124 | return new File(f).toURI().toURL();
125 | }
126 | catch (MalformedURLException e)
127 | {
128 | throw new RuntimeException(e);
129 | }
130 | }).toArray(s -> new URL[s]);
131 | return new URLClassLoader(urls);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/async/src/main/java/com/ea/async/Async.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async;
30 |
31 | import com.ea.async.instrumentation.InitializeAsync;
32 |
33 | import org.slf4j.Logger;
34 | import org.slf4j.LoggerFactory;
35 |
36 | import java.util.concurrent.CompletableFuture;
37 | import java.util.concurrent.CompletionStage;
38 |
39 | /**
40 | * This class together with bytecode instrumentation implements the async-await functionality in the JVM.
41 | *
42 | * To use it call Async.await(future) from within methods whose return type is
43 | * CompletionStage, CompletableFuture or subclasses of CompletableFuture.
44 | *
45 | * Async.await(future) won't block. It is semantically equivalent to call future.join()
46 | * But instead of blocking the code in instrumented to become a state machine that evolves as the futures passed
47 | * to await are completed.
48 | *
49 | * This is equivalent to use CompletableFuture composition methods (ex: thenApply, handle, thenCompose).
50 | * The advantage of using await is that the code will resemble sequential blocking code.
51 | *
52 | * Example:
53 | *
54 | * import static com.ea.async.Async.await;
55 | * import static java.util.concurrent.CompletableFuture.completedFuture;*
56 | *
57 | * public class Store
58 | * {
59 | * public CompletableFuture buyItem(String itemTypeId, int cost)
60 | * {
61 | * if(!await(bank.decrement(cost))) {
62 | * return completedFuture(false);
63 | * }
64 | * await(inventory.giveItem(itemTypeId));
65 | * return completedFuture(true);
66 | * }
67 | * }
68 | *
69 | */
70 | public class Async
71 | {
72 | /**
73 | * Ensure that if no pre instrumentation was done, that the Async runtime instrumentation is running.
74 | *
75 | * Attention! The build time instrumentation will remove calls to this method.
76 | */
77 | public static void init()
78 | {
79 | InitializeAsync.init();
80 | }
81 |
82 | private static Logger logger;
83 |
84 | /**
85 | * This method will behave as a CompletableFuture.join() but will actually cause the
86 | * caller to return a promise instead of blocking.
87 | *
88 | * Calls to this method are replaced by the EA Async instrumentation.
89 | *
90 | * @param future a future to wait for.
91 | * @param the future value type.
92 | * @return the return value of the future
93 | * @throws java.util.concurrent.CompletionException wrapping the actual exception if an exception occured.
94 | */
95 | public static > T await(F future)
96 | {
97 | String warning;
98 | if (!InitializeAsync.isRunning())
99 | {
100 | warning = "Warning: Illegal call to await, static { Async.init(); } must be added to the main program class and the method invoking await must return a CompletableFuture";
101 | }
102 | else
103 | {
104 | warning = "Warning: Illegal call to await, the method invoking await must return a CompletableFuture";
105 | }
106 | LoggerFactory.getLogger(Async.class);
107 | if (logger == null)
108 | {
109 | logger = LoggerFactory.getLogger(Async.class);
110 | }
111 | if (logger.isDebugEnabled())
112 | {
113 | logger.warn(warning, new Throwable());
114 | }
115 | else
116 | {
117 | logger.warn(warning);
118 | }
119 | if (future instanceof CompletableFuture)
120 | {
121 | //noinspection unchecked
122 | return ((CompletableFuture) future).join();
123 | }
124 | return future.toCompletableFuture().join();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/maven-plugin/pom.xml:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 | 4.0.0
31 |
32 | com.ea.async
33 | ea-async-parent
34 | 1.2.4-SNAPSHOT
35 | ..
36 |
37 | com.ea.async
38 | ea-async-maven-plugin
39 | maven-plugin
40 | EA Async-Await Maven Plugin
41 | Pre instruments class files to work with the async-await paradigm
42 |
43 |
44 |
45 |
46 | org.apache.maven.plugins
47 | maven-plugin-plugin
48 | 3.5.1
49 |
50 |
51 | default-descriptor
52 |
53 | descriptor
54 |
55 | process-classes
56 |
57 |
58 | help-descriptor
59 |
60 | helpmojo
61 |
62 | process-classes
63 |
64 |
65 |
66 |
67 | maven-clean-plugin
68 | 3.0.0
69 |
70 | false
71 |
72 |
73 |
74 |
75 |
76 |
77 | com.ea.async
78 | ea-async
79 | ${project.version}
80 |
81 |
82 |
83 | org.apache.maven
84 | maven-artifact
85 | 3.5.3
86 |
87 |
88 |
89 | org.apache.maven
90 | maven-plugin-api
91 | 3.5.3
92 |
93 |
94 |
95 | org.apache.maven
96 | maven-core
97 | 3.5.3
98 |
99 |
100 |
101 | org.codehaus.plexus
102 | plexus-archiver
103 | 3.7.0
104 |
105 |
106 |
107 | org.apache.maven
108 | maven-compat
109 | 3.5.3
110 | test
111 |
112 |
113 |
114 |
115 | org.apache.maven.plugin-tools
116 | maven-plugin-annotations
117 | 3.5.1
118 | provided
119 |
120 |
121 |
122 | org.apache.maven.plugin-testing
123 | maven-plugin-testing-harness
124 | 3.3.0
125 | test
126 |
127 |
128 |
129 | junit
130 | junit
131 | test
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/async/src/test/java/com/ea/async/test/AConstNullTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async.test;
30 |
31 | import com.ea.async.Task;
32 |
33 | import org.junit.Test;
34 |
35 | import static com.ea.async.Async.await;
36 | import static org.junit.Assert.assertNull;
37 |
38 | public class AConstNullTest extends BaseTest
39 | {
40 |
41 | @Test
42 | public void nullInitialization() throws Exception
43 | {
44 | final Task res = NullInit.doIt(getBlockedTask());
45 | completeFutures();
46 | assertNull(res.join());
47 |
48 | }
49 |
50 | public static class NullInit
51 | {
52 | public static Task doIt(Task task)
53 | {
54 | String x = null;
55 | try
56 | {
57 | await(task);
58 | return Task.done();
59 | }
60 | catch (Exception ex)
61 | {
62 | return Task.done();
63 | }
64 | }
65 | }
66 |
67 | @Test
68 | public void nullInitialization2() throws Exception
69 | {
70 | final Task res = NullInit2.doIt(getBlockedTask());
71 | completeFutures();
72 | assertNull(res.join());
73 |
74 | }
75 |
76 | public static class NullInit2
77 | {
78 | public static Task doIt(Task task)
79 | {
80 | String x = null;
81 | String y = x;
82 | try
83 | {
84 | await(task);
85 | return Task.done();
86 | }
87 | catch (Exception ex)
88 | {
89 | return Task.done();
90 | }
91 | }
92 | }
93 |
94 | @Test
95 | public void nullInitialization3() throws Exception
96 | {
97 | final Task res = NullInit3.doIt(getBlockedTask());
98 | completeFutures();
99 | assertNull(res.join());
100 |
101 | }
102 |
103 | public static class NullInit3
104 | {
105 | public static Task doIt(Task task)
106 | {
107 | String x = null;
108 | call0(x, await(task));
109 | return Task.done();
110 | }
111 | }
112 |
113 | public static void call0(final Object a, final Object b)
114 | {
115 | }
116 |
117 |
118 | @Test
119 | public void nullInitialization4() throws Exception
120 | {
121 | final Task res = NullInit4.doIt(getBlockedTask());
122 | completeFutures();
123 | assertNull(res.join());
124 |
125 | }
126 |
127 | public static class NullInit4
128 | {
129 | public static Task doIt(Task task)
130 | {
131 | String x = null;
132 | call0(await(task), x);
133 | return Task.done();
134 | }
135 | }
136 |
137 |
138 | @Test
139 | public void nullInTheStack() throws Exception
140 | {
141 | debugTransform(AConstNullTest.class.getName() + "$NullInTheStack");
142 | final Task res = NullInTheStack.doIt(getBlockedTask());
143 | completeFutures();
144 | assertNull(res.join());
145 | }
146 |
147 | public static class NullInTheStack
148 | {
149 | public static Task doIt(Task task)
150 | {
151 | call2(null, await(task));
152 | return Task.done();
153 | }
154 |
155 | private static void call2(final Object o, final Object p)
156 | {
157 | }
158 | }
159 |
160 |
161 | @Test
162 | public void nullInTheStack2() throws Exception
163 | {
164 | debugTransform(AConstNullTest.class.getName() + "$NullInTheStack2");
165 | final Task res = NullInTheStack2.doIt(getBlockedTask());
166 | completeFutures();
167 | assertNull(res.join());
168 | }
169 |
170 | public static class NullInTheStack2
171 | {
172 | public static Task doIt(Task task)
173 | {
174 | call2(null, await(task));
175 | return Task.done();
176 | }
177 | private static void call2(final String o, final Object p)
178 | {
179 | }
180 |
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/async/src/test/java/com/ea/async/test/BasicTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async.test;
30 |
31 | import com.ea.async.Async;
32 |
33 | import org.junit.Test;
34 |
35 | import java.util.concurrent.CompletableFuture;
36 |
37 | import static com.ea.async.Async.await;
38 | import static org.junit.Assert.assertEquals;
39 |
40 | public class BasicTest extends BaseTest
41 | {
42 | public static class SomethingAsync
43 | {
44 | public CompletableFuture doSomething(CompletableFuture blocker)
45 | {
46 | String res = await(blocker);
47 | return CompletableFuture.completedFuture(":" + res);
48 | }
49 | }
50 |
51 | public static class SomethingWithDataMutation
52 | {
53 | public CompletableFuture doSomething(CompletableFuture blocker)
54 | {
55 | String op = "1";
56 | String res = "[" + await(blocker) + "]";
57 | op = op + "2";
58 | return CompletableFuture.completedFuture(":" + op + res);
59 | }
60 | }
61 |
62 | public static class SomethingWithLocalsAndStack
63 | {
64 | public CompletableFuture doSomething(CompletableFuture blocker)
65 | {
66 | int local = 7;
67 | String res = ":" + Math.max(local, await(blocker).length());
68 | return CompletableFuture.completedFuture(res);
69 | }
70 | }
71 |
72 | public static class SomethingAsyncWithEx
73 | {
74 | public CompletableFuture doSomething(CompletableFuture blocker)
75 | {
76 | try
77 | {
78 | String res = await(blocker);
79 | return CompletableFuture.completedFuture(":" + res);
80 | }
81 | catch (Exception ex)
82 | {
83 | return CompletableFuture.completedFuture(":" + ex.getCause().getMessage());
84 | }
85 | }
86 | }
87 |
88 | @Test
89 | public void testDirectPathNonBlocking() throws IllegalAccessException, InstantiationException
90 | {
91 | // test an example where the async function blocks (returns incomplete future)
92 | // this would not work without instrumentation
93 | final SomethingAsync a = new SomethingAsync();
94 |
95 | CompletableFuture blocker = CompletableFuture.completedFuture("x");
96 | final CompletableFuture res = a.doSomething(blocker);
97 | assertEquals(":x", res.join());
98 | }
99 |
100 | @Test(timeout = 2_000)
101 | public void testBlocking() throws IllegalAccessException, InstantiationException
102 | {
103 | final SomethingAsync a = new SomethingAsync();
104 | CompletableFuture blocker = new CompletableFuture<>();
105 | final CompletableFuture res = a.doSomething(blocker);
106 | blocker.complete("x");
107 | assertEquals(":x", res.join());
108 | }
109 |
110 |
111 | @Test(timeout = 2_000)
112 | public void testBlockingWithStackAndLocal() throws IllegalAccessException, InstantiationException
113 | {
114 | final SomethingWithLocalsAndStack a = new SomethingWithLocalsAndStack();
115 |
116 | CompletableFuture blocker = new CompletableFuture<>();
117 | final CompletableFuture res = a.doSomething(blocker);
118 | blocker.complete("0123456789");
119 | assertEquals(":10", res.join());
120 | }
121 |
122 | @Test(timeout = 2_000)
123 | public void testBlockingAndException() throws IllegalAccessException, InstantiationException
124 | {
125 | final SomethingAsyncWithEx a = new SomethingAsyncWithEx();
126 |
127 | CompletableFuture blocker = new CompletableFuture<>();
128 | final CompletableFuture res = a.doSomething(blocker);
129 | blocker.completeExceptionally(new RuntimeException("Exception"));
130 | assertEquals(":Exception", res.join());
131 | }
132 |
133 | @Test(timeout = 2_000)
134 | public void testDataFlow()
135 | {
136 | final SomethingWithDataMutation a = new SomethingWithDataMutation();
137 |
138 | CompletableFuture blocker = new CompletableFuture<>();
139 | final CompletableFuture res = a.doSomething(blocker);
140 | blocker.complete("x");
141 | assertEquals(":12[x]", res.join());
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/gradle-plugin/pom.xml:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 | 4.0.0
31 |
32 | com.ea.async
33 | ea-async-parent
34 | 1.2.4-SNAPSHOT
35 | ..
36 |
37 | ea-async-gradle-plugin
38 | jar
39 | EA Async-Await Gradle Plugin
40 | Pre-instruments class files to work with the async-await paradigm
41 |
42 |
43 |
44 |
45 | org.apache.maven.plugins
46 | maven-compiler-plugin
47 |
48 |
49 | maven-clean-plugin
50 | 3.0.0
51 |
52 | false
53 |
54 |
55 |
56 | maven-assembly-plugin
57 |
58 |
59 | jar-with-dependencies
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | gradle
68 | Gradle Releases Repository
69 | https://repo.gradle.org/gradle/libs-releases-local/
70 |
71 |
72 |
73 |
74 | com.ea.async
75 | ea-async
76 | ${project.version}
77 |
78 |
79 |
80 | org.apache.maven
81 | maven-artifact
82 | 3.5.3
83 |
84 |
85 |
86 | org.apache.maven
87 | maven-core
88 | 3.5.3
89 |
90 |
91 |
92 |
93 |
94 | org.codehaus.groovy
95 | groovy
96 | 2.5.8
97 | provided
98 |
99 |
100 |
101 | org.gradle
102 | gradle-core
103 | 5.6.4
104 | provided
105 |
106 |
107 |
108 | org.gradle
109 | gradle-core-api
110 | 5.6.4
111 | provided
112 |
113 |
114 |
115 | org.gradle
116 | gradle-base-services
117 | 5.6.4
118 | provided
119 |
120 |
121 |
122 | org.gradle
123 | gradle-model-core
124 | 5.6.4
125 | provided
126 |
127 |
128 |
129 | org.gradle
130 | gradle-language-jvm
131 | 5.6.4
132 | provided
133 |
134 |
135 |
136 | org.gradle
137 | gradle-language-java
138 | 5.6.4
139 | provided
140 |
141 |
142 |
143 | org.gradle
144 | gradle-platform-jvm
145 | 5.6.4
146 | provided
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/async/src/test/java/com/ea/async/instrumentation/TransformerMainErrorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async.instrumentation;
30 |
31 | import com.ea.async.test.BaseTest;
32 |
33 | import org.junit.Test;
34 | import org.objectweb.asm.ClassReader;
35 | import org.objectweb.asm.ClassWriter;
36 | import org.objectweb.asm.MethodVisitor;
37 | import org.objectweb.asm.tree.ClassNode;
38 |
39 | import java.io.ByteArrayOutputStream;
40 | import java.io.OutputStreamWriter;
41 | import java.io.PrintStream;
42 | import java.io.StringWriter;
43 | import java.nio.file.Files;
44 | import java.nio.file.Path;
45 | import java.nio.file.Paths;
46 | import java.util.Arrays;
47 |
48 | import static org.junit.Assert.*;
49 | import static org.objectweb.asm.Opcodes.*;
50 |
51 | public class TransformerMainErrorTest extends BaseTest
52 | {
53 |
54 | public static final String COM_EA_ASYNC_TASK = "com/ea/async/Task";
55 |
56 | // sanity check of the creator
57 | @Test
58 | @SuppressWarnings("unchecked")
59 | public void transformerError() throws Exception
60 | {
61 | final ByteArrayOutputStream out = new ByteArrayOutputStream();
62 | final PrintStream printStream = new PrintStream(out);
63 | final PrintStream err = System.err;
64 | System.setErr(printStream);
65 | try
66 | {
67 | final ClassNode cn = createClassNode(Object.class, cw -> {
68 | MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", null, new String[]{ "java/lang/Exception" });
69 | mv.visitCode();
70 | mv.visitVarInsn(ALOAD, 1);
71 | mv.visitTypeInsn(CHECKCAST, COM_EA_ASYNC_TASK);
72 | mv.visitMethodInsn(INVOKESTATIC, ASYNC_NAME, "await", "(Ljava/util/concurrent/CompletableFuture;)Ljava/lang/Object;", false);
73 | mv.visitInsn(POP);
74 | mv.visitVarInsn(ALOAD, 1);
75 | mv.visitTypeInsn(CHECKCAST, COM_EA_ASYNC_TASK);
76 | mv.visitInsn(ARETURN);
77 | mv.visitMaxs(1, 2);
78 | mv.visitEnd();
79 | });
80 | ClassWriter cw = new ClassWriter(0);
81 | cn.accept(cw);
82 | final byte[] bytes = new Transformer().transform(getClass().getClassLoader(), new ClassReader(cw.toByteArray()));
83 |
84 | final Main main = new Main();
85 | final Path path = Paths.get("target/transformerError/classes2").resolve(cn.name + ".class");
86 | Files.deleteIfExists(path);
87 | Files.createDirectories(path.getParent());
88 | Files.write(path, cw.toByteArray());
89 |
90 | // still not transformed
91 | final byte[] original = Files.readAllBytes(path);
92 | assertFalse(Arrays.equals(bytes, original));
93 |
94 | final Path path3 = Paths.get("target/transformerError/classes3").resolve(cn.name + ".class");
95 | Files.deleteIfExists(path3);
96 | // in output dir
97 | main.doMain(new String[]{ "-d", "target/transformerError/classes3", path.toString() });
98 | printStream.flush();
99 | assertTrue(new String(out.toByteArray()).startsWith("Invalid use of"));
100 | out.reset();
101 | assertArrayEquals(bytes, Files.readAllBytes(path3));
102 | // still unchanged
103 | assertArrayEquals(original, Files.readAllBytes(path));
104 |
105 | // passing a dir as parameter
106 | final Path path4 = Paths.get("target/transformerError/classes4").resolve(cn.name + ".class");
107 | Files.deleteIfExists(path4);
108 | main.doMain(new String[]{ "-d", "target/transformerError/classes4", path.getParent().toString() });
109 | printStream.flush();
110 | assertTrue(new String(out.toByteArray()).startsWith("Invalid use of"));
111 | out.reset();
112 | assertTrue("Can't find file: " + path4, Files.exists(path4));
113 | assertArrayEquals(bytes, Files.readAllBytes(path4));
114 | // still unchanged
115 | assertArrayEquals(original, Files.readAllBytes(path));
116 |
117 | // in place
118 | main.doMain(new String[]{ path.toString() });
119 | printStream.flush();
120 | assertTrue(new String(out.toByteArray()).startsWith("Invalid use of"));
121 | out.reset();
122 | assertArrayEquals(bytes, Files.readAllBytes(path));
123 | assertFalse(Arrays.equals(original, Files.readAllBytes(path)));
124 | }
125 | finally
126 | {
127 | System.setErr(err);
128 | }
129 |
130 | }
131 |
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/async/src/main/java/com/ea/async/instrumentation/Main.java:
--------------------------------------------------------------------------------
1 | package com.ea.async.instrumentation;
2 |
3 |
4 | import org.objectweb.asm.ClassReader;
5 |
6 | import java.io.FileInputStream;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.nio.file.Files;
10 | import java.nio.file.Path;
11 | import java.nio.file.Paths;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * Tool class to perform build time instrumentation.
17 | */
18 | public class Main
19 | {
20 | private boolean verbose;
21 | private List fileList;
22 | private Path outputDirectory;
23 | private ClassLoader classLoader = getClass().getClassLoader();
24 |
25 | public static void main(String args[]) throws IOException
26 | {
27 | final int ret = new Main().doMain(args);
28 | if (ret != 0)
29 | {
30 | System.exit(-1);
31 | }
32 | }
33 |
34 | public int doMain(final String[] args) throws IOException
35 | {
36 | outputDirectory = null;
37 |
38 | fileList = new ArrayList<>();
39 | if (args.length == 0)
40 | {
41 | printUsage();
42 | return 0;
43 | }
44 | for (int i = 0; i < args.length; i++)
45 | {
46 | final String arg = args[i];
47 | if ("--help".equals(arg))
48 | {
49 | printUsage();
50 | continue;
51 | }
52 | if ("-verbose".equals(arg))
53 | {
54 | verbose = true;
55 | continue;
56 | }
57 | if ("-d".equals(arg))
58 | {
59 | if (i + 1 == args.length)
60 | {
61 | error("Invalid usage of the -d option");
62 | return 1;
63 | }
64 | outputDirectory = Paths.get(args[++i]);
65 | continue;
66 | }
67 | final Path path = Paths.get(arg);
68 | if (Files.isDirectory(path))
69 | {
70 | Files.walk(path)
71 | .filter(Files::isRegularFile)
72 | .filter(p -> p.toString().endsWith(".class"))
73 | .forEach(fileList::add);
74 | continue;
75 | }
76 | if (Files.isRegularFile(path))
77 | {
78 | fileList.add(path);
79 | continue;
80 | }
81 | error("Invalid argument or file: " + arg);
82 | return 1;
83 | }
84 | return transform() >= 0 ? 0 : 1;
85 | }
86 |
87 | public int transform() throws IOException
88 | {
89 | final Transformer transformer = new Transformer();
90 | transformer.setErrorListener(System.err::println);
91 | boolean error = false;
92 | int count = 0;
93 | final Path outputDir = getOutputDirectory();
94 | for (Path path : fileList)
95 | {
96 | byte[] bytes = null;
97 | try (InputStream in = new FileInputStream(path.toFile()))
98 | {
99 | bytes = transformer.instrument(classLoader, in);
100 | }
101 | catch (Exception e)
102 | {
103 | error("Error instrumenting " + path, e);
104 | error = true;
105 | }
106 | if (bytes != null)
107 | {
108 | if (verbose)
109 | {
110 | info("instrumented: " + path);
111 | }
112 | if (outputDir != null)
113 | {
114 | // writing to the output directory, using the package name as path.
115 | final Path outPath = outputDir.resolve(new ClassReader(bytes).getClassName() + ".class");
116 | final Path outParent = outPath.getParent();
117 | if (!Files.exists(outParent))
118 | {
119 | Files.createDirectories(outParent);
120 | }
121 | Files.write(outPath, bytes);
122 | }
123 | else
124 | {
125 | // replacing the original file
126 | Files.write(path, bytes);
127 | }
128 | count++;
129 | }
130 | }
131 | return error ? -1 : count;
132 | }
133 |
134 | private void error(final String msg, final Exception e)
135 | {
136 | System.err.println(msg);
137 | e.printStackTrace();
138 | }
139 |
140 |
141 | protected void error(String msg)
142 | {
143 | System.err.println(msg);
144 | }
145 |
146 | protected void info(String msg)
147 | {
148 | System.err.println(msg);
149 | }
150 |
151 | protected void printUsage()
152 | {
153 | System.out.println("usage:");
154 | System.out.println(" java -cp project-class-path -jar ea-async.jar -d output-directory file1 input-dir1 input-dir2");
155 | System.out.println("options:");
156 | System.out.println("-d directory");
157 | System.out.println(" Set the destination directory for class files. ");
158 | System.out.println(" If not specified the original files will be modified in place");
159 | System.out.println("-help");
160 | System.out.println(" Shows this help. ");
161 | }
162 |
163 | public boolean isVerbose()
164 | {
165 | return verbose;
166 | }
167 |
168 | public void setVerbose(final boolean verbose)
169 | {
170 | this.verbose = verbose;
171 | }
172 |
173 | public List getFileList()
174 | {
175 | return fileList;
176 | }
177 |
178 | public void setFileList(final List fileList)
179 | {
180 | this.fileList = fileList;
181 | }
182 |
183 | public Path getOutputDirectory()
184 | {
185 | return outputDirectory;
186 | }
187 |
188 | public void setOutputDirectory(final Path outputDirectory)
189 | {
190 | this.outputDirectory = outputDirectory;
191 | }
192 |
193 | public ClassLoader getClassLoader()
194 | {
195 | return classLoader;
196 | }
197 |
198 | public void setClassLoader(final ClassLoader classLoader)
199 | {
200 | this.classLoader = classLoader;
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/gradle-plugin/src/main/java/com/ea/async/gradle/plugin/AsyncPlugin.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2020 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async.gradle.plugin;
30 |
31 | import com.ea.async.instrumentation.Transformer;
32 |
33 | import org.gradle.api.Plugin;
34 | import org.gradle.api.Project;
35 | import org.gradle.api.tasks.compile.JavaCompile;
36 |
37 | import java.io.File;
38 | import java.io.FileInputStream;
39 | import java.io.IOException;
40 | import java.net.MalformedURLException;
41 | import java.net.URL;
42 | import java.net.URLClassLoader;
43 | import java.nio.file.Files;
44 | import java.nio.file.Paths;
45 | import java.util.ArrayList;
46 | import java.util.List;
47 | import java.util.Objects;
48 |
49 | /**
50 | * A plugin that allows easily integrating EA Async instrumentation into the Gradle build process.
51 | */
52 | public class AsyncPlugin implements Plugin
53 | {
54 |
55 | /**
56 | * Applies the plugin to the project. Prepares Java compilation tasks to be followed up on by instrumentation.
57 | * @param project the Gradle project to which the plugin was applied
58 | */
59 | @Override
60 | public void apply(final Project project)
61 | {
62 | project.getTasks().withType(JavaCompile.class, task ->
63 | {
64 | // This will be called for every JavaCompile task, whether already existing or added dynamically later
65 | task.doLast("EA Async instrumentation", t -> instrumentCompileResults(task));
66 | });
67 | }
68 |
69 | /**
70 | * Rewrites compiled classes output by a Java compilation task.
71 | * Called for each compilation task after it finishes.
72 | * @param task the Java compilation task whose output to instrument
73 | */
74 | private void instrumentCompileResults(final JavaCompile task)
75 | {
76 | // Set up the Transformer to explode on failure
77 | final Transformer asyncTransformer = new Transformer();
78 | asyncTransformer.setErrorListener(err ->
79 | {
80 | throw new RuntimeException("Failed to instrument the output of " + task.toString() + ": " + err);
81 | });
82 | // Create a classloader that can see the same CompletionStage subclasses as the compiler
83 | final List classpathUrls = new ArrayList<>();
84 | try
85 | {
86 | for (File f : task.getClasspath().plus(task.getOutputs().getFiles()).getFiles())
87 | {
88 | classpathUrls.add(f.toURI().toURL());
89 | }
90 | }
91 | catch (MalformedURLException e)
92 | {
93 | throw new RuntimeException("Bad URL when preparing instrumentation of " + task.toString(), e);
94 | }
95 | final ClassLoader classLoader = new URLClassLoader(classpathUrls.toArray(new URL[0]));
96 | // Rewrite the class files in the output directory
97 | instrumentFile(task.getDestinationDir(), task, asyncTransformer, classLoader);
98 | }
99 |
100 | /**
101 | * Recursively instruments the specified file system entry and its descendants.
102 | * @param fsEntry a File in the output directory of a task
103 | * @param task the JavaCompile task whose output is being instrumented
104 | * @param asyncTransformer the transformer used to instrument the classes
105 | * @param classLoader the classloader to supply to the transformer
106 | */
107 | private void instrumentFile(final File fsEntry, final JavaCompile task,
108 | final Transformer asyncTransformer, final ClassLoader classLoader)
109 | {
110 | if (fsEntry.isDirectory())
111 | {
112 | // Recurse into subdirectories and files
113 | for (File subentry : Objects.requireNonNull(fsEntry.listFiles())) {
114 | instrumentFile(subentry, task, asyncTransformer, classLoader);
115 | }
116 | }
117 | else if (fsEntry.isFile() && fsEntry.getName().endsWith(".class"))
118 | {
119 | // Instrument a class
120 | byte[] instrumentedClass;
121 | try (FileInputStream fis = new FileInputStream(fsEntry))
122 | {
123 | instrumentedClass = asyncTransformer.instrument(classLoader, fis);
124 | }
125 | catch (IOException e)
126 | {
127 | throw new RuntimeException("Failed to instrument '" + fsEntry.getPath() + "' from " + task.toString(), e);
128 | }
129 | if (instrumentedClass != null)
130 | {
131 | // Transformer#instrument returns null if no changes were needed
132 | try
133 | {
134 | Files.write(Paths.get(fsEntry.getAbsolutePath()), instrumentedClass);
135 | }
136 | catch (IOException e)
137 | {
138 | throw new RuntimeException("Failed to write '" + fsEntry.getPath() + "'", e);
139 | }
140 | }
141 | }
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/async/src/test/java/com/ea/async/instrumentation/TransformerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async.instrumentation;
30 |
31 | import com.ea.async.test.BaseTest;
32 | import com.ea.async.Task;
33 |
34 | import org.junit.Test;
35 | import org.objectweb.asm.ClassReader;
36 | import org.objectweb.asm.ClassWriter;
37 | import org.objectweb.asm.MethodVisitor;
38 | import org.objectweb.asm.tree.ClassNode;
39 |
40 | import static org.junit.Assert.assertEquals;
41 | import static org.objectweb.asm.Opcodes.*;
42 |
43 | public class TransformerTest extends BaseTest
44 | {
45 |
46 | public static final String COM_EA_ASYNC_TASK = "com/ea/async/Task";
47 |
48 | // sanity check of the creator
49 | @Test
50 | @SuppressWarnings("unchecked")
51 | public void simpleAsyncMethod() throws Exception
52 | {
53 | final ClassNode cn = createClassNode(AsyncFunction.class, cw -> {
54 | MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "apply", "(Ljava/lang/Object;)Lcom/ea/async/Task;", null, new String[]{ "java/lang/Exception" });
55 | mv.visitCode();
56 | mv.visitVarInsn(ALOAD, 1);
57 | mv.visitTypeInsn(CHECKCAST, COM_EA_ASYNC_TASK);
58 | mv.visitMethodInsn(INVOKESTATIC, ASYNC_NAME, "await", "(Ljava/util/concurrent/CompletableFuture;)Ljava/lang/Object;", false);
59 | mv.visitInsn(POP);
60 | mv.visitVarInsn(ALOAD, 1);
61 | mv.visitTypeInsn(CHECKCAST, COM_EA_ASYNC_TASK);
62 | mv.visitInsn(ARETURN);
63 | mv.visitMaxs(1, 2);
64 | mv.visitEnd();
65 | });
66 | ClassWriter cw = new ClassWriter(0);
67 | cn.accept(cw);
68 | final byte[] bytes = new Transformer().transform(getClass().getClassLoader(), new ClassReader(cw.toByteArray()));
69 | DevDebug.debugSaveTrace(cn.name, bytes);
70 | assertEquals("hello", createClass(AsyncFunction.class, bytes).apply(Task.fromValue("hello")).join());
71 | }
72 |
73 | @Test
74 | @SuppressWarnings("unchecked")
75 | public void withLocals() throws Exception
76 | {
77 | final ClassNode cn = createClassNode(AsyncFunction.class, cw -> {
78 | MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "apply", "(Ljava/lang/Object;)L"+COM_EA_ASYNC_TASK+";", null, new String[]{ "java/lang/Exception" });
79 | mv.visitCode();
80 | mv.visitIntInsn(SIPUSH, 1);
81 | mv.visitVarInsn(ISTORE, 2);
82 |
83 | mv.visitInsn(LCONST_0);
84 | mv.visitVarInsn(LSTORE, 3);
85 | mv.visitVarInsn(ALOAD, 1);
86 | mv.visitTypeInsn(CHECKCAST, COM_EA_ASYNC_TASK);
87 | mv.visitMethodInsn(INVOKESTATIC, ASYNC_NAME, "await", "(Ljava/util/concurrent/CompletableFuture;)Ljava/lang/Object;", false);
88 | mv.visitInsn(POP);
89 | mv.visitVarInsn(ALOAD, 1);
90 | mv.visitTypeInsn(CHECKCAST, COM_EA_ASYNC_TASK);
91 | mv.visitInsn(ARETURN);
92 | mv.visitMaxs(2, 5);
93 | mv.visitEnd();
94 | });
95 | ClassWriter cw = new ClassWriter(0);
96 | cn.accept(cw);
97 | final byte[] bytes = new Transformer().transform(getClass().getClassLoader(), new ClassReader(cw.toByteArray()));
98 | // DevDebug.debugSaveTrace(cn.name, bytes);
99 | assertEquals("hello", createClass(AsyncFunction.class, bytes).apply(Task.fromValue("hello")).join());
100 | }
101 |
102 |
103 | @Test(timeout = 10_000L)
104 | @SuppressWarnings("unchecked")
105 | public void withTwoFutures() throws Exception
106 | {
107 | final ClassNode cn = createClassNode(AsyncBiFunction.class, cw -> {
108 | MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "apply", "(Ljava/lang/Object;Ljava/lang/Object;)L"+COM_EA_ASYNC_TASK+";", null, new String[]{ "java/lang/Exception" });
109 | mv.visitCode();
110 |
111 | mv.visitVarInsn(ALOAD, 1);
112 | mv.visitTypeInsn(CHECKCAST, COM_EA_ASYNC_TASK);
113 | mv.visitMethodInsn(INVOKESTATIC, ASYNC_NAME, "await", "(Ljava/util/concurrent/CompletableFuture;)Ljava/lang/Object;", false);
114 | mv.visitInsn(POP);
115 |
116 | mv.visitVarInsn(ALOAD, 2);
117 | mv.visitTypeInsn(CHECKCAST, COM_EA_ASYNC_TASK);
118 | mv.visitMethodInsn(INVOKESTATIC, ASYNC_NAME, "await", "(Ljava/util/concurrent/CompletableFuture;)Ljava/lang/Object;", false);
119 | mv.visitInsn(POP);
120 |
121 | mv.visitVarInsn(ALOAD, 1);
122 | mv.visitTypeInsn(CHECKCAST, COM_EA_ASYNC_TASK);
123 | mv.visitInsn(ARETURN);
124 | mv.visitMaxs(2, 5);
125 | mv.visitEnd();
126 | });
127 | ClassWriter cw = new ClassWriter(0);
128 | cn.accept(cw);
129 | final byte[] bytes = new Transformer().transform(getClass().getClassLoader(), new ClassReader(cw.toByteArray()));
130 | DevDebug.debugSaveTrace(cn.name, bytes);
131 |
132 | assertEquals("hello", createClass(AsyncBiFunction.class, bytes).apply(Task.fromValue("hello"), Task.fromValue("world")).join());
133 |
134 | final Task rest = createClass(AsyncBiFunction.class, bytes).apply(getBlockedTask("hello"), getBlockedTask("world"));
135 | completeFutures();
136 | assertEquals("hello", rest.join());
137 | }
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/async/src/test/java/com/ea/async/instrumentation/DevDebug.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async.instrumentation;
30 |
31 | import org.objectweb.asm.ClassReader;
32 | import org.objectweb.asm.tree.AbstractInsnNode;
33 | import org.objectweb.asm.tree.ClassNode;
34 | import org.objectweb.asm.tree.MethodNode;
35 | import org.objectweb.asm.tree.analysis.Analyzer;
36 | import org.objectweb.asm.tree.analysis.AnalyzerException;
37 | import org.objectweb.asm.tree.analysis.BasicValue;
38 | import org.objectweb.asm.tree.analysis.Frame;
39 | import org.objectweb.asm.util.Textifier;
40 | import org.objectweb.asm.util.TraceClassVisitor;
41 | import org.objectweb.asm.util.TraceMethodVisitor;
42 |
43 | import java.io.IOException;
44 | import java.io.PrintWriter;
45 | import java.io.StringWriter;
46 | import java.nio.charset.Charset;
47 | import java.nio.file.Files;
48 | import java.nio.file.Path;
49 | import java.nio.file.Paths;
50 |
51 | /**
52 | * For development use only
53 | */
54 | public class DevDebug
55 | {
56 | public static void printMethod(final ClassNode cn, final MethodNode mv)
57 | {
58 | final PrintWriter pw = new PrintWriter(System.out);
59 | pw.println("method " + mv.name + mv.desc);
60 | Textifier p = new Textifier();
61 | final TraceMethodVisitor tv = new TraceMethodVisitor(p);
62 |
63 | try
64 | {
65 | Analyzer analyzer2 = new Analyzer(new FrameAnalyzer.TypeInterpreter());
66 | Frame[] frames2;
67 | try
68 | {
69 | frames2 = analyzer2.analyze(cn.superName, mv);
70 | }
71 | catch (AnalyzerException ex)
72 | {
73 | if (ex.node != null)
74 | {
75 | pw.print("Error at: ");
76 | ex.node.accept(tv);
77 | }
78 | ex.printStackTrace();
79 | frames2 = null;
80 | }
81 | catch (Exception ex)
82 | {
83 | ex.printStackTrace();
84 | frames2 = null;
85 | }
86 |
87 | final AbstractInsnNode[] isns2 = mv.instructions.toArray();
88 |
89 | for (int i = 0; i < isns2.length; i++)
90 | {
91 | Frame frame;
92 | if (frames2 != null && (frame = frames2[i]) != null)
93 | {
94 | p.getText().add("Locals: [ ");
95 | for (int x = 0; x < frame.getLocals(); x++)
96 | {
97 | p.getText().add(String.valueOf(((BasicValue) frame.getLocal(x)).getType()));
98 | p.getText().add(" ");
99 | }
100 | p.getText().add("]\r\n");
101 | p.getText().add("Stack: [ ");
102 | for (int x = 0; x < frame.getStackSize(); x++)
103 | {
104 | p.getText().add(String.valueOf(((BasicValue) frame.getStack(x)).getType()));
105 | p.getText().add(" ");
106 | }
107 | p.getText().add("]\r\n");
108 | }
109 | p.getText().add(i + ": ");
110 | isns2[i].accept(tv);
111 | }
112 | p.print(pw);
113 | pw.println();
114 | pw.flush();
115 | }
116 | catch (Exception ex)
117 | {
118 | ex.printStackTrace();
119 | }
120 | }
121 |
122 |
123 | public static void debugSave(final ClassNode classNode, final byte[] bytes)
124 | {
125 | try
126 | {
127 |
128 | Path path = Paths.get("target/classes2/" + classNode.name + ".class");
129 | Files.createDirectories(path.getParent());
130 | Files.write(path, bytes);
131 | }
132 | catch (IOException e)
133 | {
134 | e.printStackTrace();
135 | }
136 | }
137 |
138 |
139 | public static void debugSaveTrace(String name, final byte[] bytes)
140 | {
141 | try
142 | {
143 | StringWriter sw = new StringWriter();
144 | PrintWriter pw = new PrintWriter(sw);
145 | new ClassReader(bytes).accept(new TraceClassVisitor(pw), 0);
146 | pw.flush();
147 |
148 | Path path = Paths.get("target/classes2/" + name + ".asmtrace");
149 | Files.createDirectories(path.getParent());
150 | Files.write(path, sw.toString().getBytes(Charset.forName("UTF-8")));
151 | System.out.println("Writing file to " + path.toUri());
152 | }
153 | catch (IOException e)
154 | {
155 | e.printStackTrace();
156 | }
157 | }
158 |
159 | public static void debugSaveTrace(String name, ClassNode node)
160 | {
161 | try
162 | {
163 | StringWriter sw = new StringWriter();
164 | PrintWriter pw = new PrintWriter(sw);
165 | node.accept(new TraceClassVisitor(pw));
166 | pw.flush();
167 |
168 | Path path = Paths.get("target/classes2/" + name + ".asmtrace");
169 | Files.createDirectories(path.getParent());
170 | Files.write(path, sw.toString().getBytes(Charset.forName("UTF-8")));
171 | System.out.println("Writing file to " + path.toUri());
172 | }
173 | catch (IOException e)
174 | {
175 | e.printStackTrace();
176 | }
177 | }
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/async/src/test/java/com/ea/async/test/ExceptionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async.test;
30 |
31 | import com.ea.async.Async;
32 | import com.ea.async.Task;
33 |
34 | import org.junit.Test;
35 |
36 | import java.lang.reflect.InvocationTargetException;
37 |
38 | import static com.ea.async.Async.await;
39 | import static junit.framework.TestCase.assertNull;
40 | import static org.junit.Assert.assertEquals;
41 | import static org.junit.Assert.assertTrue;
42 |
43 | public class ExceptionTest extends BaseTest
44 | {
45 |
46 | @Test
47 | public void testTryCatch() throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException
48 | {
49 | final Task res = doTryCatch();
50 | completeFutures();
51 | assertEquals((Integer) 10, res.join());
52 | }
53 |
54 | private Task doTryCatch()
55 | {
56 | try
57 | {
58 | if (await(getBlockedFuture(10)) == 10)
59 | {
60 | throw new IllegalArgumentException(String.valueOf(10));
61 | }
62 |
63 | }
64 | catch (IllegalArgumentException ex)
65 | {
66 | return Task.fromValue(10);
67 | }
68 | return null;
69 | }
70 |
71 | @Test
72 | public void testFinally() throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException
73 | {
74 | final Task res = doTestFinally();
75 | completeFutures();
76 | assertEquals((Integer) 1337, res.join());
77 | }
78 |
79 | private Task doTestFinally()
80 | {
81 | try
82 | {
83 | if (await(getBlockedFuture(10)) == 10)
84 | {
85 | throw new IllegalArgumentException(String.valueOf(10));
86 | }
87 |
88 | }
89 | finally
90 | {
91 | return Task.fromValue(1337);
92 | }
93 | }
94 |
95 |
96 | @Test
97 | public void testTryCatch2() throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException
98 | {
99 | final Task res = doTryCatch2();
100 | completeFutures();
101 | assertTrue(res.isDone());
102 | }
103 |
104 | private Task doTryCatch2()
105 | {
106 | int c = 1;
107 | try
108 | {
109 | await(getBlockedFuture());
110 | }
111 | catch (Exception ex)
112 | {
113 | // once this was causing a verification error, now fixed
114 | c = c + 1;
115 | }
116 | return Task.done();
117 | }
118 |
119 |
120 | @Test
121 | public void testTryCatch3() throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException
122 | {
123 | final Task res = doTryCatch3();
124 | completeFutures();
125 | assertEquals("fail", res.join());
126 | }
127 |
128 | private Task doTryCatch3()
129 | {
130 | int c = 1;
131 | try
132 | {
133 | await(getBlockedTask());
134 | await(getBlockedTask());
135 | await(getBlockedTask());
136 | await(getBlockedTask());
137 | await(Task.fromException(new Exception("fail")));
138 | }
139 | catch (Exception ex)
140 | {
141 | await(getBlockedTask());
142 | await(getBlockedTask());
143 | await(getBlockedTask());
144 | await(getBlockedTask());
145 | return Task.fromValue(ex.getCause().getMessage());
146 | }
147 | return Task.done();
148 | }
149 |
150 |
151 | @Test
152 | public void testTryCatch4() throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException
153 | {
154 | final Task res = doTryCatch4();
155 | completeFutures();
156 | assertEquals("fail", res.join());
157 | }
158 |
159 | private Task doTryCatch4()
160 | {
161 | int c = 1;
162 | try
163 | {
164 | await(getBlockedTask());
165 | await(getBlockedTask());
166 | if (await(getBlockedTask()) == null)
167 | {
168 | throw new Exception("fail");
169 | }
170 | await(getBlockedTask());
171 | }
172 | catch (Exception ex)
173 | {
174 | await(getBlockedTask());
175 | await(getBlockedTask());
176 | await(getBlockedTask());
177 | await(getBlockedTask());
178 | return Task.fromValue(ex.getMessage());
179 | }
180 | return Task.done();
181 | }
182 |
183 |
184 | private Object stackAnalysisProblem(final Exception ex)
185 | {
186 | Throwable t = ex;
187 |
188 | while (t.getCause() != null && !(t instanceof IllegalAccessException))
189 | {
190 | t = t.getCause();
191 | }
192 |
193 | if (t instanceof IllegalAccessException)
194 | {
195 | IllegalAccessException wex = (IllegalAccessException) t;
196 | }
197 |
198 | return null;
199 | }
200 |
201 |
202 | @Test
203 | public void smallTest() throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException
204 | {
205 | final Task> res = doIt(Task.done());
206 | completeFutures();
207 | assertNull(res.join());
208 | }
209 |
210 |
211 | public Task> doIt(Task> t)
212 | {
213 | try
214 | {
215 | await(t);
216 | }
217 | catch (Exception ex)
218 | {
219 | await(t);
220 | }
221 | return t;
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/async/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
29 |
32 | 4.0.0
33 |
34 | com.ea.async
35 | ea-async-parent
36 | 1.2.4-SNAPSHOT
37 | ..
38 |
39 | ea-async
40 | EA Async-Await
41 |
42 |
43 | 7.1
44 |
45 |
46 |
47 |
48 |
49 | org.apache.maven.plugins
50 | maven-compiler-plugin
51 |
52 |
53 | -proc:none
54 |
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-jar-plugin
59 |
60 |
61 | true
62 |
63 | com.ea.async.instrumentation.Premain
64 | com.ea.async.instrumentation.Agent
65 | com.ea.async.instrumentation.Main
66 | true
67 | true
68 |
69 |
70 |
71 |
72 |
73 | maven-shade-plugin
74 |
75 |
76 | package
77 |
78 | shade
79 |
80 |
81 |
82 |
83 | net.bytebuddy:byte-buddy-agent
84 | org.ow2.asm:asm
85 | org.ow2.asm:asm-analysis
86 | org.ow2.asm:asm-util
87 | org.ow2.asm:asm-tree
88 |
89 |
90 |
91 |
92 | org.objectweb.
93 | com.ea.async.shaded.org.objectweb.
94 |
95 |
96 | net.bytebuddy.
97 | com.ea.async.shaded.net.bytebuddy.
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | org.ow2.asm
110 | asm
111 | ${asm.version}
112 |
113 |
114 | org.ow2.asm
115 | asm-util
116 | ${asm.version}
117 |
118 |
119 | org.ow2.asm
120 | asm-analysis
121 | ${asm.version}
122 |
123 |
124 | org.ow2.asm
125 | asm-tree
126 | ${asm.version}
127 |
128 |
129 | org.slf4j
130 | slf4j-api
131 | 1.7.25
132 |
133 |
134 | net.bytebuddy
135 | byte-buddy-agent
136 | 1.9.13
137 |
138 |
139 | org.apache.commons
140 | commons-lang3
141 | 3.7
142 | test
143 |
144 |
145 | junit
146 | junit
147 | test
148 |
149 |
150 | commons-io
151 | commons-io
152 | 2.6
153 | test
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | EA Async
2 | ============
3 |
4 | [](https://github.com/electronicarts/ea-async/releases)
5 | [](http://repo1.maven.org/maven2/com/ea/async/)
6 | [](http://www.javadoc.io/doc/com.ea.async/ea-async)
7 | [](https://travis-ci.org/electronicarts/ea-async)
8 |
9 | EA Async implements Async-Await methods in the JVM.
10 | It allows programmers to write asynchronous code in a sequential fashion.
11 |
12 | It is heavily inspired by Async-Await on the .NET CLR, see [Asynchronous Programming with Async and Await](https://msdn.microsoft.com/en-us/library/hh191443.aspx) for more information.
13 |
14 | Who should use it?
15 | ------
16 |
17 | EA Async should be used to write non-blocking asynchronous code that makes heavy use of CompletableFutures or CompletionStage.
18 | It improves scalability by freeing worker threads while your code awaits other processes;
19 | And improves productivity by making asynchronous code simpler and more readable.
20 |
21 | Developer & License
22 | ======
23 | This project was developed by [Electronic Arts](http://www.ea.com) and is licensed under the [BSD 3-Clause License](LICENSE).
24 |
25 | Examples
26 | =======
27 | #### With EA Async
28 |
29 | ```java
30 | import static com.ea.async.Async.await;
31 | import static java.util.concurrent.CompletableFuture.completedFuture;
32 |
33 | public class Store
34 | {
35 | public CompletableFuture buyItem(String itemTypeId, int cost)
36 | {
37 | if(!await(bank.decrement(cost))) {
38 | return completedFuture(false);
39 | }
40 | await(inventory.giveItem(itemTypeId));
41 | return completedFuture(true);
42 | }
43 | }
44 | ```
45 | In this example `Bank.decrement` returns `CompletableFuture` and `Inventory.giveItem` returns `CompletableFuture`
46 |
47 | EA Async rewrites the calls to `Async.await` making your methods non-blocking.
48 |
49 | The methods look blocking but are actually transformed into asynchronous methods that use
50 | CompletableFutures to continue the execution as intermediary results arrive.
51 |
52 | #### Without EA Async
53 |
54 | This is how the first example looks without EA Async. It is a bit less readable.
55 |
56 | ```java
57 | import static java.util.concurrent.CompletableFuture.completedFuture;
58 |
59 | public class Store
60 | {
61 | public CompletableFuture buyItem(String itemTypeId, int cost)
62 | {
63 | return bank.decrement(cost)
64 | .thenCompose(result -> {
65 | if(!result) {
66 | return completedFuture(false);
67 | }
68 | return inventory.giveItem(itemTypeId).thenApply(res -> true);
69 | });
70 | }
71 | }
72 | ```
73 | This is a small example... A method with a few more CompletableFutures can look very convoluted.
74 |
75 | EA Async abstracts away the complexity of the CompletableFutures.
76 |
77 | #### With EA Async (2)
78 |
79 | So you like CompletableFutures?
80 | Try converting this method to use only CompletableFutures without ever blocking (so no joining):
81 |
82 | ```java
83 | import static com.ea.async.Async.await;
84 | import static java.util.concurrent.CompletableFuture.completedFuture;
85 |
86 | public class Store
87 | {
88 | public CompletableFuture buyItem(String itemTypeId, int cost)
89 | {
90 | if(!await(bank.decrement(cost))) {
91 | return completedFuture(false);
92 | }
93 | try {
94 | await(inventory.giveItem(itemTypeId));
95 | return completedFuture(true);
96 | } catch (Exception ex) {
97 | await(bank.refund(cost));
98 | throw new AppException(ex);
99 | }
100 | }
101 | }
102 | ```
103 |
104 | Got it? Send it [to us](https://github.com/electronicarts/ea-async/issues/new). It probably looks ugly...
105 |
106 | Getting started
107 | ---------------
108 |
109 | EA Async currently supports JDK 8-10.
110 |
111 | It works with Java and Scala and should work with most JVM languages.
112 | The only requirement to use EA Async is that must be used only inside methods that return `CompletableFuture`, `CompletionStage`, or subclasses of `CompletableFuture`.
113 |
114 | ### Using with maven
115 |
116 | ```xml
117 |
118 | com.ea.async
119 | ea-async
120 | 1.2.3
121 |
122 | ```
123 |
124 | ### Gradle
125 |
126 | ```
127 | 'com.ea.async:ea-async:1.2.3'
128 | ```
129 |
130 | ### Instrumenting your code
131 |
132 | #### Option 1 - JVM parameter
133 |
134 | Start your application with an extra JVM parameter: `-javaagent:ea-async-1.2.3.jar`
135 | ```
136 | java -javaagent:ea-async-1.2.3.jar -cp your_claspath YourMainClass args...
137 | ```
138 |
139 | It's recommended to add this as a default option to launchers in IntelliJ projects that use ea-async.
140 |
141 | #### Option 2 - Runtime
142 | On your main class or as early as possible, call at least once:
143 | ```
144 | Async.init();
145 | ```
146 | Provided that your JVM has the capability enabled, this will start a runtime instrumentation agent.
147 | If you forget to invoke this function, the first call to `await` will initialize the system (and print a warning).
148 |
149 | This is a solution for testing and development, it has the least amount of configuration.
150 | It might interfere with JVM debugging. This alternative is present as a fallback.
151 |
152 | #### Option 3 - Run instrumentation tool
153 |
154 | The ea-async-1.2.3.jar is a runnable jar that can pre-instrument your files.
155 |
156 | Usage:
157 |
158 | ```bash
159 | java -cp YOUR_PROJECT_CLASSPATH -jar ea-async-1.2.3.jar classDirectory
160 | ```
161 |
162 | Example:
163 |
164 | ```bash
165 | java -cp guava.jar;commons-lang.jar -jar ea-async-1.2.3.jar target/classes
166 | ```
167 |
168 | After that all the files in target/classes will have been instrumented.
169 | There will be no references to `Async.await` and `Async.init` left in those classes.
170 |
171 |
172 | #### Option 4 - Build time instrumentation, with Maven - Preferred
173 |
174 | Use the [ea-async-maven-plugin](maven-plugin). It will instrument your classes in compile time and
175 | remove all references to `Async.await` and `Async.init()`.
176 |
177 | With build time instrumentation your project users won't need to have EA Async in their classpath unless they also choose to use it.
178 | This means that EA Async does not need to be a transitive dependency .
179 |
180 | This is the best option for libraries and maven projects.
181 |
182 | ```xml
183 |
184 |
185 |
186 | com.ea.async
187 | ea-async-maven-plugin
188 | 1.2.3
189 |
190 |
191 |
192 | instrument
193 | instrument-test
194 |
195 |
196 |
197 |
198 |
199 |
200 | ```
201 |
202 |
--------------------------------------------------------------------------------
/async/src/test/java/com/ea/async/test/ArrayVarsTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async.test;
30 |
31 | import com.ea.async.Task;
32 |
33 | import org.junit.Test;
34 |
35 | import static com.ea.async.Async.await;
36 | import static com.ea.async.Task.fromValue;
37 | import static junit.framework.Assert.assertEquals;
38 |
39 | public class ArrayVarsTest extends BaseTest
40 | {
41 | @Test
42 | public void arrayParams01()
43 | {
44 | class Experiment
45 | {
46 | Task doIt(Object params[])
47 | {
48 | await(Task.done());
49 | return fromValue(params[0]);
50 | }
51 | }
52 | assertEquals("x", new Experiment().doIt(new Object[]{"x"}).join());
53 | }
54 |
55 | @Test
56 | public void arrayParams02()
57 | {
58 | class Experiment
59 | {
60 | Task doIt(Object params[][])
61 | {
62 | await(Task.done());
63 | return fromValue(params[0][0]);
64 | }
65 | }
66 | assertEquals("x", new Experiment().doIt(new Object[][]{{"x"}}).join());
67 | }
68 |
69 | @Test
70 | public void arrayParams03()
71 | {
72 | class Experiment
73 | {
74 | Task doIt(Object params[][][])
75 | {
76 | await(Task.done());
77 | return fromValue(params[0][0][0]);
78 | }
79 | }
80 | assertEquals("x", new Experiment().doIt(new Object[][][]{{{"x"}}}).join());
81 | }
82 |
83 | @Test
84 | public void arrayParams04()
85 | {
86 | class Experiment
87 | {
88 | Task doIt(Object params[][][][])
89 | {
90 | await(Task.done());
91 | return fromValue(params[0][0][0][0]);
92 | }
93 | }
94 | assertEquals("x", new Experiment().doIt(new Object[][][][]{{{{"x"}}}}).join());
95 | }
96 |
97 |
98 | @Test
99 | public void optionalParam()
100 | {
101 | class Experiment
102 | {
103 | Task doIt(Object... params)
104 | {
105 | await(Task.done());
106 | return fromValue(params[0]);
107 | }
108 | }
109 | assertEquals("x", new Experiment().doIt(new Object[]{"x"}).join());
110 | }
111 |
112 |
113 | @Test
114 | public void stringArrayParams01()
115 | {
116 | class Experiment
117 | {
118 | Task doIt(String params[])
119 | {
120 | await(Task.done());
121 | return fromValue(params[0]);
122 | }
123 | }
124 | assertEquals("x", new Experiment().doIt(new String[]{"x"}).join());
125 | }
126 |
127 | @Test
128 | public void arrayVar01()
129 | {
130 | class Experiment
131 | {
132 | Task doIt(String x)
133 | {
134 | Object arr[] = new Object[]{x};
135 | await(Task.done());
136 | return fromValue(arr[0]);
137 | }
138 | }
139 | assertEquals("x", new Experiment().doIt("x").join());
140 | }
141 |
142 |
143 | @Test
144 | public void stringVar01()
145 | {
146 | class Experiment
147 | {
148 | Task doIt(String x)
149 | {
150 | String arr[] = new String[]{x};
151 | await(Task.done());
152 | return fromValue(arr[0]);
153 | }
154 | }
155 | assertEquals("x", new Experiment().doIt("x").join());
156 | }
157 |
158 | @Test
159 | public void primitiveArray01()
160 | {
161 | class Experiment
162 | {
163 | Task doIt(int x)
164 | {
165 | int arr[] = new int[]{x};
166 | await(Task.done());
167 | return fromValue(arr[0]);
168 | }
169 | }
170 | assertEquals(10, new Experiment().doIt(10).join());
171 | }
172 |
173 | @Test
174 | public void arraysWithAwait01()
175 | {
176 | class Experiment
177 | {
178 | Task doIt(Object params[])
179 | {
180 | await(getBlockedFuture());
181 | return fromValue(params[0]);
182 | }
183 | }
184 | final Task task = new Experiment().doIt(new Object[]{"x"});
185 | completeFutures();
186 | assertEquals("x", task.join());
187 | }
188 |
189 | @Test
190 | public void arraysWithAwait02()
191 | {
192 | class Experiment
193 | {
194 | Task doIt(Object params[])
195 | {
196 | Object arr[][] = new Object[][]{params};
197 | await(getBlockedFuture());
198 | return fromValue(arr[0][0]);
199 | }
200 | }
201 | final Task task = new Experiment().doIt(new Object[]{"x"});
202 | completeFutures();
203 | assertEquals("x", task.join());
204 | }
205 |
206 |
207 | @Test
208 | public void arraysWithAwait03()
209 | {
210 | class Experiment
211 | {
212 | Task doIt(Object params[])
213 | {
214 | Object arr[][];
215 | if (params != null)
216 | {
217 | arr = new Object[][]{params};
218 | await(getBlockedFuture());
219 | }
220 | else
221 | {
222 | arr = new Object[][]{params, null};
223 | await(getBlockedFuture());
224 | }
225 | return fromValue(arr[0][0]);
226 | }
227 | }
228 | final Task task = new Experiment().doIt(new Object[]{"x"});
229 | completeFutures();
230 | assertEquals("x", task.join());
231 | }
232 |
233 | @Test
234 | public void arraysAndIfs()
235 | {
236 | class Experiment
237 | {
238 | Task doIt(int x)
239 | {
240 | Object[][] arr = new Object[][]{{x}};
241 | if (x == 11)
242 | {
243 | arr = null;
244 | }
245 | // this forces a stack frame map to be created
246 | else
247 | {
248 |
249 | await(getBlockedFuture());
250 | }
251 | return fromValue(arr[0][0]);
252 | }
253 | }
254 | final Task task = new Experiment().doIt(10);
255 | completeFutures();
256 | assertEquals(10, task.join());
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/maven-plugin/src/main/java/com/ea/async/maven/plugin/AbstractAsyncMojo.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of
14 | its contributors may be used to endorse or promote products derived
15 | from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | package com.ea.async.maven.plugin;
30 |
31 | import com.ea.async.instrumentation.Transformer;
32 |
33 | import org.apache.maven.plugin.AbstractMojo;
34 | import org.apache.maven.plugin.MojoExecutionException;
35 | import org.apache.maven.plugins.annotations.Component;
36 | import org.apache.maven.plugins.annotations.Parameter;
37 | import org.apache.maven.project.MavenProject;
38 | import org.codehaus.plexus.archiver.ArchiverException;
39 | import org.codehaus.plexus.components.io.resources.PlexusIoFileResourceCollection;
40 | import org.codehaus.plexus.components.io.resources.PlexusIoResource;
41 | import org.codehaus.plexus.util.IOUtil;
42 |
43 | import java.io.File;
44 | import java.io.FileOutputStream;
45 | import java.io.IOException;
46 | import java.io.InputStream;
47 | import java.net.URLClassLoader;
48 | import java.util.Iterator;
49 |
50 |
51 | public abstract class AbstractAsyncMojo extends AbstractMojo
52 | {
53 |
54 | private static final String[] DEFAULT_EXCLUDES = new String[]{ "**/package.html" };
55 |
56 | private static final String[] DEFAULT_INCLUDES = new String[]{ "**/**" };
57 |
58 | /**
59 | * List of files to include.
60 | * Fileset patterns relative to the input directory whose contents
61 | * is being instrumented.
62 | */
63 | @Parameter
64 | private String[] includes;
65 |
66 | /**
67 | * List of files to include.
68 | * Fileset patterns relative to the input directory whose contents
69 | * is being instrumented.
70 | */
71 | @Parameter
72 | private String[] excludes;
73 |
74 | /**
75 | * Directory where the instrumented files will be written.
76 | * If not set the files will be overwritten with the new data.
77 | */
78 | @Parameter(required = false)
79 | private File outputDirectory;
80 |
81 | @Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
82 | protected File classesDirectory;
83 |
84 | /**
85 | * Prints each file being instrumented
86 | */
87 | @Parameter
88 | protected boolean verbose = false;
89 |
90 | @Component
91 | protected MavenProject project;
92 |
93 |
94 | /**
95 | * Return the specific output directory to instrument.
96 | */
97 | protected abstract File getClassesDirectory();
98 |
99 | protected abstract String getType();
100 |
101 | /**
102 | * Instruments the files.
103 | */
104 | public void execute() throws MojoExecutionException
105 | {
106 | if (getClassesDirectory() == null || (!getClassesDirectory().exists() || getClassesDirectory().list().length < 1))
107 | {
108 | getLog().info("Skipping instrumentation of the " + getType());
109 | }
110 | else
111 | {
112 | instrumentFiles();
113 | }
114 | }
115 |
116 | private String[] getIncludes()
117 | {
118 | if (includes != null && includes.length > 0)
119 | {
120 | return includes;
121 | }
122 | return DEFAULT_INCLUDES;
123 | }
124 |
125 | private String[] getExcludes()
126 | {
127 | if (excludes != null && excludes.length > 0)
128 | {
129 | return excludes;
130 | }
131 | return DEFAULT_EXCLUDES;
132 | }
133 |
134 | private File getOutputDirectory()
135 | {
136 | if (outputDirectory == null)
137 | {
138 | return getClassesDirectory();
139 | }
140 | return outputDirectory;
141 | }
142 |
143 | public boolean isVerbose()
144 | {
145 | return verbose;
146 | }
147 |
148 | /**
149 | * Instrument the files that require instrumentation.
150 | */
151 | public void instrumentFiles() throws MojoExecutionException
152 | {
153 | try
154 | {
155 | File contentDirectory = getClassesDirectory();
156 | if (!contentDirectory.exists())
157 | {
158 | getLog().warn(getType() + " directory is empty! " + contentDirectory);
159 | return;
160 | }
161 | final Iterator it = getFiles(contentDirectory);
162 | final Transformer transformer = new Transformer();
163 | transformer.setErrorListener(error -> getLog().error(error));
164 | int instrumentedCount = 0;
165 | ClassLoader classLoader = createClassLoader();
166 | while (it.hasNext())
167 | {
168 | final PlexusIoResource resource = it.next();
169 | if (resource.isFile() && resource.getName().endsWith(".class"))
170 | {
171 | byte[] bytes = null;
172 | try (InputStream in = resource.getContents())
173 | {
174 | bytes = transformer.instrument(classLoader, in);
175 | }
176 | catch (Exception e)
177 | {
178 | getLog().error("Error instrumenting " + resource.getName(), e);
179 | }
180 | if (bytes != null)
181 | {
182 | if (isVerbose())
183 | {
184 | getLog().info("instrumented: " + resource.getName());
185 | }
186 | else if (getLog().isDebugEnabled())
187 | {
188 | getLog().debug("instrumented: " + resource.getName());
189 | }
190 | IOUtil.copy(bytes, new FileOutputStream(new File(getOutputDirectory(), resource.getName())));
191 | instrumentedCount++;
192 | }
193 | }
194 | }
195 | getLog().info("Orbit Async " + getType() + " instrumented: " + instrumentedCount);
196 | }
197 | catch (Exception e)
198 | {
199 | throw new MojoExecutionException("Error assembling instrumenting", e);
200 | }
201 | }
202 |
203 |
204 | private Iterator getFiles(final File contentDirectory) throws IOException
205 | {
206 | if (!contentDirectory.isDirectory())
207 | {
208 | throw new ArchiverException(contentDirectory.getAbsolutePath() + " is not a directory.");
209 | }
210 |
211 | final PlexusIoFileResourceCollection collection = new PlexusIoFileResourceCollection();
212 |
213 | collection.setIncludes(getIncludes());
214 | collection.setExcludes(getExcludes());
215 | collection.setBaseDir(contentDirectory);
216 | collection.setIncludingEmptyDirectories(false);
217 | collection.setPrefix("");
218 | collection.setUsingDefaultExcludes(true);
219 |
220 | return collection.getResources();
221 | }
222 |
223 | abstract ClassLoader createClassLoader();
224 | }
225 |
--------------------------------------------------------------------------------