2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.listeners;
6 |
7 | /**
8 | * Gets notified about uncaught exceptions thrown by actors.
9 | *
10 | * @see PrintStreamFailureLogger
11 | * @see CrashEarlyFailureHandler
12 | */
13 | public interface FailureHandler {
14 |
15 | /**
16 | * Should log the exception and possibly do some error recovery.
17 | *
18 | * May stop the actor thread by interrupting the current thread. Otherwise the actor thread (and all actors in it)
19 | * will keep on processing messages.
20 | *
21 | * Should not throw any exceptions - that would result in implementation specific behaviour.
22 | */
23 | void uncaughtException(Object actor, Object message, Throwable exception);
24 | }
25 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/queue/MessageQueue.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.queue;
6 |
7 | import javax.annotation.Nullable;
8 | import javax.annotation.concurrent.ThreadSafe;
9 | import java.util.concurrent.*;
10 |
11 | /**
12 | * Asynchronous unbounded queue for message passing.
13 | */
14 | @ThreadSafe
15 | public class MessageQueue implements MessageSender, MessageReceiver {
16 |
17 | private final BlockingQueue queue = new LinkedBlockingQueue<>();
18 |
19 | @Override
20 | public void send(T message) {
21 | queue.add(message);
22 | }
23 |
24 | @Override
25 | public T take() throws InterruptedException {
26 | return queue.take();
27 | }
28 |
29 | @Nullable
30 | @Override
31 | public T poll() {
32 | return queue.poll();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/test/java/fi/jumi/actors/generator/reference/dummyListener/DummyListenerToEvent.java:
--------------------------------------------------------------------------------
1 | package fi.jumi.actors.generator.reference.dummyListener;
2 |
3 | import fi.jumi.actors.eventizers.Event;
4 | import fi.jumi.actors.generator.DummyListener;
5 | import fi.jumi.actors.queue.MessageSender;
6 | import javax.annotation.Generated;
7 |
8 | @Generated(value = "fi.jumi.actors.generator.EventStubGenerator",
9 | comments = "Based on fi.jumi.actors.generator.DummyListener",
10 | date = "2000-12-31")
11 | public class DummyListenerToEvent implements DummyListener {
12 |
13 | private final MessageSender> target;
14 |
15 | public DummyListenerToEvent(MessageSender> target) {
16 | this.target = target;
17 | }
18 |
19 | @Override
20 | public void onSomething(String foo, String bar) {
21 | target.send(new OnSomethingEvent(foo, bar));
22 | }
23 |
24 | @Override
25 | public void onOther() {
26 | target.send(new OnOtherEvent());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/listeners/PrintStreamFailureLogger.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.listeners;
6 |
7 | import javax.annotation.concurrent.Immutable;
8 | import java.io.PrintStream;
9 |
10 | /**
11 | * Prints all uncaught exceptions. Meant for production use (without a logging framework).
12 | */
13 | @Immutable
14 | public class PrintStreamFailureLogger implements FailureHandler {
15 |
16 | private final PrintStream out;
17 |
18 | public PrintStreamFailureLogger(PrintStream out) {
19 | this.out = out;
20 | }
21 |
22 | @Override
23 | public void uncaughtException(Object actor, Object message, Throwable exception) {
24 | synchronized (out) {
25 | out.println("uncaught exception from " + actor + " when processing " + message);
26 | exception.printStackTrace(out);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/main/java/fi/jumi/actors/generator/GenerateEventizer.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.generator;
6 |
7 | import java.lang.annotation.*;
8 |
9 | /**
10 | * Causes the compiler to generate {@link fi.jumi.actors.eventizers.Eventizer eventizer} classes for the interface which
11 | * is annotated with this annotation.
12 | */
13 | @Retention(RetentionPolicy.SOURCE)
14 | @Target(ElementType.TYPE)
15 | public @interface GenerateEventizer {
16 |
17 | /**
18 | * Behave as if this annotation was on the parent interface instead of this interface. Used for generating
19 | * eventizers for interfaces which are not under your control (i.e. part of the JDK or a 3rd party library).
20 | */
21 | boolean useParentInterface() default false;
22 |
23 | /**
24 | * Write eventizer classes to another package instead of the current package.
25 | */
26 | String targetPackage() default "";
27 | }
28 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/main/java/fi/jumi/actors/generator/codegen/JavaClasses.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.generator.codegen;
6 |
7 | import javax.lang.model.element.*;
8 | import java.util.*;
9 |
10 | public class JavaClasses {
11 |
12 | public static List getMethods(TypeElement clazz) {
13 | // TODO: use javax.lang.model.util.Elements.getAllMembers()
14 | ArrayList methods = new ArrayList<>();
15 | for (Element enclosedElement : clazz.getEnclosedElements()) {
16 | if (enclosedElement.getKind() == ElementKind.METHOD) {
17 | methods.add(new JavaMethod((ExecutableElement) enclosedElement));
18 | }
19 | }
20 | // TODO: get parent using javax.lang.model.util.Types.directSupertypes or javax.lang.model.util.Elements.getTypeElement()
21 | // TODO: get methods of parent interfaces
22 | return methods;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/thread-safety-agent/src/test/java/fi/jumi/threadsafetyagent/ThreadUtil.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.threadsafetyagent;
6 |
7 | import java.util.concurrent.*;
8 |
9 | public class ThreadUtil {
10 |
11 | public static void runInNewThread(String threadName, Runnable target) throws Throwable {
12 | FutureTask future = new FutureTask<>(target, null);
13 | new Thread(future, threadName).start();
14 | try {
15 | future.get();
16 | } catch (ExecutionException e) {
17 | throw e.getCause();
18 | }
19 | }
20 |
21 | public static Throwable getExceptionFromNewThread(String threadName, Runnable target) {
22 | try {
23 | runInNewThread(threadName, target);
24 | } catch (Throwable throwable) {
25 | return throwable;
26 | }
27 | throw new AssertionError("Expected to throw an exception but did not throw anything");
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/eventizers/dynamic/DynamicEventizerProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers.dynamic;
6 |
7 | import fi.jumi.actors.eventizers.*;
8 |
9 | import javax.annotation.concurrent.Immutable;
10 | import java.util.concurrent.ConcurrentHashMap;
11 |
12 | /**
13 | * Supports all actor interfaces using reflection.
14 | */
15 | @Immutable
16 | public class DynamicEventizerProvider implements EventizerProvider {
17 |
18 | private final ConcurrentHashMap, Eventizer>> cache = new ConcurrentHashMap<>();
19 |
20 | @SuppressWarnings("unchecked")
21 | @Override
22 | public Eventizer getEventizerForType(Class type) {
23 | Eventizer eventizer = (Eventizer) cache.get(type);
24 | if (eventizer == null) {
25 | eventizer = new DynamicEventizer<>(type);
26 | cache.put(type, eventizer);
27 | }
28 | return eventizer;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/test/java/fi/jumi/actors/generator/reference/DummyListenerEventizer.java:
--------------------------------------------------------------------------------
1 | package fi.jumi.actors.generator.reference;
2 |
3 | import fi.jumi.actors.eventizers.Event;
4 | import fi.jumi.actors.eventizers.Eventizer;
5 | import fi.jumi.actors.generator.DummyListener;
6 | import fi.jumi.actors.generator.reference.dummyListener.*;
7 | import fi.jumi.actors.queue.MessageSender;
8 | import javax.annotation.Generated;
9 |
10 | @Generated(value = "fi.jumi.actors.generator.EventStubGenerator",
11 | comments = "Based on fi.jumi.actors.generator.DummyListener",
12 | date = "2000-12-31")
13 | public class DummyListenerEventizer implements Eventizer {
14 |
15 | @Override
16 | public Class getType() {
17 | return DummyListener.class;
18 | }
19 |
20 | @Override
21 | public DummyListener newFrontend(MessageSender> target) {
22 | return new DummyListenerToEvent(target);
23 | }
24 |
25 | @Override
26 | public MessageSender> newBackend(DummyListener target) {
27 | return new EventToDummyListener(target);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/queue/MessageQueueTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.queue;
6 |
7 | import org.junit.*;
8 |
9 | import static org.hamcrest.MatcherAssert.assertThat;
10 | import static org.hamcrest.Matchers.is;
11 |
12 | public class MessageQueueTest {
13 |
14 | private final MessageQueue messageQueue = new MessageQueue<>();
15 |
16 | @After
17 | public void clearThreadInterruptedStatus() {
18 | Thread.interrupted();
19 | }
20 |
21 | @Test
22 | public void send_does_not_change_the_interrupt_status_of_the_current_thread() {
23 | Thread.currentThread().interrupt();
24 |
25 | messageQueue.send("any message");
26 |
27 | assertThat("interrupt status after send", Thread.currentThread().isInterrupted(), is(true));
28 | }
29 |
30 | @Test
31 | public void send_enqueues_even_when_interrupted() {
32 | Thread.currentThread().interrupt();
33 |
34 | messageQueue.send("the message");
35 |
36 | assertThat(messageQueue.poll(), is("the message"));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/test/java/fi/jumi/actors/generator/reference/dummyListener/OnSomethingEvent.java:
--------------------------------------------------------------------------------
1 | package fi.jumi.actors.generator.reference.dummyListener;
2 |
3 | import fi.jumi.actors.eventizers.Event;
4 | import fi.jumi.actors.eventizers.EventToString;
5 | import fi.jumi.actors.generator.DummyListener;
6 | import java.io.Serializable;
7 | import javax.annotation.Generated;
8 |
9 | @Generated(value = "fi.jumi.actors.generator.EventStubGenerator",
10 | comments = "Based on fi.jumi.actors.generator.DummyListener",
11 | date = "2000-12-31")
12 | public class OnSomethingEvent implements Event, Serializable {
13 |
14 | private final String foo;
15 | private final String bar;
16 |
17 | public OnSomethingEvent(String foo, String bar) {
18 | this.foo = foo;
19 | this.bar = bar;
20 | }
21 |
22 | public String getFoo() {
23 | return foo;
24 | }
25 |
26 | public String getBar() {
27 | return bar;
28 | }
29 |
30 | @Override
31 | public void fireOn(DummyListener target) {
32 | target.onSomething(foo, bar);
33 | }
34 |
35 | @Override
36 | public String toString() {
37 | return EventToString.format("DummyListener", "onSomething", foo, bar);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/RELEASE-NOTES.md:
--------------------------------------------------------------------------------
1 |
2 | Release Notes
3 | =============
4 |
5 | ### Upcoming Changes
6 |
7 | - Removed `jumi-actors-maven-plugin` in favor of `jumi-actors-generator`
8 | - Easy access to an actor's own `ActorThread` using `Actors.currentThread()`
9 |
10 | ### Jumi Actors 1.0.277 (2015-09-06)
11 |
12 | - Fixed generating imports for nested generics
13 |
14 | ### Jumi Actors 1.0.270 (2015-08-28)
15 |
16 | - Jumi Actors is from this release onwards its own project with its own release cycle
17 | - Maven groupId was changed from `fi.jumi` to `fi.jumi.actors`
18 | - Upgraded to ASM 5, making the thread-safety-agent Java 8 compatible
19 | - New annotation processor based code generator. Includes the following enhancements:
20 | - Getters for event classes
21 | - Cleaner generated code
22 |
23 | ### Jumi Actors 0.1.196 (2012-09-19)
24 |
25 | - Improved logging of events with string parameters; special characters are now escaped
26 | - Made configurable the language level for the Java compiler used by the Jumi Actors Maven plugin. Enables the use of event interfaces which depend on Java 7+ language features
27 |
28 | ### Jumi Actors 0.1.64 (2012-07-10)
29 |
30 | - Javadocs for the public APIs of Jumi Actors
31 | - Fixed a concurrency bug in `WorkerCounter`
32 |
33 | ### Jumi Actors 0.1.46 (2012-07-07)
34 |
35 | - Initial release of Jumi Actors
36 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | fi.jumi.actors
8 | parent
9 | 1.1-SNAPSHOT
10 | parent/pom.xml
11 |
12 |
13 | project
14 | pom
15 |
16 | Jumi Actors
17 |
18 |
19 | jumi-actors
20 | jumi-actors-generator
21 | thread-safety-agent
22 | end-to-end-tests
23 | parent
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | maven-deploy-plugin
33 |
34 | true
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/eventizers/ComposedEventizerProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers;
6 |
7 | import javax.annotation.concurrent.Immutable;
8 | import java.util.*;
9 |
10 | /**
11 | * To be used with generated or hand-written {@link Eventizer}s.
12 | */
13 | @Immutable
14 | public class ComposedEventizerProvider implements EventizerProvider {
15 |
16 | private final Map, Eventizer>> eventizers;
17 |
18 | public ComposedEventizerProvider(Eventizer>... eventizers) {
19 | HashMap, Eventizer>> map = new HashMap<>();
20 | for (Eventizer> eventizer : eventizers) {
21 | map.put(eventizer.getType(), eventizer);
22 | }
23 | this.eventizers = Collections.unmodifiableMap(map);
24 | }
25 |
26 | @SuppressWarnings({"unchecked"})
27 | @Override
28 | public Eventizer getEventizerForType(Class type) {
29 | Eventizer> eventizer = eventizers.get(type);
30 | if (eventizer == null) {
31 | throw new IllegalArgumentException("unsupported type: " + type);
32 | }
33 | return (Eventizer) eventizer;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/eventizers/dynamic/DynamicEventizerProviderTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers.dynamic;
6 |
7 | import fi.jumi.actors.eventizers.Eventizer;
8 | import org.junit.Test;
9 |
10 | import static org.fest.assertions.Assertions.assertThat;
11 |
12 | public class DynamicEventizerProviderTest {
13 |
14 | @Test
15 | public void returns_an_eventizer_for_any_listener_interface() {
16 | DynamicEventizerProvider provider = new DynamicEventizerProvider();
17 |
18 | Eventizer eventizer = provider.getEventizerForType(DummyListener.class);
19 |
20 | assertThat(eventizer.getType()).isEqualTo(DummyListener.class);
21 | }
22 |
23 | @Test
24 | public void caches_the_eventizer_instances() {
25 | DynamicEventizerProvider provider = new DynamicEventizerProvider();
26 |
27 | Eventizer eventizer1 = provider.getEventizerForType(DummyListener.class);
28 | Eventizer eventizer2 = provider.getEventizerForType(DummyListener.class);
29 |
30 | assertThat(eventizer1).isSameAs(eventizer2);
31 | }
32 |
33 |
34 | private interface DummyListener {
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/eventizers/dynamic/DynamicEventizer.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers.dynamic;
6 |
7 | import fi.jumi.actors.eventizers.*;
8 | import fi.jumi.actors.queue.MessageSender;
9 |
10 | import javax.annotation.concurrent.Immutable;
11 | import java.lang.reflect.Proxy;
12 |
13 | /**
14 | * Supports any actor interface using reflection.
15 | */
16 | @Immutable
17 | public class DynamicEventizer implements Eventizer {
18 |
19 | private final Class type;
20 |
21 | public DynamicEventizer(Class type) {
22 | Eventizers.validateActorInterface(type);
23 | this.type = type;
24 | }
25 |
26 | @Override
27 | public Class getType() {
28 | return type;
29 | }
30 |
31 | @Override
32 | public T newFrontend(MessageSender> target) {
33 | return type.cast(Proxy.newProxyInstance(
34 | type.getClassLoader(),
35 | new Class>[]{type},
36 | new DynamicListenerToEvent<>(target))
37 | );
38 | }
39 |
40 | @Override
41 | public MessageSender> newBackend(T target) {
42 | return new EventToDynamicListener<>(target);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/listeners/PrintStreamFailureLoggerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.listeners;
6 |
7 | import fi.jumi.actors.DummyException;
8 | import org.junit.Test;
9 |
10 | import java.io.*;
11 |
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 | import static org.hamcrest.Matchers.containsString;
14 |
15 | public class PrintStreamFailureLoggerTest {
16 |
17 | private final ByteArrayOutputStream output = new ByteArrayOutputStream();
18 | private final PrintStreamFailureLogger failureHandler = new PrintStreamFailureLogger(new PrintStream(output));
19 |
20 | @Test
21 | public void logs_uncaught_exceptions() {
22 | failureHandler.uncaughtException("the actor", "the message", new DummyException());
23 |
24 | String output = this.output.toString();
25 | assertThat(output, containsString("uncaught exception"));
26 | assertThat(output, containsString("the actor"));
27 | assertThat(output, containsString("the message"));
28 | assertThat(output, containsString(DummyException.class.getName()));
29 | assertThat("should contain the stack trace", output, containsString("at " + getClass().getName()));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/benchmarks/RingStart.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.benchmarks;
6 |
7 | import fi.jumi.actors.*;
8 |
9 | public class RingStart extends RingNode {
10 |
11 | public RingStart(ActorThread actorThread) {
12 | super(actorThread);
13 | }
14 |
15 | @Override
16 | public void forward(int roundTrips) {
17 | if (roundTrips > 0) {
18 | roundTrips--;
19 | super.forward(roundTrips);
20 | }
21 | }
22 | }
23 |
24 | class RingNode implements Ring {
25 |
26 | private final ActorThread actorThread;
27 | private ActorRef next;
28 |
29 | public RingNode(ActorThread actorThread) {
30 | this.actorThread = actorThread;
31 | }
32 |
33 | @Override
34 | public void build(int ringSize, ActorRef first) {
35 | ringSize--;
36 | if (ringSize > 0) {
37 | next = actorThread.bindActor(Ring.class, new RingNode(actorThread));
38 | next.tell().build(ringSize, first);
39 | } else {
40 | next = first;
41 | }
42 | }
43 |
44 | @Override
45 | public void forward(int roundTrips) {
46 | next.tell().forward(roundTrips);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/end-to-end-tests/src/test/java/fi/jumi/test/TestEnvironment.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2014, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.test;
6 |
7 | import fi.luontola.buildtest.*;
8 |
9 | import java.io.File;
10 | import java.util.Properties;
11 |
12 | public class TestEnvironment {
13 |
14 | public static final VersionNumbering VERSION_NUMBERING = new VersionNumbering();
15 | public static final ProjectArtifacts ARTIFACTS;
16 |
17 | static {
18 | Properties p = ResourcesUtil.getProperties("testing.properties");
19 | ARTIFACTS = new ProjectArtifacts(getDirectory(p, "test.projectArtifactsDir"), VERSION_NUMBERING);
20 | }
21 |
22 | private static File getDirectory(Properties properties, String key) {
23 | File file = new File(filteredProperty(properties, key));
24 | if (!file.isDirectory()) {
25 | throw new IllegalArgumentException("not a directory: " + file);
26 | }
27 | return file;
28 | }
29 |
30 | private static String filteredProperty(Properties properties, String key) {
31 | String value = properties.getProperty(key);
32 | if (value.startsWith("${")) {
33 | throw new IllegalStateException("the property '" + key + "' was not filled in: " + value);
34 | }
35 | return value;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/parent/parent.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/test/java/fi/jumi/actors/generator/ast/LibrarySourceLocatorTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.generator.ast;
6 |
7 | import org.junit.Test;
8 |
9 | import static org.hamcrest.MatcherAssert.assertThat;
10 | import static org.hamcrest.Matchers.*;
11 |
12 | public class LibrarySourceLocatorTest {
13 |
14 | private final LibrarySourceLocator locator = new LibrarySourceLocator();
15 |
16 | @Test
17 | public void finds_sources_from_JDK_src_zip() {
18 | String sources = locator.findSources(java.lang.String.class.getName());
19 | assertThat(sources, containsString("class String"));
20 | }
21 |
22 | @Test
23 | public void cannot_find_sources_of_unpublished_JDK_classes() {
24 | String sources = locator.findSources(sun.misc.Unsafe.class.getName());
25 | assertThat(sources, is(nullValue()));
26 | }
27 |
28 | @Test
29 | public void finds_sources_from_Maven_sources_jar() {
30 | String sources = locator.findSources(com.google.common.io.ByteStreams.class.getName());
31 | assertThat(sources, containsString("class ByteStreams"));
32 | }
33 |
34 | @Test
35 | public void finds_sources_from_classpath() {
36 | String sources = locator.findSources(org.mockito.asm.Opcodes.class.getName());
37 | assertThat(sources, containsString("interface Opcodes"));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/project.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/thread-safety-agent/src/main/java/fi/jumi/threadsafetyagent/util/AbstractTransformationChain.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2014, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.threadsafetyagent.util;
6 |
7 | import org.objectweb.asm.*;
8 |
9 | import java.lang.instrument.*;
10 | import java.security.ProtectionDomain;
11 |
12 | public abstract class AbstractTransformationChain implements ClassFileTransformer {
13 |
14 | @Override
15 | public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined,
16 | ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
17 | // TODO: at least the ClassLoader could be passed to the adapters, so they could examine super classes, package annotations etc.
18 | ClassReader cr = new ClassReader(classfileBuffer);
19 | ClassWriter cw;
20 | if (enableAdditiveTransformationOptimization()) {
21 | cw = new ClassWriter(cr, 0);
22 | } else {
23 | cw = new ClassWriter(0);
24 | }
25 | try {
26 | ClassVisitor cv = getAdapters(cw);
27 | cr.accept(cv, 0);
28 | return cw.toByteArray();
29 | } catch (DoNotTransformException e) {
30 | return null;
31 | }
32 | }
33 |
34 | protected boolean enableAdditiveTransformationOptimization() {
35 | return true;
36 | }
37 |
38 | protected abstract ClassVisitor getAdapters(ClassVisitor cv);
39 | }
40 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/End_to_end_tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/thread-safety-agent/src/test/java/fi/jumi/threadsafetyagent/util/ClassNameMatcherTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.threadsafetyagent.util;
6 |
7 | import org.junit.Test;
8 |
9 | import static org.junit.Assert.*;
10 |
11 | public class ClassNameMatcherTest {
12 |
13 | @Test
14 | public void matching_a_single_class() {
15 | ClassNameMatcher matcher = new ClassNameMatcher("x.Foo");
16 |
17 | assertTrue("that class", matcher.matches("x.Foo"));
18 | assertFalse("other class", matcher.matches("x.Bar"));
19 | }
20 |
21 | @Test
22 | public void matching_all_classes_in_a_package() {
23 | ClassNameMatcher matcher = new ClassNameMatcher("x.*");
24 |
25 | assertTrue("that package", matcher.matches("x.Foo"));
26 | assertFalse("subpackage", matcher.matches("x.y.Foo"));
27 | assertFalse("other package", matcher.matches("y.Foo"));
28 |
29 | assertFalse("Corner case: package with the same prefix", matcher.matches("xx.Foo"));
30 | }
31 |
32 | @Test
33 | public void matching_all_classes_in_subpackages() {
34 | ClassNameMatcher matcher = new ClassNameMatcher("x.**");
35 |
36 | assertTrue("that package", matcher.matches("x.Foo"));
37 | assertTrue("subpackage", matcher.matches("x.y.Foo"));
38 | assertFalse("other package", matcher.matches("y.Foo"));
39 |
40 | assertFalse("Corner case: package with the same prefix", matcher.matches("xx.Foo"));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/ActorRef.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | import javax.annotation.concurrent.ThreadSafe;
8 |
9 | /**
10 | * Handle for communicating with an actor.
11 | */
12 | @ThreadSafe
13 | public class ActorRef {
14 |
15 | private final T proxy;
16 |
17 | /**
18 | * Can be used to wrap test doubles into {@code ActorRef}s for unit testing purposes.
19 | *
20 | * Warning: Never use this method in production code! This method is meant to be used
21 | * only by the {@link Actors} class.
22 | */
23 | public static ActorRef wrap(T proxy) {
24 | return new ActorRef<>(proxy);
25 | }
26 |
27 | private ActorRef(T proxy) {
28 | this.proxy = proxy;
29 | }
30 |
31 | /**
32 | * Used for sending asynchronous messages to an actor. The recommended usage pattern is {@code
33 | * actorRef.tell().theMessage(theParameters)}
34 | *
35 | * To avoid confusion, the proxy returned from this method should never be stored in a variable or passed as a
36 | * parameter to a method. Otherwise it can be hard to know that when you are holding the real actor object and when
37 | * a proxy to it. Though that may sometimes be warranted when interacting with actor-unaware code or if you wish to
38 | * avoid the dependency to {@code ActorRef}.
39 | */
40 | public T tell() {
41 | return proxy;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/UncaughtExceptionCollector.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | import fi.jumi.actors.listeners.FailureHandler;
8 |
9 | import javax.annotation.concurrent.ThreadSafe;
10 | import java.util.List;
11 | import java.util.concurrent.CopyOnWriteArrayList;
12 |
13 | @ThreadSafe
14 | public class UncaughtExceptionCollector implements Thread.UncaughtExceptionHandler, FailureHandler {
15 |
16 | private final List uncaughtExceptions = new CopyOnWriteArrayList<>();
17 |
18 | @Override
19 | public void uncaughtException(Thread t, Throwable e) {
20 | uncaughtExceptions.add(e);
21 | }
22 |
23 | @Override
24 | public void uncaughtException(Object actor, Object message, Throwable exception) {
25 | uncaughtExceptions.add(exception);
26 | }
27 |
28 | public RuntimeException throwDummyException() {
29 | throw new DummyException();
30 | }
31 |
32 | public void failIfNotEmpty() {
33 | for (Throwable uncaughtException : uncaughtExceptions) {
34 | if (!isDummyException(uncaughtException)) {
35 | throw (AssertionError)
36 | new AssertionError("there were exceptions in a background thread")
37 | .initCause(uncaughtException);
38 | }
39 | }
40 | }
41 |
42 | private static boolean isDummyException(Throwable t) {
43 | return t instanceof DummyException;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/EventSpy.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | import java.util.*;
8 | import java.util.concurrent.ConcurrentLinkedQueue;
9 |
10 | import static org.hamcrest.MatcherAssert.assertThat;
11 | import static org.hamcrest.Matchers.is;
12 |
13 | public class EventSpy {
14 |
15 | private final Queue events = new ConcurrentLinkedQueue<>();
16 |
17 | public void log(String event) {
18 | events.add(event);
19 | }
20 |
21 | public void await(int expectedCount, long timeout) {
22 | long limit = System.currentTimeMillis() + timeout;
23 | while (events.size() < expectedCount) {
24 | if (System.currentTimeMillis() > limit) {
25 | throw new AssertionError("timed out; expected " + expectedCount + " or more events but got " + events);
26 | }
27 | Thread.yield();
28 | }
29 | }
30 |
31 | public void assertContains(String... expected) {
32 | List actual = new ArrayList<>(events);
33 | assertThat("events", actual, is(Arrays.asList(expected)));
34 | }
35 |
36 | public void expectNoMoreEvents() {
37 | int before = events.size();
38 | try {
39 | Thread.sleep(1);
40 | } catch (InterruptedException e) {
41 | Thread.currentThread().interrupt();
42 | }
43 | int after = events.size();
44 | assertThat("expected no more events, but still got some more: " + events, after, is(before));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/All_tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/eventizers/dynamic/DynamicListenerToEvent.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2018, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers.dynamic;
6 |
7 | import fi.jumi.actors.Promise;
8 | import fi.jumi.actors.eventizers.Event;
9 | import fi.jumi.actors.queue.MessageSender;
10 |
11 | import javax.annotation.concurrent.ThreadSafe;
12 | import java.lang.reflect.*;
13 | import java.util.concurrent.Future;
14 |
15 | @ThreadSafe
16 | public class DynamicListenerToEvent implements InvocationHandler {
17 |
18 | private final MessageSender> target;
19 |
20 | public DynamicListenerToEvent(MessageSender> target) {
21 | this.target = target;
22 | }
23 |
24 | @Override
25 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
26 | if (method.getDeclaringClass().equals(Object.class)) {
27 | return method.invoke(this, args);
28 | }
29 | // The declared return type must be assignable from Promise, because this handler will always
30 | // return Promise to the caller, even if the callee returns some other Future implementation.
31 | Class> returnType = method.getReturnType();
32 | if (returnType == Future.class || returnType == Promise.class) {
33 | Promise.Deferred deferred = Promise.defer();
34 | target.send(new DynamicEvent<>(method, args, deferred));
35 | return deferred.promise();
36 | } else {
37 | target.send(new DynamicEvent<>(method, args));
38 | return null;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/End_to_end_tests__no_rebuild_.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/End_to_end_tests__check_threads_.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/projectCodeStyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/main/java/fi/jumi/actors/generator/codegen/Imports.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.generator.codegen;
6 |
7 | import java.util.*;
8 |
9 | public class Imports {
10 |
11 | private final List classesToImport = new ArrayList<>();
12 | private final List packagesToImport = new ArrayList<>();
13 |
14 | public String getSimpleName(JavaType type) {
15 | addImports(type);
16 | return type.getGenericSimpleName();
17 | }
18 |
19 | public void addImports(JavaType... types) {
20 | for (JavaType type : types) {
21 | classesToImport.addAll(type.getClassImports());
22 | }
23 | }
24 |
25 | public void addPackageImport(String packageName) {
26 | packagesToImport.add(packageName);
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | SortedSet imports = new TreeSet<>();
32 | for (JavaType type : classesToImport) {
33 | if (isAlreadyInScope(type)) {
34 | continue;
35 | }
36 | // TODO: do not import classes from target package
37 | imports.add(type.getCanonicalName());
38 | }
39 | for (String packageName : packagesToImport) {
40 | imports.add(packageName + ".*");
41 | }
42 |
43 | StringBuilder sb = new StringBuilder();
44 | for (String anImport : imports) {
45 | sb.append("import " + anImport + ";\n");
46 | }
47 | sb.append("\n");
48 | return sb.toString();
49 | }
50 |
51 | private static boolean isAlreadyInScope(JavaType type) {
52 | return type.getPackage().equals("java.lang");
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/MultiThreadedActors.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | import fi.jumi.actors.eventizers.EventizerProvider;
8 | import fi.jumi.actors.listeners.*;
9 |
10 | import javax.annotation.concurrent.ThreadSafe;
11 | import java.util.concurrent.Executor;
12 |
13 |
14 | /**
15 | * Multi-threaded actors container for production use. Each {@link ActorThread} will be backed by a thread from the
16 | * {@link Executor} which is given to the constructor of this class.
17 | */
18 | @ThreadSafe
19 | public class MultiThreadedActors extends Actors {
20 |
21 | private final Executor executor;
22 |
23 | public MultiThreadedActors(Executor executor, EventizerProvider eventizerProvider, FailureHandler failureHandler, MessageListener messageListener) {
24 | super(eventizerProvider, failureHandler, messageListener);
25 | this.executor = executor;
26 | }
27 |
28 | @Override
29 | void startActorThread(MessageProcessor actorThread) {
30 | executor.execute(new BlockingActorProcessor(actorThread));
31 | }
32 |
33 |
34 | @ThreadSafe
35 | private static class BlockingActorProcessor implements Runnable {
36 | private final MessageProcessor actorThread;
37 |
38 | public BlockingActorProcessor(MessageProcessor actorThread) {
39 | this.actorThread = actorThread;
40 | }
41 |
42 | @Override
43 | public void run() {
44 | try {
45 | while (!Thread.interrupted()) {
46 | actorThread.processNextMessage();
47 | }
48 | } catch (InterruptedException e) {
49 | // actor was told to exit
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/MultiThreadedActorsTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | import fi.jumi.actors.eventizers.EventizerProvider;
8 | import fi.jumi.actors.listeners.*;
9 | import org.junit.*;
10 |
11 | import java.util.concurrent.*;
12 |
13 | import static org.hamcrest.MatcherAssert.assertThat;
14 | import static org.hamcrest.Matchers.*;
15 |
16 | public class MultiThreadedActorsTest extends ActorsContract {
17 |
18 | private final ExecutorService executor = Executors.newCachedThreadPool();
19 |
20 | @Override
21 | protected MultiThreadedActors newActors(EventizerProvider eventizerProvider, FailureHandler failureHandler, MessageListener messageListener) {
22 | return new MultiThreadedActors(executor, eventizerProvider, failureHandler, messageListener);
23 | }
24 |
25 | @Override
26 | protected void processEvents() {
27 | // noop; background threads run automatically, rely on the timeouts in the contract tests for waiting
28 | }
29 |
30 | @After
31 | public void stopExecutor() throws InterruptedException {
32 | executor.shutdownNow();
33 | }
34 |
35 |
36 | @Test
37 | public void actor_threads_are_backed_by_real_threads() throws InterruptedException {
38 | SpyDummyListener rawActor = new SpyDummyListener();
39 |
40 | ActorThread actorThread = actors.startActorThread();
41 | ActorRef actorRef = actorThread.bindActor(DummyListener.class, rawActor);
42 | actorRef.tell().onSomething("event");
43 | awaitEvents(1);
44 |
45 | assertThat(rawActor.thread, is(notNullValue()));
46 | assertThat(rawActor.thread, is(not(Thread.currentThread())));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/end-to-end-tests/src/test/java/fi/jumi/test/JumiActorsGeneratorTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.test;
6 |
7 | import fi.jumi.actors.eventizers.Event;
8 | import fi.jumi.actors.queue.*;
9 | import fi.jumi.test.guineaPig.OnFooEvent;
10 | import org.junit.*;
11 |
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 | import static org.hamcrest.Matchers.*;
14 | import static org.mockito.Mockito.*;
15 |
16 | public class JumiActorsGeneratorTest {
17 |
18 | @Test
19 | public void generates_eventizers_for_project_interfaces() {
20 | GuineaPigEventizer eventizer = new GuineaPigEventizer();
21 |
22 | assertThat("type", eventizer.getType(), is(equalTo(GuineaPig.class)));
23 | }
24 |
25 | @Test
26 | public void generates_eventizer_frontends() {
27 | GuineaPigEventizer eventizer = new GuineaPigEventizer();
28 | MessageQueue> queue = new MessageQueue<>();
29 |
30 | GuineaPig frontend = eventizer.newFrontend(queue);
31 | frontend.onFoo("foo");
32 |
33 | OnFooEvent message = (OnFooEvent) queue.poll();
34 | assertThat("message", message, is(notNullValue()));
35 | assertThat("message value", message.getFoo(), is("foo"));
36 | }
37 |
38 | @Test
39 | public void generates_eventizer_backends() {
40 | GuineaPigEventizer eventizer = new GuineaPigEventizer();
41 | GuineaPig target = mock(GuineaPig.class);
42 |
43 | MessageSender> backend = eventizer.newBackend(target);
44 | backend.send(new OnFooEvent("foo"));
45 |
46 | verify(target).onFoo("foo");
47 | }
48 |
49 | @Ignore // TODO
50 | @Test
51 | public void generates_eventizers_for_3rd_party_interfaces() {
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/jumi-actors-generator/jumi-actors-generator.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/eventizers/dynamic/DynamicEventizerBenchmark.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2018, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers.dynamic;
6 |
7 | import com.google.caliper.Benchmark;
8 | import com.google.caliper.api.VmOptions;
9 | import com.google.caliper.runner.CaliperMain;
10 | import fi.jumi.actors.eventizers.Eventizer;
11 |
12 | // XXX: workaround for https://stackoverflow.com/questions/29199509/caliper-error-cicompilercount-of-1-is-invalid-must-be-at-least-2
13 | @VmOptions("-XX:-TieredCompilation")
14 | public class DynamicEventizerBenchmark {
15 |
16 | private final DynamicEventizerProvider provider = new DynamicEventizerProvider();
17 |
18 | @Benchmark
19 | public int timeEventizerLookup(int reps) {
20 | int junk = 0;
21 | for (int i = 0; i < reps; i++) {
22 | Eventizer> eventizer = provider.getEventizerForType(ListenerWithLotsOfMethods.class);
23 | junk += eventizer.hashCode();
24 | }
25 | return junk;
26 | }
27 |
28 |
29 | public static void main(String[] args) {
30 | CaliperMain.main(DynamicEventizerBenchmark.class, new String[0]);
31 | }
32 |
33 | private interface ListenerWithLotsOfMethods {
34 |
35 | void event1();
36 |
37 | void event2();
38 |
39 | void event3();
40 |
41 | void event4();
42 |
43 | void event5();
44 |
45 | void event6();
46 |
47 | void event7();
48 |
49 | void event8();
50 |
51 | void event9();
52 |
53 | void event10();
54 |
55 | void event11();
56 |
57 | void event12();
58 |
59 | void event13();
60 |
61 | void event14();
62 |
63 | void event15();
64 |
65 | void event16();
66 |
67 | void event17();
68 |
69 | void event18();
70 |
71 | void event19();
72 |
73 | void event20();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/main/java/fi/jumi/actors/generator/codegen/JavaVar.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.generator.codegen;
6 |
7 | import com.google.common.base.Joiner;
8 |
9 | import javax.lang.model.element.VariableElement;
10 | import java.util.*;
11 |
12 | public class JavaVar {
13 |
14 | private final JavaType type;
15 | private final String name;
16 |
17 | public static JavaVar of(VariableElement var) {
18 | JavaType type = JavaType.of(var.asType());
19 | String name = var.getSimpleName().toString();
20 | return of(type, name);
21 | }
22 |
23 | public static JavaVar of(JavaType type, String name) {
24 | return new JavaVar(type, name);
25 | }
26 |
27 | private JavaVar(JavaType type, String name) {
28 | this.type = type;
29 | this.name = name;
30 | }
31 |
32 | public JavaType getType() {
33 | return type;
34 | }
35 |
36 | public String getName() {
37 | return name;
38 | }
39 |
40 | public static String toFormalArguments(List arguments, Imports imports) {
41 | List vars = new ArrayList<>();
42 | for (JavaVar var : arguments) {
43 | vars.add(imports.getSimpleName(var.getType()) + " " + var.getName());
44 | }
45 | return Joiner.on(", ").join(vars);
46 | }
47 |
48 | public static String toActualArguments(List arguments) {
49 | List vars = new ArrayList<>();
50 | for (JavaVar var : arguments) {
51 | vars.add(var.getName());
52 | }
53 | return Joiner.on(", ").join(vars);
54 | }
55 |
56 | public static String toActualVarargs(List arguments) {
57 | String args = toActualArguments(arguments);
58 | if (args.length() > 0) {
59 | return ", " + args;
60 | }
61 | return args;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/Matchers.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | import org.hamcrest.*;
8 |
9 | public class Matchers {
10 |
11 | public static Matcher containsLineWithWords(final String... expectedWords) {
12 | return new TypeSafeMatcher() {
13 | @Override
14 | protected boolean matchesSafely(String item) {
15 | for (String line : item.split("\n")) {
16 | if (lineContainsExpectedWords(line)) {
17 | return true;
18 | }
19 | }
20 | return false;
21 | }
22 |
23 | private boolean lineContainsExpectedWords(String line) {
24 | int pos = 0;
25 | for (String expectedWord : expectedWords) {
26 | pos = line.indexOf(expectedWord, pos);
27 | if (pos < 0) {
28 | return false;
29 | }
30 | pos++;
31 | }
32 | return true;
33 | }
34 |
35 | @Override
36 | public void describeTo(Description description) {
37 | description.appendText("contains line with words ").appendValueList("", ", ", "", expectedWords);
38 | }
39 | };
40 | }
41 |
42 | public static Matcher hasCause(final Matcher> causeMatcher) {
43 | return new TypeSafeMatcher() {
44 | @Override
45 | protected boolean matchesSafely(Throwable item) {
46 | return causeMatcher.matches(item.getCause());
47 | }
48 |
49 | @Override
50 | public void describeTo(Description description) {
51 | description.appendText("has cause ")
52 | .appendDescriptionOf(causeMatcher);
53 | }
54 | };
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | http://www.w3.org/1999/xhtml
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/thread-safety-agent/src/test/java/fi/jumi/threadsafetyagent/util/ClassNameMatcher.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.threadsafetyagent.util;
6 |
7 | import java.util.regex.Pattern;
8 |
9 | /**
10 | * Matches class names with a pattern syntax similar to Ant .
11 | *
12 | * foo.Bar - Single class foo.bar
13 | * foo.* - All classes in package foo
14 | * foo.** - All classes in package foo and its subpackages
15 | *
16 | */
17 | public class ClassNameMatcher {
18 |
19 | private static final String PACKAGE_REGEX = "[^\\.]*";
20 | private static final String SUBPACKAGE_REGEX = ".*";
21 |
22 | private final Pattern pattern;
23 |
24 | public ClassNameMatcher(String pattern) {
25 | this.pattern = Pattern.compile(toRegex(pattern));
26 | }
27 |
28 | private static String toRegex(String pattern) {
29 | String regex = "";
30 | for (int i = 0; i < pattern.length(); i++) {
31 | if (subpackagePatternAt(i, pattern)) {
32 | regex += SUBPACKAGE_REGEX;
33 | } else if (packagePatternAt(i, pattern)) {
34 | regex += PACKAGE_REGEX;
35 | } else {
36 | regex += quoteCharAt(i, pattern);
37 | }
38 | }
39 | return regex;
40 | }
41 |
42 | private static boolean subpackagePatternAt(int i, String pattern) {
43 | return packagePatternAt(i, pattern)
44 | && packagePatternAt(i + 1, pattern); // PIT: false warning about "Replaced integer addition with subtraction"
45 | }
46 |
47 | private static boolean packagePatternAt(int i, String pattern) {
48 | return i < pattern.length()
49 | && pattern.charAt(i) == '*';
50 | }
51 |
52 | private static String quoteCharAt(int i, String pattern) {
53 | return Pattern.quote("" + pattern.charAt(i));
54 | }
55 |
56 | public boolean matches(String className) {
57 | return pattern.matcher(className).matches();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/thread-safety-agent/thread-safety-agent.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/ActorThread.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | /**
8 | * Handle for creating and stopping actors.
9 | */
10 | public interface ActorThread {
11 |
12 | /**
13 | * Binds an actor to this {@code ActorThread} and returns an {@link ActorRef} for sending messages to it. The
14 | * recommended pattern for creating actors is {@code ActorRef theActor =
15 | * actorThread.bindActor(TheActorInterface.class, new TheActor(theParameters));} all in one line.
16 | *
17 | * Extra care should be taken to never pass around the raw actor instance. It should always be used through an
18 | * {@link ActorRef}. Use the convention that when passing an actor as a method/constructor parameter to another
19 | * class, the type of the parameter is {@code ActorRef}.
20 | *
21 | * All actors bound to the same {@code ActorThread} will be executed in the same {@link Thread}, so it is OK for
22 | * them to share some mutable state when it is known that all the actors are bound to the same thread. A common
23 | * pattern is to pass an actor its own {@code ActorThread}, so that it can create short-lived actors for callbacks,
24 | * or a reference to itself, when communicating with other actors.
25 | *
26 | * After nobody is holding the actor's {@link ActorRef}, that actor will be garbage collected. It is OK to create
27 | * lots of short-lived actors.
28 | */
29 | ActorRef bindActor(Class type, T rawActor);
30 |
31 | /**
32 | * Stops all actors which are bound to this {@code ActorThread} after all previously sent messages to them
33 | * have been processed. It is not possible to stop just one actor from an {@code ActorThread}, though due to garbage
34 | * collection that is usually not needed.
35 | *
36 | * An alternative way to stop actors is to call {@link Thread#interrupt()} on the thread where the actor is running,
37 | * which will stop that {@code ActorThread} immediately.
38 | */
39 | void stop();
40 | }
41 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/listeners/ContainsLineWithWordsMatcherTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.listeners;
6 |
7 | import org.junit.Test;
8 |
9 | import static fi.jumi.actors.Matchers.containsLineWithWords;
10 | import static org.hamcrest.MatcherAssert.assertThat;
11 |
12 | public class ContainsLineWithWordsMatcherTest {
13 |
14 | @Test
15 | public void passes_when_input_contains_only_expected_words() {
16 | assertThat("foo", containsLineWithWords("foo"));
17 | }
18 |
19 | @Test(expected = AssertionError.class)
20 | public void fails_when_input_does_not_contain_any_expected_words() {
21 | assertThat("foo", containsLineWithWords("bar"));
22 | }
23 |
24 | @Test
25 | public void passes_when_at_least_one_line_has_the_expected_words() {
26 | assertThat("before\nfoo\nafter", containsLineWithWords("foo"));
27 | }
28 |
29 | @Test
30 | public void passes_when_the_same_line_has_additional_words_around_the_expected_words() {
31 | assertThat("before foo after", containsLineWithWords("foo"));
32 | }
33 |
34 | @Test
35 | public void passes_when_the_same_line_has_additional_words_between_expected_words() {
36 | assertThat("foo middle bar", containsLineWithWords("foo", "bar"));
37 | }
38 |
39 | @Test(expected = AssertionError.class)
40 | public void fails_when_the_expected_words_are_in_wrong_order() {
41 | assertThat("bar middle foo", containsLineWithWords("foo", "bar"));
42 | }
43 |
44 | @Test(expected = AssertionError.class)
45 | public void fails_when_expected_word_is_not_repeated_enough_many_times() {
46 | assertThat("xx", containsLineWithWords("x", "x", "x"));
47 | }
48 |
49 | @Test(expected = AssertionError.class)
50 | public void fails_when_line_has_only_some_of_the_expected_words() {
51 | assertThat("foo", containsLineWithWords("foo", "bar"));
52 | }
53 |
54 | @Test(expected = AssertionError.class)
55 | public void fails_when_expected_words_are_on_different_lines() {
56 | assertThat("foo\nbar", containsLineWithWords("foo", "bar"));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/thread-safety-agent/src/main/java/fi/jumi/threadsafetyagent/EnabledWhenAnnotatedWith.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.threadsafetyagent;
6 |
7 | import fi.jumi.threadsafetyagent.util.DoNotTransformException;
8 | import org.objectweb.asm.*;
9 |
10 | import java.util.*;
11 |
12 | public class EnabledWhenAnnotatedWith extends ClassVisitor {
13 |
14 | private final List myAnnotationDescs = new ArrayList<>();
15 | private final String enablerAnnotationDesc;
16 |
17 | public EnabledWhenAnnotatedWith(String enablerAnnotation, ClassVisitor next) {
18 | super(Opcodes.ASM5, next);
19 | this.enablerAnnotationDesc = "L" + enablerAnnotation + ";";
20 | }
21 |
22 | @Override
23 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
24 | myAnnotationDescs.add(desc);
25 | return super.visitAnnotation(desc, visible);
26 | }
27 |
28 | @Override
29 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
30 | checkIsTransformationEnabled();
31 | return super.visitField(access, name, desc, signature, value);
32 | }
33 |
34 | @Override
35 | public void visitInnerClass(String name, String outerName, String innerName, int access) {
36 | checkIsTransformationEnabled();
37 | super.visitInnerClass(name, outerName, innerName, access);
38 | }
39 |
40 | @Override
41 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
42 | checkIsTransformationEnabled();
43 | return super.visitMethod(access, name, desc, signature, exceptions);
44 | }
45 |
46 | @Override
47 | public void visitOuterClass(String owner, String name, String desc) {
48 | checkIsTransformationEnabled();
49 | super.visitOuterClass(owner, name, desc);
50 | }
51 |
52 | @Override
53 | public void visitEnd() {
54 | checkIsTransformationEnabled();
55 | super.visitEnd();
56 | }
57 |
58 | private void checkIsTransformationEnabled() {
59 | if (!myAnnotationDescs.contains(enablerAnnotationDesc)) {
60 | throw new DoNotTransformException();
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/test/java/fi/jumi/actors/generator/codegen/ImportsTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.generator.codegen;
6 |
7 | import fi.jumi.actors.generator.EnclosingClass;
8 | import org.junit.Test;
9 |
10 | import java.util.*;
11 |
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 | import static org.hamcrest.Matchers.is;
14 |
15 | public class ImportsTest {
16 |
17 | private final Imports imports = new Imports();
18 |
19 | @Test
20 | public void imports_classes() {
21 | imports.addImports(JavaType.of(ArrayList.class));
22 |
23 | assertThat(imports.toString().trim(), is("import java.util.ArrayList;"));
24 | }
25 |
26 | @Test
27 | public void imports_member_classes() {
28 | imports.addImports(JavaType.of(EnclosingClass.MemberInterface.class));
29 |
30 | assertThat(imports.toString().trim(), is("import fi.jumi.actors.generator.EnclosingClass.MemberInterface;"));
31 | }
32 |
33 | @Test
34 | public void imports_packages() {
35 | imports.addPackageImport("com.example");
36 |
37 | assertThat(imports.toString().trim(), is("import com.example.*;"));
38 | }
39 |
40 | @Test
41 | public void each_class_is_imported_only_once() {
42 | imports.addImports(JavaType.of(ArrayList.class));
43 | imports.addImports(JavaType.of(ArrayList.class));
44 |
45 | assertThat(imports.toString().trim(), is("import java.util.ArrayList;"));
46 | }
47 |
48 | @Test
49 | public void multiple_classes_from_the_same_package_are_imported_one_class_at_a_time() {
50 | imports.addImports(JavaType.of(ArrayList.class));
51 | imports.addImports(JavaType.of(LinkedList.class));
52 |
53 | assertThat(imports.toString().trim(), is("import java.util.ArrayList;\nimport java.util.LinkedList;"));
54 | }
55 |
56 | @Test
57 | public void does_not_import_java_lang_classes() {
58 | imports.addImports(JavaType.of(String.class));
59 |
60 | assertThat(imports.toString().trim(), is(""));
61 | }
62 |
63 | @Test
64 | public void does_not_import_primitive_types() {
65 | imports.addImports(JavaType.of(int.class));
66 |
67 | assertThat(imports.toString().trim(), is(""));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/eventizers/ComposedEventizerProviderTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers;
6 |
7 | import fi.jumi.actors.queue.MessageSender;
8 | import org.junit.*;
9 | import org.junit.rules.ExpectedException;
10 |
11 | import static org.hamcrest.MatcherAssert.assertThat;
12 | import static org.hamcrest.Matchers.is;
13 |
14 | public class ComposedEventizerProviderTest {
15 |
16 | @Rule
17 | public ExpectedException thrown = ExpectedException.none();
18 |
19 | @Test
20 | public void returns_an_eventizer_which_corresponds_the_specified_type() {
21 | Class type1 = Integer.class;
22 | Eventizer eventizer1 = new DummyEventizer<>(type1);
23 | Class type2 = Double.class;
24 | Eventizer eventizer2 = new DummyEventizer<>(type2);
25 |
26 | ComposedEventizerProvider provider = new ComposedEventizerProvider(eventizer1, eventizer2);
27 |
28 | assertThat(provider.getEventizerForType(type1), is(eventizer1));
29 | assertThat(provider.getEventizerForType(type2), is(eventizer2));
30 | }
31 |
32 | @Test
33 | public void cannot_return_eventizers_for_unsupported_types() {
34 | ComposedEventizerProvider provider = new ComposedEventizerProvider();
35 |
36 | thrown.expect(IllegalArgumentException.class);
37 | thrown.expectMessage("unsupported type");
38 | thrown.expectMessage(NoEventizerForThisListener.class.getName());
39 |
40 | provider.getEventizerForType(NoEventizerForThisListener.class);
41 | }
42 |
43 |
44 | private interface NoEventizerForThisListener {
45 | }
46 |
47 | private static class DummyEventizer implements Eventizer {
48 | private final Class type;
49 |
50 | private DummyEventizer(Class type) {
51 | this.type = type;
52 | }
53 |
54 | @Override
55 | public Class getType() {
56 | return type;
57 | }
58 |
59 | @Override
60 | public T newFrontend(MessageSender> target) {
61 | return null;
62 | }
63 |
64 | @Override
65 | public MessageSender> newBackend(Object target) {
66 | return null;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/Promise.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | import com.google.common.util.concurrent.*;
8 |
9 | import javax.annotation.Nullable;
10 | import javax.annotation.concurrent.ThreadSafe;
11 | import java.util.concurrent.Future;
12 |
13 | @ThreadSafe
14 | public final class Promise extends AbstractFuture {
15 |
16 | public static Deferred defer() {
17 | return new Deferred<>();
18 | }
19 |
20 | public static Promise of(@Nullable V value) {
21 | Deferred deferred = defer();
22 | deferred.resolve(value);
23 | return deferred.promise();
24 | }
25 |
26 | private Promise() {
27 | }
28 |
29 | public void then(Callback callback) {
30 | Futures.addCallback(this, new FutureCallback() {
31 | @Override
32 | public void onSuccess(@Nullable V result) {
33 | callback.onResult(result);
34 | }
35 |
36 | @Override
37 | public void onFailure(Throwable error) {
38 | // TODO
39 | }
40 | });
41 | }
42 |
43 | @ThreadSafe
44 | public static final class Deferred {
45 |
46 | private final Promise promise = new Promise<>();
47 |
48 | private Deferred() {
49 | }
50 |
51 | public Promise promise() {
52 | return promise;
53 | }
54 |
55 | public boolean resolve(@Nullable V result) {
56 | return promise.set(result);
57 | }
58 |
59 | public boolean reject(Throwable error) {
60 | // TODO
61 | //return promise.setException(error);
62 | throw new UnsupportedOperationException("TODO");
63 | }
64 |
65 | public void delegate(Future source) {
66 | Futures.addCallback(JdkFutureAdapters.listenInPoolThread(source), new FutureCallback() {
67 | @Override
68 | public void onSuccess(@Nullable V result) {
69 | resolve(result);
70 | }
71 |
72 | @Override
73 | public void onFailure(Throwable error) {
74 | reject(error);
75 | }
76 | });
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Incremental_build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Clean_build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Generate_sources.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/end-to-end-tests/end-to-end-tests.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/thread-safety-agent/src/test/java/fi/jumi/threadsafetyagent/util/ClassFileTransformerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2014, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.threadsafetyagent.util;
6 |
7 | import org.junit.Test;
8 | import org.objectweb.asm.*;
9 |
10 | import java.lang.instrument.ClassFileTransformer;
11 |
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 | import static org.hamcrest.Matchers.is;
14 |
15 | public class ClassFileTransformerTest {
16 |
17 | @Test
18 | public void instruments_classes_with_the_provided_transformer() throws Exception {
19 | AbstractTransformationChain transformer = new AbstractTransformationChain() {
20 | @Override
21 | protected ClassVisitor getAdapters(ClassVisitor cv) {
22 | return new AddEqualsMethodWhichReturnsTrue(cv);
23 | }
24 | };
25 |
26 | Object obj = createWithTransformer(ClassToInstrument.class, transformer);
27 |
28 | assertThat(obj.equals(new Object()), is(true));
29 | }
30 |
31 | @Test
32 | public void null_transformer_does_not_instrument_classes() throws Exception {
33 | NullClassFileTransformer transformer = new NullClassFileTransformer();
34 |
35 | Object obj = createWithTransformer(ClassToInstrument.class, transformer);
36 |
37 | assertThat(obj.equals(new Object()), is(false));
38 | }
39 |
40 | private static Object createWithTransformer(Class> clazz, ClassFileTransformer transformer) throws Exception {
41 | String className = clazz.getName();
42 | ClassLoader loader = new TransformationTestClassLoader(className, transformer, null);
43 | return loader.loadClass(className).newInstance();
44 | }
45 |
46 |
47 | private static class AddEqualsMethodWhichReturnsTrue extends ClassVisitor {
48 |
49 | public AddEqualsMethodWhichReturnsTrue(ClassVisitor next) {
50 | super(Opcodes.ASM5, next);
51 | }
52 |
53 | @Override
54 | public void visitEnd() {
55 | MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
56 | mv.visitCode();
57 | mv.visitInsn(Opcodes.ICONST_1);
58 | mv.visitInsn(Opcodes.IRETURN);
59 | mv.visitMaxs(1, 2);
60 | mv.visitEnd();
61 | super.visitEnd();
62 | }
63 | }
64 |
65 | public static class ClassToInstrument {
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/eventizers/Eventizers.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2018, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers;
6 |
7 | import fi.jumi.actors.Promise;
8 |
9 | import javax.annotation.concurrent.Immutable;
10 | import java.lang.reflect.Method;
11 | import java.util.*;
12 | import java.util.concurrent.Future;
13 |
14 | @Immutable
15 | public class Eventizers {
16 |
17 | private static List> ALLOWED_RETURN_TYPES = Arrays.asList(
18 | Void.TYPE,
19 | Future.class,
20 | Promise.class
21 | );
22 |
23 |
24 | private Eventizers() {
25 | // utility class, not to be instantiated
26 | }
27 |
28 | public static void validateActorInterface(Class> type) {
29 | checkIsInterface(type);
30 | for (Method method : type.getMethods()) {
31 | checkReturnTypeIsAllowed(type, method);
32 | checkDoesNotThrowExceptions(type, method);
33 | }
34 | }
35 |
36 | private static void checkIsInterface(Class> type) {
37 | if (!type.isInterface()) {
38 | throw new IllegalArgumentException("actor interfaces must be interfaces, but got " + type);
39 | }
40 | }
41 |
42 | private static void checkReturnTypeIsAllowed(Class> type, Method method) {
43 | Class> returnType = method.getReturnType();
44 | if (!ALLOWED_RETURN_TYPES.contains(returnType)) {
45 | throw new IllegalArgumentException("actor interface methods must return void or " + Future.class.getName() + ", " +
46 | "but method " + method.getName() + " of " + type + " had return type " + returnType.getName());
47 | }
48 | }
49 |
50 | private static void checkDoesNotThrowExceptions(Class> type, Method method) {
51 | Class>[] exceptionTypes = method.getExceptionTypes();
52 | if (exceptionTypes.length > 0) {
53 | throw new IllegalArgumentException("actor interface methods may not throw exceptions, " +
54 | "but method " + method.getName() + " of " + type + " throws " + format(exceptionTypes));
55 | }
56 | }
57 |
58 | private static String format(Class>[] types) {
59 | List names = new ArrayList<>();
60 | for (Class> type : types) {
61 | names.add(type.getName());
62 | }
63 | String s = names.toString();
64 | return s.substring(1, s.length() - 1);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/benchmarks/RingTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.benchmarks;
6 |
7 | import fi.jumi.actors.*;
8 | import fi.jumi.actors.eventizers.dynamic.DynamicEventizerProvider;
9 | import fi.jumi.actors.listeners.*;
10 | import org.junit.Test;
11 |
12 | import static org.mockito.Mockito.*;
13 |
14 | public class RingTest {
15 |
16 | private final MessageListener messageListener = mock(MessageListener.class);
17 | private final SingleThreadedActors actors = new SingleThreadedActors(
18 | new DynamicEventizerProvider(),
19 | new CrashEarlyFailureHandler(),
20 | messageListener
21 | );
22 | private final ActorThread actorThread = actors.startActorThread();
23 |
24 |
25 | @Test
26 | public void one_node_one_round_trip() {
27 | int ringSize = 1;
28 | int roundTrips = 1;
29 | checkRingContract(ringSize, roundTrips);
30 | }
31 |
32 | @Test
33 | public void many_nodes_one_round_trip() {
34 | int ringSize = 10;
35 | int roundTrips = 1;
36 | checkRingContract(ringSize, roundTrips);
37 | }
38 |
39 | @Test
40 | public void one_node_many_round_trips() {
41 | int ringSize = 1;
42 | int roundTrips = 10;
43 | checkRingContract(ringSize, roundTrips);
44 | }
45 |
46 | @Test
47 | public void many_nodes_many_round_trips() {
48 | int ringSize = 3;
49 | int roundTrips = 5;
50 | checkRingContract(ringSize, roundTrips);
51 | }
52 |
53 | private void checkRingContract(int ringSize, int roundTrips) {
54 | ActorRef ring = createRing(ringSize);
55 |
56 | doRoundTrips(ring, roundTrips);
57 |
58 | verifyNumberOfMessagesSent(ringSize, roundTrips);
59 | }
60 |
61 | private ActorRef createRing(int ringSize) {
62 | ActorRef first = actorThread.bindActor(Ring.class, new RingStart(actorThread));
63 | first.tell().build(ringSize, first);
64 | actors.processEventsUntilIdle();
65 | return first;
66 | }
67 |
68 | private void doRoundTrips(ActorRef ring, int roundTrips) {
69 | reset(messageListener);
70 | ring.tell().forward(roundTrips);
71 | actors.processEventsUntilIdle();
72 | }
73 |
74 | private void verifyNumberOfMessagesSent(int ringSize, int roundTrips) {
75 | int initialMessage = 1;
76 | verify(messageListener, times(ringSize * roundTrips + initialMessage)).onProcessingStarted(any(), any());
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/jumi-actors/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | fi.jumi.actors
8 | parent
9 | 1.1-SNAPSHOT
10 | ../parent/pom.xml
11 |
12 |
13 | jumi-actors
14 | jar
15 |
16 |
17 | fi.jumi.actors.generator.INTERNAL
18 |
19 |
20 |
21 |
22 |
23 | com.google.guava
24 | guava
25 |
26 |
27 |
28 | com.google.caliper
29 | caliper
30 | test
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | maven-shade-plugin
42 |
43 |
44 |
45 | shade
46 |
47 |
48 |
49 |
50 |
51 | com.google
52 | ${shadedPrefix}.com.google
53 |
54 |
55 |
56 |
57 |
58 | com.google.guava:guava
59 |
60 | META-INF/**
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/thread-safety-agent/src/test/java/fi/jumi/threadsafetyagent/util/TransformationTestClassLoader.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2014, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.threadsafetyagent.util;
6 |
7 | import org.apache.commons.io.*;
8 |
9 | import java.io.*;
10 | import java.lang.instrument.*;
11 |
12 | public class TransformationTestClassLoader extends ClassLoader {
13 |
14 | private final ClassNameMatcher classesToInstrument;
15 | private final ClassFileTransformer transformer;
16 | private final File dirToWriteClasses;
17 |
18 | public TransformationTestClassLoader(String classesToInstrumentPattern, ClassFileTransformer transformer, File dirToWriteClasses) {
19 | super(TransformationTestClassLoader.class.getClassLoader());
20 | this.classesToInstrument = new ClassNameMatcher(classesToInstrumentPattern);
21 | this.transformer = transformer;
22 | this.dirToWriteClasses = dirToWriteClasses;
23 | }
24 |
25 | @Override
26 | public synchronized Class> loadClass(String name) throws ClassNotFoundException {
27 | Class> c = findLoadedClass(name);
28 | if (c == null) {
29 | c = classesToInstrument.matches(name) ? findClass(name) : super.loadClass(name);
30 | }
31 | return c;
32 | }
33 |
34 | @Override
35 | protected Class> findClass(String name) throws ClassNotFoundException {
36 | try {
37 | byte[] original = readClassBytes(name);
38 | byte[] result = transformer.transform(this, name, null, null, original);
39 | if (result == null) {
40 | result = original;
41 | }
42 | if (dirToWriteClasses != null) {
43 | try {
44 | FileUtils.writeByteArrayToFile(new File(dirToWriteClasses, name + ".class"), result);
45 | } catch (IOException e) {
46 | throw new RuntimeException(e);
47 | }
48 | }
49 | return defineClass(name, result, 0, result.length);
50 |
51 | } catch (IllegalClassFormatException e) {
52 | throw new ClassNotFoundException(name, e);
53 | }
54 | }
55 |
56 | private byte[] readClassBytes(String name) throws ClassNotFoundException {
57 | InputStream in = getResourceAsStream(name.replaceAll("\\.", "/") + ".class");
58 | if (in == null) {
59 | throw new ClassNotFoundException(name);
60 | }
61 | try {
62 | return IOUtils.toByteArray(in);
63 | } catch (IOException e) {
64 | throw new ClassNotFoundException(name, e);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/eventizers/EventToStringTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers;
6 |
7 | import org.junit.Test;
8 |
9 | import java.nio.charset.Charset;
10 |
11 | import static org.hamcrest.MatcherAssert.assertThat;
12 | import static org.hamcrest.Matchers.is;
13 |
14 | public class EventToStringTest {
15 |
16 | @Test
17 | public void shows_the_method_and_all_its_arguments() {
18 | assertThat(EventToString.format("TheClass", "theMethod"), is("TheClass.theMethod()"));
19 | assertThat(EventToString.format("TheClass", "theMethod", 123), is("TheClass.theMethod(123)"));
20 | assertThat(EventToString.format("TheClass", "theMethod", 123, true), is("TheClass.theMethod(123, true)"));
21 | }
22 |
23 | @Test
24 | public void does_not_crash_to_null_arguments() {
25 | assertThat(EventToString.format("TheClass", "theMethod", (Object) null), is("TheClass.theMethod(null)"));
26 | }
27 |
28 | @Test
29 | public void quotes_string_arguments() {
30 | assertThat(EventToString.format("TheClass", "theMethod", "foo"), is("TheClass.theMethod(\"foo\")"));
31 | }
32 |
33 | @Test
34 | public void shows_strings_using_string_literal_escape_sequences() {
35 | assertThat(escapeSpecialChars("\b"), is("\\b"));
36 | assertThat(escapeSpecialChars("\t"), is("\\t"));
37 | assertThat(escapeSpecialChars("\n"), is("\\n"));
38 | assertThat(escapeSpecialChars("\f"), is("\\f"));
39 | assertThat(escapeSpecialChars("\r"), is("\\r"));
40 | assertThat(escapeSpecialChars("\""), is("\\\""));
41 | assertThat(escapeSpecialChars("\\"), is("\\\\"));
42 | }
43 |
44 | @Test
45 | public void escapes_ISO_control_characters() {
46 | assertThat(escapeSpecialChars("\0"), is("\\u0000"));
47 | assertThat(escapeSpecialChars("\1"), is("\\u0001"));
48 | assertThat(escapeSpecialChars("\u001f"), is("\\u001f"));
49 | }
50 |
51 | @Test
52 | public void escapes_unmappable_characters_for_the_current_charset() {
53 | assertThat(escapeSpecialChars("åäö", Charset.forName("ISO-8859-1")), is("åäö"));
54 | assertThat(escapeSpecialChars("åäö", Charset.forName("US-ASCII")), is("\\u00e5\\u00e4\\u00f6"));
55 | }
56 |
57 |
58 | // helpers
59 |
60 | private static String escapeSpecialChars(String arg) {
61 | return escapeSpecialChars(arg, Charset.forName("UTF-8"));
62 | }
63 |
64 | private static String escapeSpecialChars(String arg, Charset charset) {
65 | return new EventToString(charset).escapeSpecialChars(arg).build();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/eventizers/dynamic/DynamicEvent.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers.dynamic;
6 |
7 | import com.google.common.base.Throwables;
8 | import fi.jumi.actors.Promise;
9 | import fi.jumi.actors.eventizers.*;
10 |
11 | import javax.annotation.Nullable;
12 | import javax.annotation.concurrent.ThreadSafe;
13 | import java.io.*;
14 | import java.lang.reflect.Method;
15 | import java.util.concurrent.Future;
16 |
17 | @ThreadSafe
18 | public class DynamicEvent implements Event, Serializable {
19 |
20 | private transient Method method;
21 | private final Object[] args;
22 | private final transient Promise.Deferred deferred;
23 |
24 | public DynamicEvent(Method method, Object[] args) {
25 | this(method, args, null);
26 | }
27 |
28 | public DynamicEvent(Method method, Object[] args, @Nullable Promise.Deferred deferred) {
29 | this.method = method;
30 | this.args = args;
31 | this.deferred = deferred;
32 | }
33 |
34 | @SuppressWarnings("unchecked")
35 | @Override
36 | public void fireOn(T target) {
37 | try {
38 | Object result = method.invoke(target, args);
39 | if (deferred != null) {
40 | deferred.delegate((Future) result);
41 | }
42 | } catch (Exception e) {
43 | throw Throwables.propagate(e);
44 | }
45 | }
46 |
47 | private void writeObject(ObjectOutputStream out) throws IOException {
48 | out.defaultWriteObject();
49 | out.writeObject(method.getName());
50 | out.writeObject(method.getDeclaringClass());
51 | out.writeObject(method.getParameterTypes());
52 | }
53 |
54 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
55 | in.defaultReadObject();
56 | String name = (String) in.readObject();
57 | Class> declaringClass = (Class>) in.readObject();
58 | Class>[] parameterTypes = (Class>[]) in.readObject();
59 | try {
60 | method = declaringClass.getMethod(name, parameterTypes);
61 | } catch (NoSuchMethodException e) {
62 | throw new RuntimeException(e);
63 | }
64 | }
65 |
66 | @Override
67 | public String toString() {
68 | return EventToString.format(method.getDeclaringClass().getSimpleName(), method.getName(), nonNull(args));
69 | }
70 |
71 | private static Object[] nonNull(@Nullable Object[] args) {
72 | if (args == null) {
73 | return new Object[0];
74 | }
75 | return args;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/benchmarks/RingBenchmark.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2018, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.benchmarks;
6 |
7 | import com.google.caliper.*;
8 | import com.google.caliper.api.VmOptions;
9 | import com.google.caliper.runner.CaliperMain;
10 | import fi.jumi.actors.*;
11 | import fi.jumi.actors.eventizers.dynamic.DynamicEventizerProvider;
12 | import fi.jumi.actors.listeners.*;
13 |
14 | import java.util.concurrent.*;
15 |
16 | /**
17 | * Create a ring of actors and forward a message around the ring multiple times.
18 | *
19 | * Based on http://blog.grayproductions.net/articles/erlang_message_passing/
20 | */
21 | // XXX: workaround for https://stackoverflow.com/questions/29199509/caliper-error-cicompilercount-of-1-is-invalid-must-be-at-least-2
22 | @VmOptions("-XX:-TieredCompilation")
23 | public class RingBenchmark {
24 |
25 | @Param int ringSize;
26 | @Param int roundTrips;
27 |
28 | private final CyclicBarrier barrier = new CyclicBarrier(2);
29 | private final ExecutorService executor = Executors.newCachedThreadPool();
30 |
31 | private ActorRef ring;
32 |
33 | @BeforeExperiment
34 | public void before() {
35 | MultiThreadedActors actors = new MultiThreadedActors(
36 | executor,
37 | new DynamicEventizerProvider(),
38 | new CrashEarlyFailureHandler(),
39 | new NullMessageListener()
40 | );
41 | ActorThread actorThread = actors.startActorThread();
42 |
43 | ring = actorThread.bindActor(Ring.class, new RingStart(actorThread) {
44 | @Override
45 | public void forward(int roundTrips) {
46 | super.forward(roundTrips);
47 | if (roundTrips == 0) {
48 | sync(barrier);
49 | }
50 | }
51 | });
52 | ring.tell().build(ringSize, ring);
53 | }
54 |
55 | @AfterExperiment
56 | public void after() {
57 | executor.shutdownNow();
58 | }
59 |
60 | @Benchmark
61 | public void timeRingRoundTrips(int reps) {
62 | for (int i = 0; i < reps; i++) {
63 | ring.tell().forward(roundTrips);
64 | sync(barrier);
65 | }
66 | }
67 |
68 | private static void sync(CyclicBarrier barrier) {
69 | try {
70 | barrier.await();
71 | } catch (Exception e) {
72 | e.printStackTrace();
73 | }
74 | }
75 |
76 | public static void main(String[] args) {
77 | CaliperMain.main(RingBenchmark.class, new String[]{"-DringSize=1,10,100,1000,10000", "-DroundTrips=1000"});
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/benchmarks/WarmStartupBenchmark.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2018, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.benchmarks;
6 |
7 | import com.google.caliper.Benchmark;
8 | import com.google.caliper.api.VmOptions;
9 | import com.google.caliper.runner.CaliperMain;
10 | import fi.jumi.actors.*;
11 | import fi.jumi.actors.eventizers.dynamic.DynamicEventizerProvider;
12 | import fi.jumi.actors.listeners.*;
13 |
14 | import java.util.concurrent.*;
15 |
16 | /**
17 | * Create the actors container, send and receive one message.
18 | */
19 | // XXX: workaround for https://stackoverflow.com/questions/29199509/caliper-error-cicompilercount-of-1-is-invalid-must-be-at-least-2
20 | @VmOptions("-XX:-TieredCompilation")
21 | public class WarmStartupBenchmark {
22 |
23 | private final BusyWaitBarrier barrier = new BusyWaitBarrier();
24 | private final ExecutorService executor = Executors.newCachedThreadPool();
25 |
26 | @Benchmark
27 | public void timeMultiThreadedActors(int reps) throws Exception {
28 | for (int i = 0; i < reps; i++) {
29 | MultiThreadedActors actors = new MultiThreadedActors(
30 | executor,
31 | new DynamicEventizerProvider(),
32 | new CrashEarlyFailureHandler(),
33 | new NullMessageListener()
34 | );
35 | ActorThread actorThread = actors.startActorThread();
36 |
37 | ActorRef runnable = actorThread.bindActor(Runnable.class, new Runnable() {
38 | @Override
39 | public void run() {
40 | barrier.trigger();
41 | }
42 | });
43 | runnable.tell().run();
44 | barrier.await();
45 |
46 | actorThread.stop();
47 | }
48 | }
49 |
50 | @Benchmark
51 | public void timeSingleThreadedActors(int reps) {
52 | for (int i = 0; i < reps; i++) {
53 | SingleThreadedActors actors = new SingleThreadedActors(
54 | new DynamicEventizerProvider(),
55 | new CrashEarlyFailureHandler(),
56 | new NullMessageListener()
57 | );
58 | ActorThread actorThread = actors.startActorThread();
59 |
60 | ActorRef runnable = actorThread.bindActor(Runnable.class, new Runnable() {
61 | @Override
62 | public void run() {
63 | }
64 | });
65 | runnable.tell().run();
66 |
67 | actors.processEventsUntilIdle();
68 | }
69 | }
70 |
71 | public static void main(String[] args) {
72 | CaliperMain.main(WarmStartupBenchmark.class, new String[0]);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/thread-safety-agent/src/main/java/fi/jumi/threadsafetyagent/ThreadSafetyChecker.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.threadsafetyagent;
6 |
7 | import javax.annotation.concurrent.ThreadSafe;
8 | import java.util.*;
9 |
10 | @ThreadSafe
11 | public class ThreadSafetyChecker {
12 |
13 | /**
14 | * Not volatile, because it is not required for thread safety;
15 | * it would only would impose a read barrier and might reduce performance.
16 | */
17 | private Thread lastThread = null;
18 |
19 | private final Set calledFromThreads = new HashSet<>(1, 1);
20 | private CallLocation callLocations = null;
21 |
22 |
23 | public void checkCurrentThread() {
24 | Thread currentThread = Thread.currentThread();
25 |
26 | // TODO: measure the usefulness of this optimization later, in a scenario which is not dominated by class loading overhead
27 | // Performance optimization: avoid checking a thread twice
28 | // when called repeatedly from the same thread, which is the most common case.
29 | if (currentThread == lastThread) {
30 | return;
31 | }
32 | lastThread = currentThread;
33 |
34 | fullCheck(currentThread);
35 | }
36 |
37 | private synchronized void fullCheck(Thread currentThread) {
38 | if (calledFromThreads.contains(currentThread)) {
39 | return;
40 | }
41 | calledFromThreads.add(currentThread);
42 | callLocations = new CallLocation(callLocations);
43 | if (calledFromThreads.size() > 1) {
44 | AssertionError e = new AssertionError("non-thread-safe instance called from multiple threads: " + threadNames(calledFromThreads)) {
45 | @Override
46 | public Throwable fillInStackTrace() {
47 | return this;
48 | }
49 | };
50 | e.initCause(callLocations);
51 | throw e;
52 | }
53 | }
54 |
55 | private static String threadNames(Set threads) {
56 | List threadNames = new ArrayList<>();
57 | for (Thread thread : threads) {
58 | threadNames.add(thread.getName());
59 | }
60 | Collections.sort(threadNames);
61 | String s = "";
62 | for (String threadName : threadNames) {
63 | if (s.length() > 0) {
64 | s += ", ";
65 | }
66 | s += threadName;
67 | }
68 | return s;
69 | }
70 |
71 | private static class CallLocation extends RuntimeException {
72 | public CallLocation(CallLocation previousLocations) {
73 | super("called from thread: " + Thread.currentThread().getName(), previousLocations);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/test/java/fi/jumi/actors/generator/codegen/JavaTypeTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.generator.codegen;
6 |
7 | import org.junit.Test;
8 |
9 | import java.lang.reflect.Type;
10 | import java.util.List;
11 |
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 | import static org.hamcrest.Matchers.is;
14 |
15 | @SuppressWarnings({"UnusedDeclaration"})
16 | public class JavaTypeTest {
17 |
18 | private static final GenericType SINGLE_TYPE_ARGUMENT = null;
19 | private static final GenericType> WILDCARD_TYPE_ARGUMENT = null;
20 | private static final GenericType2 MULTIPLE_TYPE_ARGUMENTS = null;
21 | private static final GenericType> NESTED_TYPE_ARGUMENTS = null;
22 |
23 | @Test
24 | public void regular_type() {
25 | JavaType t = JavaType.of(NonGenericType.class);
26 |
27 | assertThat(t.getPackage(), is("fi.jumi.actors.generator.codegen"));
28 | assertThat(t.getRawSimpleName(), is("NonGenericType"));
29 | assertThat(t.getGenericSimpleName(), is("NonGenericType"));
30 | }
31 |
32 | @Test
33 | public void generic_type() throws Exception {
34 | JavaType t = JavaType.of(genericTypeOfField("SINGLE_TYPE_ARGUMENT"));
35 |
36 | assertThat(t.getPackage(), is("fi.jumi.actors.generator.codegen"));
37 | assertThat(t.getRawSimpleName(), is("GenericType"));
38 | assertThat(t.getGenericSimpleName(), is("GenericType"));
39 | }
40 |
41 | @Test
42 | public void wildcard_type() throws Exception {
43 | JavaType t = JavaType.of(genericTypeOfField("WILDCARD_TYPE_ARGUMENT"));
44 |
45 | assertThat(t.getPackage(), is("fi.jumi.actors.generator.codegen"));
46 | assertThat(t.getRawSimpleName(), is("GenericType"));
47 | assertThat(t.getGenericSimpleName(), is("GenericType>"));
48 | }
49 |
50 | @Test
51 | public void multiple_type_arguments() throws Exception {
52 | JavaType t = JavaType.of(genericTypeOfField("MULTIPLE_TYPE_ARGUMENTS"));
53 |
54 | assertThat(t.getRawSimpleName(), is("GenericType2"));
55 | assertThat(t.getGenericSimpleName(), is("GenericType2"));
56 |
57 | }
58 |
59 | @Test
60 | public void nested_type_arguments() throws Exception {
61 | JavaType t = JavaType.of(genericTypeOfField("NESTED_TYPE_ARGUMENTS"));
62 |
63 | assertThat(t.getRawSimpleName(), is("GenericType"));
64 | assertThat(t.getGenericSimpleName(), is("GenericType>"));
65 | }
66 |
67 | private static Type genericTypeOfField(String fieldName) throws Exception {
68 | return JavaTypeTest.class.getDeclaredField(fieldName).getGenericType();
69 | }
70 | }
71 |
72 | class NonGenericType {
73 | }
74 |
75 | class GenericType {
76 | }
77 |
78 | class GenericType2 {
79 | }
80 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/workers/WorkerCounter.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.workers;
6 |
7 | import fi.jumi.actors.ActorRef;
8 |
9 | import javax.annotation.concurrent.*;
10 | import java.util.concurrent.Executor;
11 | import java.util.concurrent.atomic.AtomicInteger;
12 |
13 | /**
14 | * Fires a callback after a transitive bunch of worker threads are finished.
15 | */
16 | @ThreadSafe
17 | public class WorkerCounter implements Executor {
18 |
19 | private final Executor realExecutor;
20 | private final AtomicInteger activeWorkers = new AtomicInteger(0);
21 |
22 | @GuardedBy("this")
23 | private ActorRef onFinished;
24 |
25 | public WorkerCounter(Executor realExecutor) {
26 | this.realExecutor = realExecutor;
27 | }
28 |
29 | @Override
30 | public void execute(Runnable command) {
31 | realExecutor.execute(new Worker(command));
32 | }
33 |
34 | /**
35 | * Calls {@link WorkerListener#onAllWorkersFinished()} on the specified callback after all commands previously
36 | * submitted to {@link #execute(Runnable)}, and recursively all commands which they submitted to {@link
37 | * #execute(Runnable)}, have finished executing.
38 | */
39 | public synchronized void afterPreviousWorkersFinished(ActorRef onFinished) {
40 | if (this.onFinished != null) {
41 | throw new IllegalStateException("a callback already exists; wait for the workers to finish before setting a new callback");
42 | }
43 | this.onFinished = onFinished;
44 | if (activeWorkers.get() == 0) {
45 | fireAllWorkersFinished();
46 | }
47 | }
48 |
49 | private synchronized void fireAllWorkersFinished() {
50 | if (onFinished != null) {
51 | onFinished.tell().onAllWorkersFinished();
52 | onFinished = null;
53 | }
54 | }
55 |
56 |
57 | // Used only from the Worker class, to make sure that they are always called
58 |
59 | private void fireWorkerCreated() {
60 | activeWorkers.incrementAndGet();
61 | }
62 |
63 | private void fireWorkerFinished() {
64 | int workers = activeWorkers.decrementAndGet();
65 | if (workers == 0) {
66 | fireAllWorkersFinished();
67 | }
68 | }
69 |
70 | @ThreadSafe
71 | private class Worker implements Runnable {
72 | private final Runnable command;
73 |
74 | public Worker(Runnable command) {
75 | fireWorkerCreated();
76 | this.command = command;
77 | }
78 |
79 | @Override
80 | public void run() {
81 | try {
82 | command.run();
83 | } finally {
84 | fireWorkerFinished();
85 | }
86 | }
87 |
88 | @Override
89 | public String toString() {
90 | return command.toString();
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/SingleThreadedActors.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | import fi.jumi.actors.eventizers.EventizerProvider;
8 | import fi.jumi.actors.listeners.*;
9 |
10 | import javax.annotation.concurrent.*;
11 | import java.util.List;
12 | import java.util.concurrent.*;
13 |
14 | /**
15 | * Single-threaded actors container for testing. The {@link ActorThread}s are not backed by real threads -
16 | * instead they will process messages when the {@link #processEventsUntilIdle()} method is called.
17 | */
18 | @NotThreadSafe
19 | public class SingleThreadedActors extends Actors {
20 |
21 | private final List actorThreads = new CopyOnWriteArrayList<>();
22 | private final MessageListener messageListener;
23 |
24 | public SingleThreadedActors(EventizerProvider eventizerProvider, FailureHandler failureHandler, MessageListener messageListener) {
25 | super(eventizerProvider, failureHandler, messageListener);
26 | this.messageListener = messageListener;
27 | }
28 |
29 | @Override
30 | void startActorThread(MessageProcessor actorThread) {
31 | actorThreads.add(actorThread);
32 | }
33 |
34 | /**
35 | * Processes in the current thread all messages which were sent to actors. The order of processing messages is
36 | * deterministic. Will block until all messages have been processed and nobody is sending more messages.
37 | *
38 | * When using {@link CrashEarlyFailureHandler}, will rethrow uncaught exceptions from actors to the caller of this
39 | * method.
40 | */
41 | public void processEventsUntilIdle() {
42 | boolean idle;
43 | do {
44 | idle = true;
45 | for (MessageProcessor actorThread : actorThreads) {
46 | if (actorThread.processNextMessageIfAny()) {
47 | idle = false;
48 | }
49 | if (Thread.interrupted()) {
50 | actorThreads.remove(actorThread);
51 | }
52 | }
53 | } while (!idle);
54 | }
55 |
56 | /**
57 | * Returns an asynchronous {@link Executor} which works the same way as all the actors in this container. Useful in
58 | * tests to have asynchrony without the non-determinism of real threads.
59 | *
60 | * @see #processEventsUntilIdle()
61 | */
62 | public Executor getExecutor() {
63 | return messageListener.getListenedExecutor(new AsynchronousExecutor());
64 | }
65 |
66 |
67 | @ThreadSafe
68 | private class AsynchronousExecutor implements Executor {
69 | @Override
70 | public void execute(final Runnable command) {
71 | // To unify the concepts of an executor and actors,
72 | // we implement the executor as one-time actor threads.
73 | ActorThread actorThread = startActorThread();
74 | ActorRef actor = actorThread.bindActor(Runnable.class, command);
75 | actor.tell().run();
76 | actorThread.stop();
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/thread-safety-agent/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | fi.jumi.actors
8 | parent
9 | 1.1-SNAPSHOT
10 | ../parent/pom.xml
11 |
12 |
13 | thread-safety-agent
14 | jar
15 |
16 |
17 | fi.jumi.threadsafetyagent.INTERNAL
18 |
19 |
20 |
21 |
22 |
23 | org.ow2.asm
24 | asm-debug-all
25 |
26 |
27 |
28 | commons-io
29 | commons-io
30 | test
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | maven-shade-plugin
42 |
43 |
44 |
45 | shade
46 |
47 |
48 |
49 |
50 |
51 | fi.jumi.threadsafetyagent.PreMain
52 |
53 |
54 |
55 |
56 |
57 |
58 | org.objectweb.asm
59 | ${shadedPrefix}.org.objectweb.asm
60 |
61 |
62 |
63 |
64 |
65 | asm:asm
66 |
67 | META-INF/**
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/examples/PiTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.examples;
6 |
7 | import fi.jumi.actors.*;
8 | import fi.jumi.actors.eventizers.dynamic.DynamicEventizerProvider;
9 | import fi.jumi.actors.listeners.*;
10 | import org.junit.Test;
11 |
12 | import java.util.*;
13 | import java.util.concurrent.Executor;
14 |
15 | import static fi.jumi.actors.examples.Pi.*;
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 | import static org.hamcrest.Matchers.*;
18 |
19 | public class PiTest {
20 |
21 | /**
22 | * An example of a unit test. We don't need to create the actors container, we just
23 | * need to wrap our test double into an ActorRef (never do this in production code!)
24 | */
25 | @Test
26 | public void each_worker_calculates_one_part_of_the_pi_approximation() {
27 | final List results = new ArrayList<>();
28 | ActorRef listener = ActorRef.wrap((ResultListener) new ResultListener() {
29 | @Override
30 | public void onResult(double result) {
31 | results.add(result);
32 | }
33 | });
34 |
35 | int nrOfElements = 10;
36 | new Worker(0, nrOfElements, listener).run();
37 | new Worker(1, nrOfElements, listener).run();
38 | new Worker(2, nrOfElements, listener).run();
39 |
40 | assertThat(results.size(), is(3));
41 | assertThat(results.get(0), is(closeTo(3.041, 0.001)));
42 | assertThat(results.get(1), is(closeTo(0.049, 0.001)));
43 | assertThat(results.get(2), is(closeTo(0.016, 0.001)));
44 | }
45 |
46 | /**
47 | * An example of an integration test. We use a single-threaded actors container
48 | * so we can test easily, deterministically, how the actors play together.
49 | */
50 | @Test
51 | public void master_combines_the_results_from_workers() {
52 | class SpyListener implements ResultListener {
53 | double result;
54 |
55 | @Override
56 | public void onResult(double result) {
57 | this.result = result;
58 | }
59 | }
60 | SpyListener spy = new SpyListener();
61 | SingleThreadedActors actors = new SingleThreadedActors(
62 | new DynamicEventizerProvider(),
63 | // Will rethrow any exceptions to this test thread
64 | new CrashEarlyFailureHandler(),
65 | new NullMessageListener()
66 | );
67 | Executor workersThreadPool = actors.getExecutor(); // Also single-threaded, runs in this same test thread
68 | ActorThread thread = actors.startActorThread();
69 |
70 | ActorRef master = thread.bindActor(Calculator.class, new Master(thread, workersThreadPool, 10, 10));
71 | ActorRef listener = thread.bindActor(ResultListener.class, spy);
72 | master.tell().approximatePi(listener);
73 |
74 | actors.processEventsUntilIdle(); // Any exceptions from actors would be thrown here
75 | assertThat(spy.result, is(closeTo(3.14, 0.01)));
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/examples/HelloWorld.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.examples;
6 |
7 | import fi.jumi.actors.*;
8 | import fi.jumi.actors.eventizers.dynamic.DynamicEventizerProvider;
9 | import fi.jumi.actors.listeners.*;
10 |
11 | import java.util.concurrent.*;
12 |
13 | public class HelloWorld {
14 |
15 | public static void main(String[] args) {
16 |
17 | // Configure the actors implementation and its dependencies:
18 | // - Executor for running the actors (when using MultiThreadedActors)
19 | // - Eventizers for converting method calls to event objects and back again
20 | // - Handler for uncaught exceptions from actors
21 | // - Logging of all messages for debug purposes (here disabled)
22 | ExecutorService actorsThreadPool = Executors.newCachedThreadPool();
23 | Actors actors = new MultiThreadedActors(
24 | actorsThreadPool,
25 | new DynamicEventizerProvider(),
26 | new CrashEarlyFailureHandler(),
27 | new NullMessageListener()
28 | );
29 |
30 | // Start up a thread where messages to actors will be executed
31 | ActorThread actorThread = actors.startActorThread();
32 |
33 | // Create an actor to be executed in this actor thread. Some guidelines:
34 | // - Never pass around a direct reference to an actor, but always use ActorRef
35 | // - To avoid confusion, also avoid passing around the proxy returned by ActorRef.tell(),
36 | // though that may sometimes be warranted when interacting with actor-unaware code
37 | // or if you wish to avoid the dependency to ActorRef
38 | ActorRef helloGreeter = actorThread.bindActor(Greeter.class, new Greeter() {
39 | @Override
40 | public void sayGreeting(String name) {
41 | System.out.println("Hello " + name + " from " + Thread.currentThread().getName());
42 | }
43 | });
44 |
45 | // The pattern for sending messages to actors
46 | helloGreeter.tell().sayGreeting("World");
47 |
48 | // "Wazzup" should be printed before "Hello World" and the thread name will be different
49 | System.out.println("Wazzup from " + Thread.currentThread().getName());
50 |
51 | // Tell all actors in this actor thread to stop themselves after processing
52 | // all previously sent messages. An actor can also itself call Thread.currentThread().interrupt()
53 | actorThread.stop();
54 |
55 | // Finally do an orderly shutdown of the executor.
56 | // Calling shutdownNow() on the executor would interrupt and stop all
57 | // actor threads immediately without waiting for messages to be processed.
58 | actorsThreadPool.shutdown();
59 | }
60 |
61 | public interface Greeter {
62 |
63 | // Methods on actor interfaces must return void and not have throws declarations.
64 | // Any parameters may be used, but immutable ones are strongly encouraged.
65 | // Actors should always be passed around as ActorRefs.
66 | void sayGreeting(String name);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/jumi-actors/src/main/java/fi/jumi/actors/eventizers/EventToString.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2012, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.eventizers;
6 |
7 | import javax.annotation.concurrent.NotThreadSafe;
8 | import java.nio.charset.*;
9 |
10 | @NotThreadSafe
11 | public class EventToString {
12 |
13 | private final CharsetEncoder charsetEncoder;
14 | private final StringBuilder result = new StringBuilder();
15 |
16 | public EventToString(Charset charset) {
17 | charsetEncoder = charset.newEncoder();
18 | }
19 |
20 | public static String format(String className, String methodName, Object... args) {
21 | return new EventToString(Charset.defaultCharset())
22 | .formatMethodCall(className, methodName, args)
23 | .build();
24 | }
25 |
26 | public String build() {
27 | return result.toString();
28 | }
29 |
30 | public EventToString formatMethodCall(String className, String methodName, Object... args) {
31 | result.append(className);
32 | result.append('.');
33 | result.append(methodName);
34 | result.append('(');
35 | for (int i = 0; i < args.length; i++) {
36 | if (i > 0) {
37 | result.append(", ");
38 | }
39 | formatArg(args[i]);
40 | }
41 | result.append(')');
42 | return this;
43 | }
44 |
45 | private void formatArg(Object arg) {
46 | if (arg instanceof String) {
47 | result.append('"');
48 | escapeSpecialChars((String) arg);
49 | result.append('"');
50 | } else {
51 | result.append(arg);
52 | }
53 | }
54 |
55 | // package-private for testing
56 | EventToString escapeSpecialChars(String arg) {
57 | for (int i = 0; i < arg.length(); i++) {
58 | escapeSpecialChar(arg.charAt(i));
59 | }
60 | return this;
61 | }
62 |
63 | private void escapeSpecialChar(char ch) {
64 | switch (ch) {
65 | case '\b':
66 | result.append("\\b");
67 | return;
68 | case '\t':
69 | result.append("\\t");
70 | return;
71 | case '\n':
72 | result.append("\\n");
73 | return;
74 | case '\f':
75 | result.append("\\f");
76 | return;
77 | case '\r':
78 | result.append("\\r");
79 | return;
80 | case '\"':
81 | result.append("\\\"");
82 | return;
83 | case '\\':
84 | result.append("\\\\");
85 | return;
86 | }
87 |
88 | if (Character.isISOControl(ch) || isUnmappable(ch)) {
89 | String mask = "\\u0000";
90 | String hex = Integer.toHexString(ch);
91 | result.append(mask, 0, mask.length() - hex.length());
92 | result.append(hex);
93 | } else {
94 | result.append(ch);
95 | }
96 | }
97 |
98 | private boolean isUnmappable(char ch) {
99 | return !charsetEncoder.canEncode(ch);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/jumi-actors/src/test/java/fi/jumi/actors/ActorInterfaceContractsTest.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2018, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors;
6 |
7 | import fi.jumi.actors.eventizers.Eventizers;
8 | import org.junit.*;
9 | import org.junit.rules.ExpectedException;
10 |
11 | import java.util.concurrent.*;
12 |
13 | public class ActorInterfaceContractsTest {
14 |
15 | public static final Class INVALID_ACTOR_INTERFACE = HasNonVoidMethods.class;
16 |
17 | @Rule
18 | public final ExpectedException thrown = ExpectedException.none();
19 |
20 | @Test
21 | public void must_be_interfaces() {
22 | thrown.expect(IllegalArgumentException.class);
23 | thrown.expectMessage("actor interfaces must be interfaces, " +
24 | "but got class fi.jumi.actors.ActorInterfaceContractsTest$NotAnInterface");
25 |
26 | Eventizers.validateActorInterface(NotAnInterface.class);
27 | }
28 |
29 | @Test
30 | public void methods_may_return_void_or_future() {
31 | Eventizers.validateActorInterface(AllAllowedReturnTypes.class);
32 | }
33 |
34 | @Test
35 | public void methods_must_not_return_other_types() {
36 | thrown.expect(IllegalArgumentException.class);
37 | thrown.expectMessage("actor interface methods must return void or java.util.concurrent.Future, " +
38 | "but method onSomething of interface fi.jumi.actors.ActorInterfaceContractsTest$HasNonVoidMethods " +
39 | "had return type java.lang.String");
40 |
41 | Eventizers.validateActorInterface(HasNonVoidMethods.class);
42 | }
43 |
44 | @Test
45 | public void methods_must_not_throw_exceptions() {
46 | thrown.expect(IllegalArgumentException.class);
47 | thrown.expectMessage("actor interface methods may not throw exceptions, " +
48 | "but method onSomething of interface fi.jumi.actors.ActorInterfaceContractsTest$HasExceptionThrowingMethods " +
49 | "throws java.lang.Exception");
50 |
51 | Eventizers.validateActorInterface(HasExceptionThrowingMethods.class);
52 | }
53 |
54 | @Test
55 | public void methods_returning_future_must_use_the_interface_instead_of_the_implementation_class() {
56 | thrown.expect(IllegalArgumentException.class);
57 | thrown.expectMessage("actor interface methods must return void or java.util.concurrent.Future, " +
58 | "but method returnsFutureTask of interface fi.jumi.actors.ActorInterfaceContractsTest$DeclaresWrongFutureImplementation " +
59 | "had return type java.util.concurrent.FutureTask");
60 |
61 | Eventizers.validateActorInterface(DeclaresWrongFutureImplementation.class);
62 | }
63 |
64 |
65 | // guinea pigs
66 |
67 | public static abstract class NotAnInterface {
68 | }
69 |
70 | public interface AllAllowedReturnTypes {
71 |
72 | void returnsVoid();
73 |
74 | Future> returnsFuture();
75 |
76 | Promise> returnsPromise();
77 | }
78 |
79 | public interface HasNonVoidMethods {
80 | String onSomething();
81 | }
82 |
83 | public interface HasExceptionThrowingMethods {
84 | void onSomething() throws Exception;
85 | }
86 |
87 | public interface DeclaresWrongFutureImplementation {
88 | FutureTask> returnsFutureTask();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/jumi-actors-generator/src/main/java/fi/jumi/actors/generator/codegen/ClassBuilder.java:
--------------------------------------------------------------------------------
1 | // Copyright © 2011-2015, Esko Luontola
2 | // This software is released under the Apache License 2.0.
3 | // The license text is at http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | package fi.jumi.actors.generator.codegen;
6 |
7 | import java.util.*;
8 |
9 | @SuppressWarnings({"StringConcatenationInsideStringBufferAppend"})
10 | public class ClassBuilder {
11 |
12 | private final String className;
13 | private final String targetPackage;
14 |
15 | private final StringBuilder methods = new StringBuilder();
16 | private final List interfaces = new ArrayList<>();
17 | private final List annotations = new ArrayList<>();
18 | public final Imports imports = new Imports();
19 | private final List constructorArguments = new ArrayList<>();
20 |
21 | public ClassBuilder(String className, String targetPackage) {
22 | this.className = className;
23 | this.targetPackage = targetPackage;
24 | }
25 |
26 | public void annotate(String annotation) {
27 | this.annotations.add(annotation);
28 | }
29 |
30 | public void implement(JavaType anInterface) {
31 | interfaces.add(anInterface);
32 | }
33 |
34 | public void fieldsAndConstructorParameters(List arguments) {
35 | constructorArguments.addAll(arguments);
36 | }
37 |
38 | public void addMethod(CharSequence methodSource) {
39 | if (methods.length() > 0) {
40 | methods.append("\n");
41 | }
42 | methods.append(methodSource);
43 | }
44 |
45 | public GeneratedClass build() {
46 | String name = targetPackage + "." + className;
47 | StringBuilder body = classBody(); // classBody adds imports, so it must be executed before imports
48 | String source = packageStatement() + imports.toString() + body;
49 | return new GeneratedClass(name, source);
50 | }
51 |
52 |
53 | // source fragments
54 |
55 | private String packageStatement() {
56 | return "package " + targetPackage + ";\n\n";
57 | }
58 |
59 | private StringBuilder classBody() {
60 | StringBuilder sb = new StringBuilder();
61 | for (String annotation : annotations) {
62 | sb.append(annotation);
63 | sb.append("\n");
64 | }
65 | sb.append("public class " + className + " implements " + toImplementsDeclaration(interfaces) + " {\n");
66 | sb.append("\n");
67 | if (constructorArguments.size() > 0) {
68 | for (JavaVar var : constructorArguments) {
69 | sb.append(" private final " + imports.getSimpleName(var.getType()) + " " + var.getName() + ";\n");
70 | }
71 | sb.append("\n");
72 | sb.append(" public " + className + "(" + JavaVar.toFormalArguments(constructorArguments, imports) + ") {\n");
73 | for (JavaVar var : constructorArguments) {
74 | sb.append(" this." + var.getName() + " = " + var.getName() + ";\n");
75 | }
76 | sb.append(" }\n");
77 | sb.append("\n");
78 | }
79 | sb.append(methods);
80 | sb.append("}\n");
81 | return sb;
82 | }
83 |
84 | private StringBuilder toImplementsDeclaration(List types) {
85 | StringBuilder sb = new StringBuilder();
86 | for (JavaType type : types) {
87 | if (sb.length() > 0) {
88 | sb.append(", ");
89 | }
90 | sb.append(imports.getSimpleName(type));
91 | }
92 | return sb;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |