├── .gitattributes ├── .gitignore ├── .idea ├── .gitignore ├── IntelliLang.xml ├── ant.xml ├── codeStyleSettings.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── copyright │ ├── Apache_2_0.xml │ └── profiles_settings.xml ├── dictionaries │ └── ORFJackal.xml ├── encodings.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── libraries │ ├── Maven__aopalliance_aopalliance_1_0.xml │ ├── Maven__com_google_caliper_caliper_1_0_beta_2.xml │ ├── Maven__com_google_code_findbugs_jsr305_2_0_1.xml │ ├── Maven__com_google_code_gson_gson_2_2_2.xml │ ├── Maven__com_google_code_java_allocation_instrumenter_java_allocation_instrumenter_3_0.xml │ ├── Maven__com_google_guava_guava_18_0.xml │ ├── Maven__com_google_inject_extensions_guice_assistedinject_3_0.xml │ ├── Maven__com_google_inject_extensions_guice_multibindings_3_0.xml │ ├── Maven__com_google_inject_guice_3_0.xml │ ├── Maven__com_intellij_annotations_9_0_4.xml │ ├── Maven__com_sun_jersey_jersey_client_1_11.xml │ ├── Maven__com_sun_jersey_jersey_core_1_11.xml │ ├── Maven__commons_io_commons_io_2_4.xml │ ├── Maven__fi_luontola_buildtest_buildtest_1_0_2.xml │ ├── Maven__javax_inject_javax_inject_1.xml │ ├── Maven__joda_time_joda_time_2_1.xml │ ├── Maven__junit_junit_4_11.xml │ ├── Maven__org_apache_commons_commons_math_2_2.xml │ ├── Maven__org_easytesting_fest_assert_1_4.xml │ ├── Maven__org_easytesting_fest_util_1_1_6.xml │ ├── Maven__org_hamcrest_hamcrest_core_1_3.xml │ ├── Maven__org_hamcrest_hamcrest_library_1_3.xml │ ├── Maven__org_mockito_mockito_core_1_9_5.xml │ ├── Maven__org_objenesis_objenesis_1_0.xml │ ├── Maven__org_ow2_asm_asm_5_0_3.xml │ ├── Maven__org_ow2_asm_asm_analysis_5_0_3.xml │ ├── Maven__org_ow2_asm_asm_commons_5_0_3.xml │ ├── Maven__org_ow2_asm_asm_debug_all_5_0_3.xml │ ├── Maven__org_ow2_asm_asm_tree_5_0_3.xml │ ├── Maven__org_ow2_asm_asm_util_5_0_3.xml │ └── Maven__org_ow2_asm_asm_xml_5_0_3.xml ├── misc.xml ├── modules.xml ├── projectCodeStyle.xml ├── runConfigurations │ ├── All_tests.xml │ ├── Clean_build.xml │ ├── End_to_end_tests.xml │ ├── End_to_end_tests__check_threads_.xml │ ├── End_to_end_tests__no_rebuild_.xml │ ├── Generate_sources.xml │ └── Incremental_build.xml ├── scopes │ └── scope_settings.xml ├── uiDesigner.xml └── vcs.xml ├── BUILDING.txt ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── RELEASE-NOTES.md ├── TODO.txt ├── end-to-end-tests ├── end-to-end-tests.iml ├── pom.xml └── src │ └── test │ ├── java │ └── fi │ │ └── jumi │ │ └── test │ │ ├── BuildTest.java │ │ ├── GuineaPig.java │ │ ├── JumiActorsGeneratorTest.java │ │ └── TestEnvironment.java │ └── resources │ └── testing.properties ├── jumi-actors-generator ├── jumi-actors-generator.iml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── fi │ │ │ └── jumi │ │ │ └── actors │ │ │ └── generator │ │ │ ├── AnnotationProcessor.java │ │ │ ├── EventStubGenerator.java │ │ │ ├── GenerateEventizer.java │ │ │ ├── ast │ │ │ ├── AstExtractor.java │ │ │ ├── JavaSourceFromString.java │ │ │ └── LibrarySourceLocator.java │ │ │ └── codegen │ │ │ ├── ClassBuilder.java │ │ │ ├── GeneratedClass.java │ │ │ ├── Imports.java │ │ │ ├── JavaClasses.java │ │ │ ├── JavaMethod.java │ │ │ ├── JavaType.java │ │ │ └── JavaVar.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── javax.annotation.processing.Processor │ └── test │ └── java │ └── fi │ └── jumi │ └── actors │ └── generator │ ├── AnnotationProcessorTest.java │ ├── DummyListener.java │ ├── EnclosingClass.java │ ├── EventStubGeneratorTest.java │ ├── ast │ └── LibrarySourceLocatorTest.java │ ├── codegen │ ├── ImportsTest.java │ └── JavaTypeTest.java │ └── reference │ ├── DummyListenerEventizer.java │ └── dummyListener │ ├── DummyListenerToEvent.java │ ├── EventToDummyListener.java │ ├── OnOtherEvent.java │ └── OnSomethingEvent.java ├── jumi-actors ├── jumi-actors.iml ├── pom.xml └── src │ ├── main │ └── java │ │ └── fi │ │ └── jumi │ │ └── actors │ │ ├── ActorRef.java │ │ ├── ActorThread.java │ │ ├── Actors.java │ │ ├── Callback.java │ │ ├── MessageProcessor.java │ │ ├── MultiThreadedActors.java │ │ ├── Promise.java │ │ ├── SingleThreadedActors.java │ │ ├── eventizers │ │ ├── ComposedEventizerProvider.java │ │ ├── Event.java │ │ ├── EventToString.java │ │ ├── Eventizer.java │ │ ├── EventizerProvider.java │ │ ├── Eventizers.java │ │ ├── dynamic │ │ │ ├── DynamicEvent.java │ │ │ ├── DynamicEventizer.java │ │ │ ├── DynamicEventizerProvider.java │ │ │ ├── DynamicListenerToEvent.java │ │ │ ├── EventToDynamicListener.java │ │ │ └── package-info.java │ │ └── package-info.java │ │ ├── listeners │ │ ├── CrashEarlyFailureHandler.java │ │ ├── FailureHandler.java │ │ ├── MessageListener.java │ │ ├── NullMessageListener.java │ │ ├── PrintStreamFailureLogger.java │ │ ├── PrintStreamMessageLogger.java │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── queue │ │ ├── MessageQueue.java │ │ ├── MessageReceiver.java │ │ ├── MessageSender.java │ │ └── package-info.java │ │ └── workers │ │ ├── WorkerCounter.java │ │ ├── WorkerListener.java │ │ └── package-info.java │ └── test │ └── java │ └── fi │ └── jumi │ └── actors │ ├── ActorInterfaceContractsTest.java │ ├── ActorsContract.java │ ├── ActorsContractHelpers.java │ ├── DummyException.java │ ├── EventSpy.java │ ├── Matchers.java │ ├── MultiThreadedActorsTest.java │ ├── SingleThreadedActorsTest.java │ ├── UncaughtExceptionCollector.java │ ├── benchmarks │ ├── BusyWaitBarrier.java │ ├── Ring.java │ ├── RingBenchmark.java │ ├── RingStart.java │ ├── RingTest.java │ └── WarmStartupBenchmark.java │ ├── eventizers │ ├── ComposedEventizerProviderTest.java │ ├── EventToStringTest.java │ └── dynamic │ │ ├── DynamicEventizerBenchmark.java │ │ ├── DynamicEventizerProviderTest.java │ │ └── DynamicEventizerTest.java │ ├── examples │ ├── HelloWorld.java │ ├── Pi.java │ └── PiTest.java │ ├── listeners │ ├── ContainsLineWithWordsMatcherTest.java │ ├── PrintStreamFailureLoggerTest.java │ └── PrintStreamMessageLoggerTest.java │ ├── queue │ └── MessageQueueTest.java │ └── workers │ └── WorkerCounterTest.java ├── parent ├── build.properties ├── parent.iml └── pom.xml ├── pom.xml ├── project.iml └── thread-safety-agent ├── pom.xml ├── src ├── main │ └── java │ │ └── fi │ │ └── jumi │ │ └── threadsafetyagent │ │ ├── AddThreadSafetyChecks.java │ │ ├── EnabledWhenAnnotatedWith.java │ │ ├── PreMain.java │ │ ├── ThreadSafetyChecker.java │ │ ├── ThreadSafetyCheckerTransformer.java │ │ └── util │ │ ├── AbstractTransformationChain.java │ │ └── DoNotTransformException.java └── test │ └── java │ └── fi │ └── jumi │ └── threadsafetyagent │ ├── AddThreadSafetyChecksTest.java │ ├── ThreadSafetyCheckerTest.java │ ├── ThreadUtil.java │ └── util │ ├── ClassFileTransformerTest.java │ ├── ClassNameMatcher.java │ ├── ClassNameMatcherTest.java │ ├── NullClassFileTransformer.java │ └── TransformationTestClassLoader.java └── thread-safety-agent.iml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.txt eol=lf 2 | *.md eol=lf 3 | *.java eol=lf 4 | *.properties eol=lf 5 | *.rb eol=lf 6 | *.xml eol=lf 7 | *.html eol=lf 8 | *.iml eol=lf 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /*/target/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | /workspace.xml 2 | -------------------------------------------------------------------------------- /.idea/IntelliLang.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringAssert.matches (org.fest.assertions) 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/ant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 86 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.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 | 53 | -------------------------------------------------------------------------------- /.idea/copyright/Apache_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | 13 | 14 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/dictionaries/ORFJackal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | capturer 5 | demuxer 6 | denormalizer 7 | eventizer 8 | eventizers 9 | jumi 10 | stderr 11 | stdout 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__aopalliance_aopalliance_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_caliper_caliper_1_0_beta_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_code_findbugs_jsr305_2_0_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_code_gson_gson_2_2_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_code_java_allocation_instrumenter_java_allocation_instrumenter_3_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_guava_guava_18_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_inject_extensions_guice_assistedinject_3_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_inject_extensions_guice_multibindings_3_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_google_inject_guice_3_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_intellij_annotations_9_0_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_sun_jersey_jersey_client_1_11.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_sun_jersey_jersey_core_1_11.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__commons_io_commons_io_2_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__fi_luontola_buildtest_buildtest_1_0_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__javax_inject_javax_inject_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__joda_time_joda_time_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__junit_junit_4_11.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_commons_commons_math_2_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_easytesting_fest_assert_1_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_easytesting_fest_util_1_1_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_library_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_mockito_mockito_core_1_9_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_objenesis_objenesis_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_5_0_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_analysis_5_0_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_commons_5_0_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_debug_all_5_0_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_tree_5_0_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_util_5_0_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_xml_5_0_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 27 | 28 | 33 | 34 | 35 | http://www.w3.org/1999/xhtml 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/projectCodeStyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /.idea/runConfigurations/All_tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Clean_build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 36 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /.idea/runConfigurations/End_to_end_tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/runConfigurations/End_to_end_tests__check_threads_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/runConfigurations/End_to_end_tests__no_rebuild_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Generate_sources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 36 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Incremental_build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 36 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BUILDING.txt: -------------------------------------------------------------------------------- 1 | 2 | BUILD INSTRUCTIONS 3 | 4 | Requirements 5 | 6 | JDK 6 7 | JDK 8 8 | Maven 3 9 | 10 | Required environment variables 11 | 12 | JAVA6_HOME = JDK 6 directory 13 | JAVA8_HOME = JDK 8 directory 14 | JAVA_HOME = JDK 8 directory 15 | 16 | Regular build, execute all end-to-end tests 17 | 18 | mvn clean verify 19 | 20 | Also execute mutation tests and produce code coverage reports 21 | (they can be found from the /target/pit-reports directories) 22 | 23 | mvn clean verify -P coverage-report 24 | 25 | Looking at dependencies etc. requires first executing the package 26 | phase, because other modules depend on jumi-actors-maven-plugin to be 27 | found from the Maven reactor 28 | 29 | mvn package dependency:tree 30 | 31 | Some other useful commands 32 | 33 | mvn versions:display-plugin-updates 34 | mvn versions:display-dependency-updates 35 | mvn com.ning.maven.plugins:maven-duplicate-finder-plugin:1.0.2:check 36 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | Jumi Actors 3 | Copyright © 2011-2012, Esko Luontola 4 | This software is released under the Apache License 2.0. 5 | The license text is at http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | This software includes code developed at the Apache Software Foundation 8 | 9 | This software includes code from the ASM bytecode manipulation library 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Jumi Actors 3 | =========== 4 | 5 | Actor library for Java to support concurrency and asynchronous event-driven programming. 6 | 7 | For more information see 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /end-to-end-tests/src/test/java/fi/jumi/test/GuineaPig.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.jumi.actors.generator.GenerateEventizer; 8 | 9 | @GenerateEventizer 10 | public interface GuineaPig { 11 | 12 | void onFoo(String foo); 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /end-to-end-tests/src/test/resources/testing.properties: -------------------------------------------------------------------------------- 1 | test.projectArtifactsDir=${test.projectArtifactsDir} 2 | -------------------------------------------------------------------------------- /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-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/ast/JavaSourceFromString.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 javax.tools.SimpleJavaFileObject; 8 | import java.net.URI; 9 | 10 | public class JavaSourceFromString extends SimpleJavaFileObject { 11 | 12 | private final String code; 13 | 14 | public JavaSourceFromString(String name, String code) { 15 | super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); 16 | this.code = code; 17 | } 18 | 19 | @Override 20 | public CharSequence getCharContent(boolean ignoreEncodingErrors) { 21 | return code; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jumi-actors-generator/src/main/java/fi/jumi/actors/generator/ast/LibrarySourceLocator.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 com.google.common.io.CharStreams; 8 | 9 | import java.io.*; 10 | import java.net.*; 11 | import java.util.zip.*; 12 | 13 | public class LibrarySourceLocator { 14 | 15 | private static final File javaHome = new File(System.getProperty("java.home")); 16 | 17 | public String findSources(String className) { 18 | String result = null; 19 | String sourceFilePath = className.replace('.', '/') + ".java"; 20 | try { 21 | // TODO: consider using javax.annotation.processing.Filer.getResource and javax.tools.StandardLocation.SOURCE_PATH 22 | 23 | // classpath 24 | InputStream in = getClass().getClassLoader().getResourceAsStream(sourceFilePath); 25 | if (in != null) { 26 | result = toString(in); 27 | } 28 | 29 | // JDK 30 | if (result == null) { 31 | File jdkSourcesFile = new File(javaHome.getParentFile(), "src.zip"); 32 | if (jdkSourcesFile.isFile()) { 33 | result = findZipFileEntry(jdkSourcesFile, sourceFilePath); 34 | } 35 | } 36 | 37 | // JDK 6 on OS X 38 | if (result == null) { 39 | File jdkSourcesFile = new File(javaHome, "src.jar"); 40 | if (jdkSourcesFile.isFile()) { 41 | result = findZipFileEntry(jdkSourcesFile, "src/" + sourceFilePath); 42 | } 43 | } 44 | 45 | // local Maven repository 46 | if (result == null) { 47 | File classesJar = findJarContaining(className); 48 | if (classesJar != null) { 49 | File sourcesJar = new File(classesJar.getPath().replaceAll("\\.jar$", "-sources.jar")); 50 | if (sourcesJar.isFile()) { 51 | result = findZipFileEntry(sourcesJar, sourceFilePath); 52 | } 53 | } 54 | } 55 | } catch (IOException e) { 56 | System.err.println("Error: failed to read sources of " + className); 57 | e.printStackTrace(); 58 | } 59 | return result; 60 | } 61 | 62 | private File findJarContaining(String className) throws MalformedURLException { 63 | URL resource = getClass().getClassLoader().getResource(className.replace('.', '/') + ".class"); 64 | if (resource != null && resource.getProtocol().equals("jar")) { 65 | String path = new URL(resource.getPath()).getPath(); 66 | File file = new File(path.substring(0, path.indexOf("!"))); 67 | if (file.isFile()) { 68 | return file; 69 | } 70 | } 71 | return null; 72 | } 73 | 74 | private String findZipFileEntry(File zipFile, String entryName) throws IOException { 75 | ZipFile zip = null; 76 | try { 77 | zip = new ZipFile(zipFile); 78 | ZipEntry entry = zip.getEntry(entryName); 79 | if (entry == null) { 80 | return null; 81 | } 82 | return toString(zip.getInputStream(entry)); 83 | } finally { 84 | if (zip != null) { 85 | zip.close(); 86 | } 87 | } 88 | } 89 | 90 | private static String toString(InputStream in) throws IOException { 91 | try { 92 | return CharStreams.toString(new InputStreamReader(in, "UTF-8")); 93 | } finally { 94 | in.close(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /jumi-actors-generator/src/main/java/fi/jumi/actors/generator/codegen/GeneratedClass.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.actors.generator.codegen; 6 | 7 | public class GeneratedClass { 8 | 9 | public final String name; 10 | public final String source; 11 | 12 | public GeneratedClass(String name, String source) { 13 | this.name = name; 14 | this.source = source; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /jumi-actors-generator/src/main/java/fi/jumi/actors/generator/codegen/JavaMethod.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 JavaMethod { 11 | 12 | private final ExecutableElement element; 13 | 14 | public JavaMethod(ExecutableElement element) { 15 | this.element = element; 16 | } 17 | 18 | public String getName() { 19 | return element.getSimpleName().toString(); 20 | } 21 | 22 | public List getArguments() { 23 | ArrayList vars = new ArrayList<>(); 24 | for (VariableElement var : element.getParameters()) { 25 | vars.add(JavaVar.of(var)); 26 | } 27 | return vars; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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-generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | fi.jumi.actors.generator.AnnotationProcessor 2 | -------------------------------------------------------------------------------- /jumi-actors-generator/src/test/java/fi/jumi/actors/generator/DummyListener.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.actors.generator; 6 | 7 | @GenerateEventizer 8 | public interface DummyListener { 9 | 10 | void onSomething(String foo, String bar); 11 | 12 | void onOther(); 13 | } 14 | -------------------------------------------------------------------------------- /jumi-actors-generator/src/test/java/fi/jumi/actors/generator/EnclosingClass.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 | public class EnclosingClass { 8 | 9 | public interface MemberInterface { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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-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-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-generator/src/test/java/fi/jumi/actors/generator/reference/dummyListener/EventToDummyListener.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 EventToDummyListener implements MessageSender> { 12 | 13 | private final DummyListener target; 14 | 15 | public EventToDummyListener(DummyListener target) { 16 | this.target = target; 17 | } 18 | 19 | @Override 20 | public void send(Event message) { 21 | message.fireOn(target); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jumi-actors-generator/src/test/java/fi/jumi/actors/generator/reference/dummyListener/OnOtherEvent.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 OnOtherEvent implements Event, Serializable { 13 | 14 | @Override 15 | public void fireOn(DummyListener target) { 16 | target.onOther(); 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return EventToString.format("DummyListener", "onOther"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/main/java/fi/jumi/actors/Callback.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.Nullable; 8 | 9 | public interface Callback { 10 | 11 | void onResult(@Nullable T result); 12 | } 13 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/MessageProcessor.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 | interface MessageProcessor { 8 | 9 | void processNextMessage() throws InterruptedException; 10 | 11 | boolean processNextMessageIfAny(); 12 | } 13 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/main/java/fi/jumi/actors/eventizers/Event.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.actors.eventizers; 6 | 7 | public interface Event { 8 | 9 | void fireOn(T target); 10 | } 11 | -------------------------------------------------------------------------------- /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/main/java/fi/jumi/actors/eventizers/Eventizer.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 fi.jumi.actors.queue.MessageSender; 8 | 9 | /** 10 | * Converts method calls to event objects, and those event objects back to method calls. 11 | */ 12 | public interface Eventizer { 13 | 14 | Class getType(); 15 | 16 | T newFrontend(MessageSender> target); 17 | 18 | MessageSender> newBackend(T target); 19 | } 20 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/eventizers/EventizerProvider.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 fi.jumi.actors.Actors; 8 | import fi.jumi.actors.eventizers.dynamic.DynamicEventizerProvider; 9 | 10 | /** 11 | * Determines the types of actors that the {@link Actors} container can create. 12 | * 13 | * @see DynamicEventizerProvider 14 | * @see ComposedEventizerProvider 15 | */ 16 | public interface EventizerProvider { 17 | 18 | Eventizer getEventizerForType(Class type); 19 | } 20 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/eventizers/dynamic/EventToDynamicListener.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.Event; 8 | import fi.jumi.actors.queue.MessageSender; 9 | 10 | import javax.annotation.concurrent.ThreadSafe; 11 | 12 | @ThreadSafe 13 | public class EventToDynamicListener implements MessageSender> { 14 | 15 | private final T target; 16 | 17 | public EventToDynamicListener(T target) { 18 | this.target = target; 19 | } 20 | 21 | @Override 22 | public void send(Event message) { 23 | message.fireOn(target); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/eventizers/dynamic/package-info.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 | @ParametersAreNonnullByDefault 6 | package fi.jumi.actors.eventizers.dynamic; 7 | 8 | import javax.annotation.ParametersAreNonnullByDefault; 9 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/eventizers/package-info.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 | @ParametersAreNonnullByDefault 6 | package fi.jumi.actors.eventizers; 7 | 8 | import javax.annotation.ParametersAreNonnullByDefault; 9 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/listeners/CrashEarlyFailureHandler.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.SingleThreadedActors; 8 | 9 | import javax.annotation.concurrent.Immutable; 10 | 11 | /** 12 | * Used with {@link SingleThreadedActors} to fail the test when an actor throws an exception. 13 | */ 14 | @Immutable 15 | public class CrashEarlyFailureHandler implements FailureHandler { 16 | 17 | @Override 18 | public void uncaughtException(Object actor, Object message, Throwable exception) { 19 | throw new RuntimeException("uncaught exception from " + actor + " when processing message " + message, exception); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/listeners/FailureHandler.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 | /** 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 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/listeners/MessageListener.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 java.util.concurrent.Executor; 8 | 9 | /** 10 | * Gets notified about all messages that actors send and receive. Can also listen for all commands submitted to an 11 | * {@link Executor} by wrapping it in {@link #getListenedExecutor}. 12 | * 13 | * @see NullMessageListener 14 | * @see PrintStreamMessageLogger 15 | */ 16 | public interface MessageListener { 17 | 18 | void onMessageSent(Object message); 19 | 20 | void onProcessingStarted(Object actor, Object message); 21 | 22 | void onProcessingFinished(); 23 | 24 | Executor getListenedExecutor(Executor realExecutor); 25 | } 26 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/listeners/NullMessageListener.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.util.concurrent.Executor; 9 | 10 | /** 11 | * Does nothing. Meant for production use. 12 | */ 13 | @Immutable 14 | public class NullMessageListener implements MessageListener { 15 | 16 | @Override 17 | public void onMessageSent(Object message) { 18 | } 19 | 20 | @Override 21 | public void onProcessingStarted(Object actor, Object message) { 22 | } 23 | 24 | @Override 25 | public void onProcessingFinished() { 26 | } 27 | 28 | @Override 29 | public Executor getListenedExecutor(Executor realExecutor) { 30 | return realExecutor; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /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/src/main/java/fi/jumi/actors/listeners/PrintStreamMessageLogger.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.listeners; 6 | 7 | import javax.annotation.concurrent.ThreadSafe; 8 | import java.io.PrintStream; 9 | import java.util.Locale; 10 | import java.util.concurrent.Executor; 11 | 12 | /** 13 | * Prints all messages that actors send and receive. Meant for debugging. 14 | */ 15 | @ThreadSafe 16 | public class PrintStreamMessageLogger implements MessageListener { 17 | 18 | private static final String OUTGOING_MESSAGE = "->"; 19 | private static final String INCOMING_MESSAGE = "<-"; 20 | 21 | private final PrintStream out; 22 | private final ThreadLocal currentActor = new ThreadLocal<>(); 23 | private final long startTime; 24 | 25 | public PrintStreamMessageLogger(PrintStream out) { 26 | this.out = out; 27 | this.startTime = nanoTime(); 28 | } 29 | 30 | @Override 31 | public void onMessageSent(Object message) { 32 | logMessage(OUTGOING_MESSAGE, message); 33 | } 34 | 35 | @Override 36 | public void onProcessingStarted(Object actor, Object message) { 37 | currentActor.set(actor); 38 | logMessage(INCOMING_MESSAGE, message); 39 | } 40 | 41 | private void logMessage(String messageDirection, Object message) { 42 | String threadName = Thread.currentThread().getName(); 43 | int messageId = System.identityHashCode(message); 44 | out.println(String.format(Locale.ENGLISH, "[%11.6f] [%s] %s %s 0x%08x %s", 45 | secondsSinceStart(), threadName, currentActorFormatted(), messageDirection, messageId, message)); 46 | } 47 | 48 | private double secondsSinceStart() { 49 | long nanosSinceStart = nanoTime() - startTime; 50 | return nanosSinceStart / 1000000000.0; 51 | } 52 | 53 | protected long nanoTime() { // protected to allow overriding in tests 54 | return System.nanoTime(); 55 | } 56 | 57 | private String currentActorFormatted() { 58 | Object currentActor = this.currentActor.get(); 59 | if (currentActor == null) { 60 | return ""; 61 | } 62 | return currentActor.toString(); 63 | } 64 | 65 | @Override 66 | public void onProcessingFinished() { 67 | currentActor.remove(); 68 | } 69 | 70 | @Override 71 | public Executor getListenedExecutor(Executor realExecutor) { 72 | return new LoggedExecutor(realExecutor); 73 | } 74 | 75 | 76 | @ThreadSafe 77 | private class LoggedExecutor implements Executor { 78 | private final Executor realExecutor; 79 | 80 | public LoggedExecutor(Executor realExecutor) { 81 | this.realExecutor = realExecutor; 82 | } 83 | 84 | @Override 85 | public void execute(Runnable realCommand) { 86 | onMessageSent(realCommand); 87 | realExecutor.execute(new LoggedRunnable(realExecutor, realCommand)); 88 | } 89 | } 90 | 91 | @ThreadSafe 92 | private class LoggedRunnable implements Runnable { 93 | private final Executor realExecutor; 94 | private final Runnable realCommand; 95 | 96 | public LoggedRunnable(Executor realExecutor, Runnable realCommand) { 97 | this.realExecutor = realExecutor; 98 | this.realCommand = realCommand; 99 | } 100 | 101 | @Override 102 | public void run() { 103 | onProcessingStarted(realExecutor, realCommand); 104 | try { 105 | realCommand.run(); 106 | } finally { 107 | onProcessingFinished(); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/listeners/package-info.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 | @ParametersAreNonnullByDefault 6 | package fi.jumi.actors.listeners; 7 | 8 | import javax.annotation.ParametersAreNonnullByDefault; 9 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/package-info.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 | @ParametersAreNonnullByDefault 6 | package fi.jumi.actors; 7 | 8 | import javax.annotation.ParametersAreNonnullByDefault; 9 | -------------------------------------------------------------------------------- /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/src/main/java/fi/jumi/actors/queue/MessageReceiver.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 | 9 | public interface MessageReceiver { 10 | 11 | T take() throws InterruptedException; 12 | 13 | @Nullable 14 | T poll(); 15 | } 16 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/queue/MessageSender.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.queue; 6 | 7 | public interface MessageSender { 8 | 9 | void send(T message); 10 | } 11 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/queue/package-info.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 | @ParametersAreNonnullByDefault 6 | package fi.jumi.actors.queue; 7 | 8 | import javax.annotation.ParametersAreNonnullByDefault; 9 | -------------------------------------------------------------------------------- /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/workers/WorkerListener.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 | public interface WorkerListener { 8 | 9 | void onAllWorkersFinished(); 10 | } 11 | -------------------------------------------------------------------------------- /jumi-actors/src/main/java/fi/jumi/actors/workers/package-info.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 | @ParametersAreNonnullByDefault 6 | package fi.jumi.actors.workers; 7 | 8 | import javax.annotation.ParametersAreNonnullByDefault; 9 | -------------------------------------------------------------------------------- /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/src/test/java/fi/jumi/actors/DummyException.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 | public class DummyException extends RuntimeException { 8 | 9 | public DummyException() { 10 | } 11 | 12 | public DummyException(String message) { 13 | super(message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/benchmarks/BusyWaitBarrier.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 | public class BusyWaitBarrier { 8 | 9 | private volatile boolean triggered = false; 10 | 11 | public void trigger() { 12 | triggered = true; 13 | } 14 | 15 | public void await() { 16 | while (!triggered) { 17 | // busy wait 18 | } 19 | triggered = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jumi-actors/src/test/java/fi/jumi/actors/benchmarks/Ring.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.ActorRef; 8 | 9 | public interface Ring { 10 | 11 | void build(int ringSize, ActorRef first); 12 | 13 | void forward(int roundTrips); 14 | } 15 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /parent/build.properties: -------------------------------------------------------------------------------- 1 | revision=${env.RELEASE_REVISION} 2 | -------------------------------------------------------------------------------- /parent/parent.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /thread-safety-agent/src/main/java/fi/jumi/threadsafetyagent/PreMain.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; 6 | 7 | import java.lang.instrument.Instrumentation; 8 | 9 | public class PreMain { 10 | 11 | // For details on Java agents, see http://java.sun.com/javase/6/docs/api/java/lang/instrument/package-summary.html 12 | 13 | public static void premain(String agentArgs, Instrumentation inst) { 14 | inst.addTransformer(new ThreadSafetyCheckerTransformer()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /thread-safety-agent/src/main/java/fi/jumi/threadsafetyagent/ThreadSafetyCheckerTransformer.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; 6 | 7 | import fi.jumi.threadsafetyagent.util.AbstractTransformationChain; 8 | import org.objectweb.asm.ClassVisitor; 9 | 10 | public class ThreadSafetyCheckerTransformer extends AbstractTransformationChain { 11 | 12 | @Override 13 | protected ClassVisitor getAdapters(ClassVisitor cv) { 14 | // the adapter declared last is processed first 15 | cv = new AddThreadSafetyChecks(cv); 16 | cv = new EnabledWhenAnnotatedWith("javax/annotation/concurrent/NotThreadSafe", cv); 17 | return cv; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /thread-safety-agent/src/main/java/fi/jumi/threadsafetyagent/util/DoNotTransformException.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 | public class DoNotTransformException extends RuntimeException { 8 | 9 | @Override 10 | public Throwable fillInStackTrace() { 11 | // performance optimization: do not generate stack trace 12 | return this; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /thread-safety-agent/src/test/java/fi/jumi/threadsafetyagent/util/NullClassFileTransformer.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 java.lang.instrument.*; 8 | import java.security.ProtectionDomain; 9 | 10 | public class NullClassFileTransformer implements ClassFileTransformer { 11 | 12 | @Override 13 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, 14 | ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 15 | return classfileBuffer; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------